diff --git a/.github/workflows/docker-tools-build-push.yaml b/.github/workflows/docker-tools-build-push.yaml index 439f4fbe81..8bb2ed2d18 100644 --- a/.github/workflows/docker-tools-build-push.yaml +++ b/.github/workflows/docker-tools-build-push.yaml @@ -10,13 +10,11 @@ on: service_name: description: "Tool to build" required: true - default: "tool-structure" # Provide a default value + default: "tool-sidecar" # Provide a default value type: choice options: # Define available options - - tool-classifier - - tool-structure - - tool-text-extractor - tool-sidecar + - tool-text-extractor add_latest_tag: description: "Also tag as 'latest'" required: false @@ -58,18 +56,12 @@ jobs: - name: Set build configuration id: build-config run: | - if [ "${{ github.event.inputs.service_name }}" == "tool-classifier" ]; then - echo "context=." >> $GITHUB_OUTPUT - echo "dockerfile=./tools/classifier/Dockerfile" >> $GITHUB_OUTPUT - elif [ "${{ github.event.inputs.service_name }}" == "tool-structure" ]; then + if [ "${{ github.event.inputs.service_name }}" == "tool-sidecar" ]; then echo "context=." >> $GITHUB_OUTPUT - echo "dockerfile=./tools/structure/Dockerfile" >> $GITHUB_OUTPUT + echo "dockerfile=docker/dockerfiles/tool-sidecar.Dockerfile" >> $GITHUB_OUTPUT elif [ "${{ github.event.inputs.service_name }}" == "tool-text-extractor" ]; then echo "context=." >> $GITHUB_OUTPUT echo "dockerfile=./tools/text_extractor/Dockerfile" >> $GITHUB_OUTPUT - elif [ "${{ github.event.inputs.service_name }}" == "tool-sidecar" ]; then - echo "context=." >> $GITHUB_OUTPUT - echo "dockerfile=docker/dockerfiles/tool-sidecar.Dockerfile" >> $GITHUB_OUTPUT fi # Determine tags based on inputs diff --git a/.github/workflows/production-build.yaml b/.github/workflows/production-build.yaml index 21edb520f7..8a9745b309 100644 --- a/.github/workflows/production-build.yaml +++ b/.github/workflows/production-build.yaml @@ -101,7 +101,6 @@ jobs: backend, frontend, platform-service, - prompt-service, runner, worker-unified, x2text-service, @@ -225,7 +224,7 @@ jobs: id: summary run: | # Initialize variables - TOTAL_SERVICES=7 + TOTAL_SERVICES=6 OVERALL_RESULT='${{ needs.build-and-push.result }}' SUCCESS_COUNT=0 FAILED_COUNT=0 @@ -316,7 +315,7 @@ jobs: echo "|---------|--------|" >> $GITHUB_STEP_SUMMARY # Define services in order - for service in backend frontend platform-service prompt-service runner worker-unified x2text-service; do + for service in backend frontend platform-service runner worker-unified x2text-service; do if [ -f "build-status/${service}.json" ]; then STATUS=$(jq -r '.status' "build-status/${service}.json") if [ "$STATUS" = "success" ]; then diff --git a/backend/backend/settings/base.py b/backend/backend/settings/base.py index a77b44adaf..cb942d9c30 100644 --- a/backend/backend/settings/base.py +++ b/backend/backend/settings/base.py @@ -153,8 +153,6 @@ def get_required_setting(setting_key: str, default: str | None = None) -> str | FLIPT_BASE_URL = os.environ.get("FLIPT_BASE_URL", "http://localhost:9005") PLATFORM_HOST = os.environ.get("PLATFORM_SERVICE_HOST", "http://localhost") PLATFORM_PORT = os.environ.get("PLATFORM_SERVICE_PORT", 3001) -PROMPT_HOST = os.environ.get("PROMPT_HOST", "http://localhost") -PROMPT_PORT = os.environ.get("PROMPT_PORT", 3003) PROMPT_STUDIO_FILE_PATH = os.environ.get( "PROMPT_STUDIO_FILE_PATH", "/app/prompt-studio-data" ) diff --git a/backend/sample.env b/backend/sample.env index e1a54b955a..0bea8efbc8 100644 --- a/backend/sample.env +++ b/backend/sample.env @@ -90,10 +90,6 @@ UNSTRACT_RUNNER_API_TIMEOUT=240 # (in seconds) 2 mins UNSTRACT_RUNNER_API_RETRY_COUNT=5 # Number of retries for failed requests UNSTRACT_RUNNER_API_BACKOFF_FACTOR=3 # Exponential backoff factor for retries -# Prompt Service -PROMPT_HOST=http://unstract-prompt-service -PROMPT_PORT=3003 - #Prompt Studio PROMPT_STUDIO_FILE_PATH=/app/prompt-studio-data diff --git a/docker/compose.debug.yaml b/docker/compose.debug.yaml index 66d247997b..3522902b76 100644 --- a/docker/compose.debug.yaml +++ b/docker/compose.debug.yaml @@ -7,7 +7,6 @@ # 5678 - backend # 5679 - runner # 5680 - platform-service -# 5681 - prompt-service # 5682 - worker-file-processing-v2 # 5683 - worker-callback-v2 # 5684 - worker-api-deployment-v2 @@ -65,21 +64,6 @@ services: --graceful-timeout 5 unstract.platform_service.run:app" ] - prompt-service: - ports: - - "5681:5681" - command: [ - "uv run python -Xfrozen_modules=off -m debugpy --listen 0.0.0.0:5681 .venv/bin/gunicorn - --bind 0.0.0.0:3003 - --workers 1 - --threads 2 - --worker-class gthread - --log-level debug - --timeout 900 - --access-logfile - - --graceful-timeout 5 unstract.prompt_service.run:app" - ] - ######################################################################################################### # V2 Workers with debugpy # Using --pool=threads for debugpy compatibility (prefork doesn't work well with debugpy) diff --git a/docker/docker-compose.build.yaml b/docker/docker-compose.build.yaml index 3969b1f98f..8431138d2d 100644 --- a/docker/docker-compose.build.yaml +++ b/docker/docker-compose.build.yaml @@ -20,36 +20,21 @@ services: build: dockerfile: docker/dockerfiles/tool-sidecar.Dockerfile context: .. - tool-structure: - image: unstract/tool-structure:${VERSION} - build: - dockerfile: tools/structure/Dockerfile - context: .. - tool-text_extractor: - image: unstract/tool-text_extractor:${VERSION} - build: - dockerfile: tools/text_extractor/Dockerfile - context: .. - tool-classifier: - image: unstract/tool-classifier:${VERSION} - build: - dockerfile: tools/classifier/Dockerfile - context: .. platform-service: image: unstract/platform-service:${VERSION} build: dockerfile: docker/dockerfiles/platform.Dockerfile context: .. - prompt-service: - image: unstract/prompt-service:${VERSION} - build: - dockerfile: docker/dockerfiles/prompt.Dockerfile - context: .. x2text-service: image: unstract/x2text-service:${VERSION} build: dockerfile: docker/dockerfiles/x2text.Dockerfile context: .. + tool-text_extractor: + image: unstract/tool-text_extractor:${VERSION} + build: + dockerfile: tools/text_extractor/Dockerfile + context: .. # Unified worker image (replaces all individual worker images) worker-unified: image: unstract/worker-unified:${VERSION} diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index b7b49a7618..3406fa3d27 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -21,7 +21,6 @@ services: - minio - minio-bootstrap - platform-service - - prompt-service - x2text-service volumes: - prompt_studio_data:/app/prompt-studio-data @@ -151,25 +150,6 @@ services: labels: - traefik.enable=false - prompt-service: - image: unstract/prompt-service:${VERSION} - container_name: unstract-prompt-service - restart: unless-stopped - depends_on: - - db - - minio - - minio-bootstrap - - rabbitmq - ports: - - "3003:3003" - env_file: - - ../prompt-service/.env - labels: - - traefik.enable=false - extra_hosts: - # "host-gateway" is a special string that translates to host docker0 i/f IP. - - "host.docker.internal:host-gateway" - x2text-service: image: unstract/x2text-service:${VERSION} container_name: unstract-x2text-service diff --git a/docker/dockerfiles/prompt.Dockerfile b/docker/dockerfiles/prompt.Dockerfile deleted file mode 100644 index cca9161d81..0000000000 --- a/docker/dockerfiles/prompt.Dockerfile +++ /dev/null @@ -1,101 +0,0 @@ -# Use a specific version of Python slim image -FROM python:3.12-slim-trixie AS base - -LABEL maintainer="Zipstack Inc." \ - description="Prompt Service Container" - -ENV PYTHONDONTWRITEBYTECODE=1 \ - PYTHONUNBUFFERED=1 \ - PYTHONPATH=/unstract \ - BUILD_CONTEXT_PATH=prompt-service \ - BUILD_PACKAGES_PATH=unstract \ - TARGET_PLUGINS_PATH=src/unstract/prompt_service/plugins \ - APP_USER=unstract \ - APP_HOME=/app \ - # OpenTelemetry configuration (disabled by default, enable in docker-compose) - OTEL_TRACES_EXPORTER=none \ - OTEL_METRICS_EXPORTER=none \ - OTEL_LOGS_EXPORTER=none \ - OTEL_SERVICE_NAME=unstract_prompt - -# Install system dependencies, create user, and setup directories in one layer -RUN apt-get update \ - && apt-get --no-install-recommends install -y \ - build-essential \ - libmagic-dev \ - pkg-config \ - git \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/* \ - && adduser -u 5678 --disabled-password --gecos "" ${APP_USER} \ - && mkdir -p ${APP_HOME} \ - && chown -R ${APP_USER}:${APP_USER} ${APP_HOME} - -# Install uv package manager -COPY --from=ghcr.io/astral-sh/uv:0.6.14 /uv /uvx /bin/ - -# Create working directory -WORKDIR ${APP_HOME} - -# ----------------------------------------------- -# EXTERNAL DEPENDENCIES STAGE - This layer gets cached if external dependencies don't change -# ----------------------------------------------- -FROM base AS ext-dependencies - -# Copy dependency-related files -COPY --chown=${APP_USER}:${APP_USER} ${BUILD_CONTEXT_PATH}/pyproject.toml ${BUILD_CONTEXT_PATH}/uv.lock ${BUILD_CONTEXT_PATH}/README.md ./ - -# Copy local package dependencies -COPY --chown=${APP_USER}:${APP_USER} ${BUILD_PACKAGES_PATH}/sdk1 /unstract/sdk1 -COPY --chown=${APP_USER}:${APP_USER} ${BUILD_PACKAGES_PATH}/core /unstract/core -COPY --chown=${APP_USER}:${APP_USER} ${BUILD_PACKAGES_PATH}/flags /unstract/flags - -# Increase timeout for large packages (flipt-client is ~45MB) -ENV UV_HTTP_TIMEOUT=120 - -# Switch to non-root user -USER ${APP_USER} - -# Install external dependencies from pyproject.toml -RUN uv sync --group deploy --locked --no-install-project --no-dev - -# ----------------------------------------------- -# FINAL STAGE - Minimal image for production -# ----------------------------------------------- -FROM ext-dependencies AS production - -# Set shell options for better error handling -SHELL ["/bin/bash", "-o", "pipefail", "-c"] - -# Copy application code (this layer changes most frequently) -COPY --chown=${APP_USER}:${APP_USER} ${BUILD_CONTEXT_PATH} ./ - -# Switch to non-root user -USER ${APP_USER} - -# Install just the application in editable mode -RUN uv sync --group deploy --locked - -# Install plugins after copying source code -RUN for dir in "${TARGET_PLUGINS_PATH}"/*/; do \ - dirpath=${dir%*/}; \ - dirname=${dirpath##*/}; \ - if [ "${dirname}" != "*" ]; then \ - echo "Installing plugin: ${dirname}..." && \ - uv pip install "${TARGET_PLUGINS_PATH}/${dirname}"; \ - fi; \ - done && \ - uv run opentelemetry-bootstrap -a requirements | uv pip install --requirement - && \ - # Use OpenTelemetry v1 - v2 breaks LiteLLM with instrumentation enabled - uv pip uninstall opentelemetry-instrumentation-openai-v2 && \ - uv pip install opentelemetry-instrumentation-openai - -# Create required directories -RUN mkdir -p prompt-studio-data - -EXPOSE 3003 - -ARG VERSION=dev -ENV UNSTRACT_APPS_VERSION=${VERSION} - -CMD ["./entrypoint.sh"] diff --git a/docker/dockerfiles/prompt.Dockerfile.dockerignore b/docker/dockerfiles/prompt.Dockerfile.dockerignore deleted file mode 100644 index f2cffbf5be..0000000000 --- a/docker/dockerfiles/prompt.Dockerfile.dockerignore +++ /dev/null @@ -1,67 +0,0 @@ -**/__pycache__ -**/.mypy_cache -**/.pytest_cache -**/.python-version -**/.pyc -**/.pyo -**/.venv -**/.classpath -**/.dockerignore -**/.env -**/.git -**/.gitignore -**/.gitkeep -**/.project -**/.settings -**/.toolstarget -**/.vs -**/.vscode -**/*.*proj.user -**/*.dbmdl -**/*.jfm -**/bin -**/charts -**/docker-compose* -**/compose* -**/Dockerfile* -**/build -**/dist -**/node_modules -**/npm-debug.log -**/obj -**/secrets.dev.yaml -**/values.dev.yaml -**/.db -**/.sqlite3 -**/.log -**/*-log.txt -**/*.drawio -**/.tmp -**/.swp -**/.swo -**/.bak -*.idea -*.vscode -*.git -!LICENSE -*.md -!README.md -# But include the agent prompt markdown files -!*_system_prompt.md -.jshintrc -.pre-commit-config.yaml -**/tests -test*.py -**/*.egg-info - -backend -frontend -platform-service -!prompt-service -tools -unstract -!unstract/core -!unstract/flags -!unstract/sdk1 -runner -x2text-service diff --git a/docker/sample.compose.override.yaml b/docker/sample.compose.override.yaml index e762d45e79..a5a52cdd49 100644 --- a/docker/sample.compose.override.yaml +++ b/docker/sample.compose.override.yaml @@ -138,38 +138,6 @@ services: - action: rebuild path: ../platform-service/uv.lock - ######################################################################################################### - # Prompt Service (memory optimized: 1 worker, 2 threads) - prompt-service: - image: unstract/prompt-service:${VERSION} - build: - dockerfile: docker/dockerfiles/prompt.Dockerfile - context: .. - entrypoint: ["bash", "-c"] - command: [ - "uv run python -Xfrozen_modules=off .venv/bin/gunicorn - --bind 0.0.0.0:3003 - --workers 1 - --threads 2 - --worker-class gthread - --log-level debug - --timeout 900 - --access-logfile - - --graceful-timeout 5 unstract.prompt_service.run:app" - ] - develop: - watch: - - action: sync+restart - path: ../prompt-service/ - target: /app - ignore: [.venv/, __pycache__/, "*.pyc", .pytest_cache/, .mypy_cache/, node_modules/] - - action: sync+restart - path: ../unstract/ - target: /unstract - ignore: [.venv/, __pycache__/, "*.pyc", .pytest_cache/, .mypy_cache/] - - action: rebuild - path: ../prompt-service/uv.lock - ######################################################################################################### # X2Text Service x2text-service: diff --git a/docker/scripts/uv-lock-gen/README.md b/docker/scripts/uv-lock-gen/README.md index f5c565640e..2c37fa758f 100644 --- a/docker/scripts/uv-lock-gen/README.md +++ b/docker/scripts/uv-lock-gen/README.md @@ -2,11 +2,10 @@ Helps generate uv's lockfiles by running `uv sync` on all necessary packages and services. -It also detects **transitive dependency changes** — if a local path dependency's `pyproject.toml` changed (e.g. `unstract/sdk1`), all services that depend on it (e.g. `backend`, `prompt-service`) will have their lockfiles regenerated too. +It also detects **transitive dependency changes** — if a local path dependency's `pyproject.toml` changed (e.g. `unstract/sdk1`), all services that depend on it (e.g. `backend`, `workers`) will have their lockfiles regenerated too. - project root - `backend` -- `prompt-service` - `runner` - `unstract/core` - `unstract/flags` @@ -22,5 +21,5 @@ Can be run without any arguments to check for lockfile generation on all necessa Accepts a list of directories to generate for as command line arguments ```shell -./uv-lock.sh backend prompt-service +./uv-lock.sh backend platform-service ``` diff --git a/docker/scripts/uv-lock-gen/uv-lock.sh b/docker/scripts/uv-lock-gen/uv-lock.sh index 9c61bf0ed5..7180b9fd56 100755 --- a/docker/scripts/uv-lock-gen/uv-lock.sh +++ b/docker/scripts/uv-lock-gen/uv-lock.sh @@ -100,7 +100,6 @@ list_descendants () directories=( "." "backend" - "prompt-service" "platform-service" "runner" "x2text-service" diff --git a/prompt-service/.gitignore b/prompt-service/.gitignore deleted file mode 100644 index ae2f699ad0..0000000000 --- a/prompt-service/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# Plugins -src/unstract/prompt_service/plugins diff --git a/prompt-service/.python-version b/prompt-service/.python-version deleted file mode 100644 index f3fe474aee..0000000000 --- a/prompt-service/.python-version +++ /dev/null @@ -1 +0,0 @@ -3.12.9 diff --git a/prompt-service/README.md b/prompt-service/README.md deleted file mode 100644 index a6f313e012..0000000000 --- a/prompt-service/README.md +++ /dev/null @@ -1 +0,0 @@ -#TODO : Add README.md diff --git a/prompt-service/entrypoint.sh b/prompt-service/entrypoint.sh deleted file mode 100755 index a2eef06dc0..0000000000 --- a/prompt-service/entrypoint.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env bash - -show_help() { - echo "Usage: ./entrypoint.sh [OPTIONS]" - echo "" - echo "Options:" - echo " --dev Run Gunicorn in development mode with --reload and reduced graceful timeout (5s)." - echo " --help, -h Show this help message and exit." -} - -# Parse arguments -dev=false - -while [[ "$#" -gt 0 ]]; do - case $1 in - --dev) dev=true ;; - --help|-h) show_help; exit 0 ;; - *) echo "Unknown argument: $1"; exit 1 ;; - esac - shift -done - -gunicorn_args=( - --bind 0.0.0.0:3003 - --workers 2 - --threads 2 - --worker-class gthread - --log-level debug - --timeout 900 - --access-logfile - -) - -if [ "$dev" = true ]; then - echo "Running in development mode" - gunicorn_args+=(--reload --graceful-timeout 5) -else - echo "Running in production mode" -fi - -# Start Gunicorn -.venv/bin/gunicorn "${gunicorn_args[@]}" unstract.prompt_service.run:app diff --git a/prompt-service/pyproject.toml b/prompt-service/pyproject.toml deleted file mode 100644 index ae32c95201..0000000000 --- a/prompt-service/pyproject.toml +++ /dev/null @@ -1,63 +0,0 @@ -[project] -name = "unstract-prompt-service" -version = "0.0.1" -description = "Unstract's prompt studio helper" -authors = [{ name = "Zipstack Inc.", email = "devsupport@zipstack.com" }] -requires-python = ">=3.12,<3.13" -readme = "README.md" -# license = {text = "MIT"} - -dependencies = [ - "peewee~=3.16", - "nltk~=3.8", - "flask~=3.0", - "llama-index>=0.14.13", - "python-dotenv==1.0.1", - "json-repair~=0.42.0", - "requests>=2.28,<3.0", - "redis>=5.0.3,<5.3", - "unstract-core", - "unstract-flags", - "unstract-sdk1[aws,gcs,azure]" -] - -[tool.uv.sources] -unstract-flags = { path = "../unstract/flags", editable = true } -unstract-core = { path = "../unstract/core", editable = true } -unstract-sdk1 = { path = "../unstract/sdk1", editable = true } - -[dependency-groups] -test = [ - "pytest~=8.0.1", - "pytest-asyncio>=0.23.0", - "pytest-dotenv==0.5.2", - "pytest-mock~=3.14.0", - "pytest-md-report>=0.6.2", - "python-dotenv==1.0.1", - "flask-WTF~=1.1", -] -deploy = [ - "gunicorn~=23.0", - # OpenTelemetry for tracing and profiling - "opentelemetry-distro", - "opentelemetry-exporter-otlp", -] -dev = ["poethepoet>=0.33.1", "debugpy>=1.8.14"] - -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[tool.hatch.build.targets.wheel] -packages = ["src/unstract"] - -[tool.poe] -envfile = ".env" - -[tool.poe.tasks.prompt-service] -cmd = "./entrypoint.sh" -help = "Runs the Unstract prompt service (Gunicorn)" - -[tool.poe.tasks.prompt-service-flask] -cmd = "uv run flask --app src/unstract/prompt_service/run.py run --port 3003" -help = "Runs the Unstract prompt service (Flask)" diff --git a/prompt-service/sample.env b/prompt-service/sample.env deleted file mode 100644 index 01d021f9d1..0000000000 --- a/prompt-service/sample.env +++ /dev/null @@ -1,79 +0,0 @@ -# Backend DB -PG_BE_HOST=unstract-db -PG_BE_PORT=5432 -PG_BE_USERNAME=unstract_dev -PG_BE_PASSWORD=unstract_pass -PG_BE_DATABASE=unstract_db -DB_SCHEMA="unstract" - -# Redis -REDIS_HOST="unstract-redis" -REDIS_PORT=6379 -REDIS_PASSWORD="" -REDIS_USER=default - -# Redis Sentinel mode - set to True for HA deployments (K8s only) -# When True: point REDIS_HOST to Sentinel K8s service DNS, REDIS_PORT to 26379 -# REDIS_PASSWORD is reused for Sentinel auth. -REDIS_SENTINEL_MODE=False -REDIS_SENTINEL_MASTER_NAME=mymaster - -# Logging -LOG_LEVEL=INFO - - -### Env from `unstract-core` ### -# Celery for PublishLogs -CELERY_BROKER_BASE_URL="amqp://unstract-rabbitmq:5672//" -CELERY_BROKER_USER=admin -CELERY_BROKER_PASS=password -# Logs Expiry of 24 hours -LOGS_EXPIRATION_TIME_IN_SECOND=86400 - - -### Env from `unstract-flags` ### -# Feature Flags -EVALUATION_SERVER_IP=unstract-flipt -EVALUATION_SERVER_PORT=9000 -PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python -# Flipt Service -FLIPT_SERVICE_AVAILABLE=False - - -### Env from `unstract-sdk` ### -# Platform Service -PLATFORM_SERVICE_HOST=http://unstract-platform-service -PLATFORM_SERVICE_PORT=3001 - -# X2Text Service -X2TEXT_HOST=http://unstract-x2text-service -X2TEXT_PORT=3004 - -# Remote storage related envs -PERMANENT_REMOTE_STORAGE='{"provider": "minio", "credentials": {"endpoint_url": "http://unstract-minio:9000", "key": "minio", "secret": "minio123"}}' -TEMPORARY_REMOTE_STORAGE='{"provider": "minio", "credentials": {"endpoint_url": "http://unstract-minio:9000", "key": "minio", "secret": "minio123"}}' -REMOTE_PROMPT_STUDIO_FILE_PATH="unstract/prompt-studio-data/" - -# Timeout for LLMW (v2) extraction -ADAPTER_LLMW_WAIT_TIMEOUT=900 # 15 mins - -# Control async extraction of LLMWhisperer (v1) -# Time in seconds to wait before polling LLMWhisperer's status API -ADAPTER_LLMW_POLL_INTERVAL=30 -# Total number of times to poll the status API. -# 500 mins to allow 1500 (max pages limit) * 20 (approx time in sec to process a page) -ADAPTER_LLMW_MAX_POLLS=1000 -# Number of times to retry the /whisper-status API before failing the extraction -ADAPTER_LLMW_STATUS_RETRIES=5 -# Retry backoff for LLMWhisperer client (v2) -# Max retry attempts for transient HTTP errors (429, 5xx). Set 0 to disable. -ADAPTER_LLMW_MAX_RETRIES=3 -# Min backoff wait in seconds between retries -ADAPTER_LLMW_RETRY_MIN_WAIT=1.0 -# Max backoff wait in seconds between retries -ADAPTER_LLMW_RETRY_MAX_WAIT=60.0 - -### Env for Rentroll Service ### -# Rentroll Service -RENTROLL_SERVICE_HOST=http://unstract-rentroll-service -RENTROLL_SERVICE_PORT=5003 diff --git a/prompt-service/src/unstract/prompt_service/__init__.py b/prompt-service/src/unstract/prompt_service/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/prompt-service/src/unstract/prompt_service/config.py b/prompt-service/src/unstract/prompt_service/config.py deleted file mode 100644 index 5edfb829ed..0000000000 --- a/prompt-service/src/unstract/prompt_service/config.py +++ /dev/null @@ -1,54 +0,0 @@ -import logging -import warnings -from os import environ as env -from pathlib import Path - -from dotenv import load_dotenv -from flask import Flask - -from unstract.core.flask import ( - PluginManager, - register_error_handlers, - register_request_id_middleware, -) -from unstract.core.flask.logging import setup_logging -from unstract.prompt_service.controllers import api -from unstract.sdk1.constants import LogLevel - -load_dotenv() - - -def create_app() -> Flask: - """Creates and configures the Flask application.""" - log_level = env.get("LOG_LEVEL", LogLevel.INFO.value).upper() - setup_logging(log_level) - - # Suppress OpenTelemetry EventLogger LogRecord deprecation warning - # This is a bug in OpenTelemetry SDK 1.37.0 where EventLogger.emit() still uses - # deprecated trace_id/span_id/trace_flags parameters instead of context parameter. - # See: https://github.com/open-telemetry/opentelemetry-python/issues/4328 - # TODO: Remove this suppression once OpenTelemetry fixes EventLogger.emit() - warnings.filterwarnings( - "ignore", - message="LogRecord init with.*trace_id.*span_id.*trace_flags.*deprecated", - category=DeprecationWarning, - module="opentelemetry.sdk._events", - ) - - log_level = getattr(logging, log_level, logging.INFO) - app = Flask("prompt-service") - app.logger.setLevel(log_level) - app.logger.info("Initializing Flask application...") - - # Load plugins - plugins_dir = Path(__file__).parent / "plugins" - plugins_pkg = "unstract.prompt_service.plugins" - manager = PluginManager(app, plugins_dir, plugins_pkg) - manager.load_plugins() - - register_request_id_middleware(app) - register_error_handlers(app) - app.register_blueprint(api) - - app.logger.info("Flask app created successfully.") - return app diff --git a/prompt-service/src/unstract/prompt_service/constants.py b/prompt-service/src/unstract/prompt_service/constants.py deleted file mode 100644 index 9eddab8423..0000000000 --- a/prompt-service/src/unstract/prompt_service/constants.py +++ /dev/null @@ -1,203 +0,0 @@ -from enum import Enum - - -class PromptServiceConstants: - """Constants used in the prompt service.""" - - WORD = "word" - SYNONYMS = "synonyms" - OUTPUTS = "outputs" - TOOL_ID = "tool_id" - RUN_ID = "run_id" - EXECUTION_ID = "execution_id" - FILE_NAME = "file_name" - FILE_HASH = "file_hash" - NAME = "name" - ACTIVE = "active" - PROMPT = "prompt" - CHUNK_SIZE = "chunk-size" - PROMPTX = "promptx" - VECTOR_DB = "vector-db" - EMBEDDING = "embedding" - X2TEXT_ADAPTER = "x2text_adapter" - CHUNK_OVERLAP = "chunk-overlap" - LLM = "llm" - IS_ASSERT = "is_assert" - ASSERTION_FAILURE_PROMPT = "assertion_failure_prompt" - RETRIEVAL_STRATEGY = "retrieval-strategy" - TYPE = "type" - NUMBER = "number" - EMAIL = "email" - DATE = "date" - BOOLEAN = "boolean" - JSON = "json" - PREAMBLE = "preamble" - SIMILARITY_TOP_K = "similarity-top-k" - PROMPT_TOKENS = "prompt_tokens" - COMPLETION_TOKENS = "completion_tokens" - TOTAL_TOKENS = "total_tokens" - RESPONSE = "response" - POSTAMBLE = "postamble" - GRAMMAR = "grammar" - PLATFORM_SERVICE_API_KEY = "PLATFORM_SERVICE_API_KEY" - EMBEDDING_SUFFIX = "embedding_suffix" - EVAL_SETTINGS = "eval_settings" - EVAL_SETTINGS_EVALUATE = "evaluate" - EVAL_SETTINGS_MONITOR_LLM = "monitor_llm" - EVAL_SETTINGS_EXCLUDE_FAILED = "exclude_failed" - TOOL_SETTINGS = "tool_settings" - LOG_EVENTS_ID = "log_events_id" - CHALLENGE_LLM = "challenge_llm" - CHALLENGE = "challenge" - ENABLE_CHALLENGE = "enable_challenge" - EXTRACTION = "extraction" - SUMMARIZE = "summarize" - SINGLE_PASS_EXTRACTION = "single-pass-extraction" - SIMPLE_PROMPT_STUDIO = "simple-prompt-studio" - LLM_USAGE_REASON = "llm_usage_reason" - METADATA = "metadata" - OUTPUT = "output" - CONTEXT = "context" - INCLUDE_METADATA = "include_metadata" - TABLE = "table" - TABLE_SETTINGS = "table_settings" - EPILOGUE = "epilogue" - PLATFORM_POSTAMBLE = "platform_postamble" - WORD_CONFIDENCE_POSTAMBLE = "word_confidence_postamble" - HIGHLIGHT_DATA_PLUGIN = "highlight-data" - SUMMARIZE_AS_SOURCE = "summarize_as_source" - VARIABLE_MAP = "variable_map" - RECORD = "record" - CUSTOM_DATA = "custom_data" - TEXT = "text" - ENABLE_HIGHLIGHT = "enable_highlight" - ENABLE_WORD_CONFIDENCE = "enable_word_confidence" - FILE_PATH = "file_path" - HIGHLIGHT_DATA = "highlight_data" - CONFIDENCE_DATA = "confidence_data" - WORD_CONFIDENCE_DATA = "word_confidence_data" - REQUIRED_FIELDS = "required_fields" - REQUIRED = "required" - EXECUTION_SOURCE = "execution_source" - METRICS = "metrics" - CAPTURE_METRICS = "capture_metrics" - LINE_ITEM = "line-item" - LINE_NUMBERS = "line_numbers" - WHISPER_HASH = "whisper_hash" - PAID_FEATURE_MSG = ( - "It is a cloud / enterprise feature. If you have purchased a plan and still " - "face this issue, please contact support" - ) - NO_CONTEXT_ERROR = ( - "Couldn't fetch context from vector DB. " - "This happens usually due to a delay by the Vector DB " - "provider to confirm writes to DB. " - "Please try again after some time" - ) - COMBINED_PROMPT = "combined_prompt" - TOOL = "tool" - JSON_POSTAMBLE = "JSON_POSTAMBLE" - DEFAULT_JSON_POSTAMBLE = "Wrap the final JSON result inbetween §§§ like below example:\n§§§\n\n§§§" - DOCUMENT_TYPE = "document_type" - # Webhook postprocessing settings - ENABLE_POSTPROCESSING_WEBHOOK = "enable_postprocessing_webhook" - POSTPROCESSING_WEBHOOK_URL = "postprocessing_webhook_url" - - -class RunLevel(Enum): - """Different stages of prompt execution. - - Comprises of prompt run and response evaluation stages. - """ - - RUN = "RUN" - EVAL = "EVAL" - CHALLENGE = "CHALLENGE" - TABLE_EXTRACTION = "TABLE_EXTRACTION" - - -class DBTableV2: - """Database tables.""" - - ORGANIZATION = "organization" - ADAPTER_INSTANCE = "adapter_instance" - PROMPT_STUDIO_REGISTRY = "prompt_studio_registry" - PLATFORM_KEY = "platform_key" - TOKEN_USAGE = "usage" - - -class FileStorageKeys: - """File storage keys.""" - - PERMANENT_REMOTE_STORAGE = "PERMANENT_REMOTE_STORAGE" - TEMPORARY_REMOTE_STORAGE = "TEMPORARY_REMOTE_STORAGE" - - -class FileStorageType(Enum): - """File storage type.""" - - PERMANENT = "permanent" - TEMPORARY = "temporary" - - -class ExecutionSource(Enum): - """Execution source.""" - - IDE = "ide" - TOOL = "tool" - - -class VariableType(str, Enum): - """Type of variable.""" - - STATIC = "STATIC" - DYNAMIC = "DYNAMIC" - CUSTOM_DATA = "CUSTOM_DATA" - - -class RetrievalStrategy(str, Enum): - """Available retrieval strategies for prompt service.""" - - SIMPLE = "simple" - SUBQUESTION = "subquestion" - FUSION = "fusion" - RECURSIVE = "recursive" - ROUTER = "router" - KEYWORD_TABLE = "keyword_table" - AUTOMERGING = "automerging" - - -class VariableConstants: - """Constants for variable extraction.""" - - VARIABLE_REGEX = "{{(.+?)}}" - DYNAMIC_VARIABLE_DATA_REGEX = r"\[(.*?)\]" - DYNAMIC_VARIABLE_URL_REGEX = ( - r"(?i)\b((?:https?://|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'\".,<>?«»" - "'']))" - ) # noqa: E501 - CUSTOM_DATA_VARIABLE_REGEX = r"custom_data\.([a-zA-Z0-9_\.]+)" - - -class IndexingConstants: - TOOL_ID = "tool_id" - EMBEDDING_INSTANCE_ID = "embedding_instance_id" - VECTOR_DB_INSTANCE_ID = "vector_db_instance_id" - X2TEXT_INSTANCE_ID = "x2text_instance_id" - FILE_PATH = "file_path" - CHUNK_SIZE = "chunk_size" - CHUNK_OVERLAP = "chunk_overlap" - REINDEX = "reindex" - FILE_HASH = "file_hash" - OUTPUT_FILE_PATH = "output_file_path" - ENABLE_HIGHLIGHT = "enable_highlight" - ENABLE_WORD_CONFIDENCE = "enable_word_confidence" - USAGE_KWARGS = "usage_kwargs" - PROCESS_TEXT = "process_text" - EXTRACTED_TEXT = "extracted_text" - TAGS = "tags" - EXECUTION_SOURCE = "execution_source" - DOC_ID = "doc_id" - TOOL_EXECUTION_METATADA = "tool_execution_metadata" - EXECUTION_DATA_DIR = "execution_data_dir" - METADATA_FILE = "METADATA.json" diff --git a/prompt-service/src/unstract/prompt_service/controllers/__init__.py b/prompt-service/src/unstract/prompt_service/controllers/__init__.py deleted file mode 100644 index e6b149de21..0000000000 --- a/prompt-service/src/unstract/prompt_service/controllers/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -from flask import Blueprint - -from .answer_prompt import answer_prompt_bp -from .extraction import extraction_bp -from .health import health_bp -from .indexing import indexing_bp - -api = Blueprint("api", __name__) - -# Register blueprint to the API Blueprint -api.register_blueprint(health_bp) -api.register_blueprint(answer_prompt_bp) -api.register_blueprint(indexing_bp) -api.register_blueprint(extraction_bp) diff --git a/prompt-service/src/unstract/prompt_service/controllers/answer_prompt.py b/prompt-service/src/unstract/prompt_service/controllers/answer_prompt.py deleted file mode 100644 index fdf0deaae1..0000000000 --- a/prompt-service/src/unstract/prompt_service/controllers/answer_prompt.py +++ /dev/null @@ -1,709 +0,0 @@ -"""Published API Controller""" - -from typing import Any - -from flask import Blueprint, request -from flask import current_app as app - -from unstract.core.flask import PluginManager -from unstract.core.flask.exceptions import APIError -from unstract.prompt_service.constants import PromptServiceConstants as PSKeys -from unstract.prompt_service.constants import RetrievalStrategy, RunLevel -from unstract.prompt_service.exceptions import BadRequest -from unstract.prompt_service.helpers.auth import AuthHelper -from unstract.prompt_service.helpers.prompt_ide_base_tool import PromptServiceBaseTool -from unstract.prompt_service.helpers.usage import UsageHelper -from unstract.prompt_service.services.answer_prompt import AnswerPromptService -from unstract.prompt_service.services.rentrolls_extractor.interface import ( - RentRollExtractor, -) -from unstract.prompt_service.services.retrieval import RetrievalService -from unstract.prompt_service.services.variable_replacement import ( - VariableReplacementService, -) -from unstract.prompt_service.utils.file_utils import FileUtils -from unstract.prompt_service.utils.log import publish_log -from unstract.sdk1.constants import LogLevel -from unstract.sdk1.embedding import EmbeddingCompat -from unstract.sdk1.exceptions import SdkError -from unstract.sdk1.index import Index -from unstract.sdk1.llm import LLM -from unstract.sdk1.platform import PlatformHelper as ToolAdapter -from unstract.sdk1.vector_db import VectorDB - -answer_prompt_bp = Blueprint("answer-prompt", __name__) - - -@AuthHelper.auth_required -@answer_prompt_bp.route("/answer-prompt", methods=["POST"]) -def prompt_processor() -> Any: - platform_key = AuthHelper.get_token_from_auth_header(request) - payload: dict[Any, Any] = request.json - if not payload: - raise BadRequest - tool_settings = payload.get(PSKeys.TOOL_SETTINGS, {}) - enable_challenge = tool_settings.get(PSKeys.ENABLE_CHALLENGE, False) - # Rename "outputs" to "prompts" in payload - prompts = payload.get(PSKeys.OUTPUTS, []) - tool_id: str = payload.get(PSKeys.TOOL_ID, "") - run_id: str = payload.get(PSKeys.RUN_ID, "") - execution_id: str = payload.get(PSKeys.EXECUTION_ID, "") - file_hash = payload.get(PSKeys.FILE_HASH) - file_path = payload.get(PSKeys.FILE_PATH) - doc_name = str(payload.get(PSKeys.FILE_NAME, "")) - log_events_id: str = payload.get(PSKeys.LOG_EVENTS_ID, "") - custom_data: dict[str, Any] = payload.get(PSKeys.CUSTOM_DATA, {}) - structured_output: dict[str, Any] = {} - metadata: dict[str, Any] = { - PSKeys.RUN_ID: run_id, - PSKeys.FILE_NAME: doc_name, - PSKeys.CONTEXT: {}, - PSKeys.REQUIRED_FIELDS: {}, - } - metrics: dict = {} - variable_names: list[str] = [] - # Identifier for source of invocation - execution_source = payload.get(PSKeys.EXECUTION_SOURCE, "") - context_retrieval_metrics = {} - publish_log( - log_events_id, - {"tool_id": tool_id, "run_id": run_id, "doc_name": doc_name}, - LogLevel.DEBUG, - RunLevel.RUN, - f"Preparing to execute '{len(prompts)}' prompt(s)", - ) - # Rename "output" to "prompt" - for output in prompts: # type:ignore - variable_names.append(output[PSKeys.NAME]) - metadata[PSKeys.REQUIRED_FIELDS][output[PSKeys.NAME]] = output.get( - PSKeys.REQUIRED, None - ) - - for output in prompts: # type:ignore - prompt_name = output[PSKeys.NAME] - prompt_text = output[PSKeys.PROMPT] - chunk_size = output[PSKeys.CHUNK_SIZE] - app.logger.info(f"[{tool_id}] chunk size: {chunk_size}") - util = PromptServiceBaseTool(platform_key=platform_key) - index = Index(tool=util, run_id=run_id, capture_metrics=True) - if VariableReplacementService.is_variables_present(prompt_text=prompt_text): - # Determine if this is from IDE (Prompt Studio) or API deployment - is_ide = execution_source == "ide" - prompt_text = VariableReplacementService.replace_variables_in_prompt( - prompt=output, - structured_output=structured_output, - log_events_id=log_events_id, - tool_id=tool_id, - prompt_name=prompt_name, - doc_name=doc_name, - custom_data=custom_data, - is_ide=is_ide, - ) - - app.logger.info(f"[{tool_id}] Executing prompt: '{prompt_name}'") - publish_log( - log_events_id, - { - "tool_id": tool_id, - "prompt_key": prompt_name, - "doc_name": doc_name, - }, - LogLevel.DEBUG, - RunLevel.RUN, - "Executing prompt", - ) - - # Finding and replacing the variables in the prompt - # The variables are in the form %variable_name% - - output[PSKeys.PROMPTX] = AnswerPromptService.extract_variable( - structured_output, variable_names, output, prompt_text - ) - - doc_id = index.generate_index_key( - file_hash=file_hash, - vector_db=output[PSKeys.VECTOR_DB], - embedding=output[PSKeys.EMBEDDING], - x2text=output[PSKeys.X2TEXT_ADAPTER], - chunk_size=str(output[PSKeys.CHUNK_SIZE]), - chunk_overlap=str(output[PSKeys.CHUNK_OVERLAP]), - ) - publish_log( - log_events_id, - { - "tool_id": tool_id, - "prompt_key": prompt_name, - "doc_name": doc_name, - }, - LogLevel.DEBUG, - RunLevel.RUN, - "Retrieved document ID", - ) - - try: - usage_kwargs = {"run_id": run_id, "execution_id": execution_id} - llm = LLM( - adapter_instance_id=output[PSKeys.LLM], - tool=util, - usage_kwargs={ - **usage_kwargs, - PSKeys.LLM_USAGE_REASON: PSKeys.EXTRACTION, - }, - capture_metrics=True, - ) - - # Only create embedding and vector_db if chunk_size > 0 - # When chunk_size is 0, we read the complete file without embeddings - embedding = None - vector_db = None - if chunk_size > 0: - embedding = EmbeddingCompat( - adapter_instance_id=output[PSKeys.EMBEDDING], - tool=util, - kwargs={ - **usage_kwargs, - }, - ) - - vector_db = VectorDB( - tool=util, - adapter_instance_id=output[PSKeys.VECTOR_DB], - embedding=embedding, - ) - except SdkError as e: - msg = f"Couldn't fetch adapter. {e}" - app.logger.error(msg) - publish_log( - log_events_id, - { - "tool_id": tool_id, - "prompt_key": prompt_name, - "doc_name": doc_name, - }, - LogLevel.ERROR, - RunLevel.RUN, - "Unable to obtain LLM / embedding / vectorDB", - ) - # Preserve the status code from the SDK error - status_code = getattr(e, "status_code", None) or 500 - raise APIError(message=msg, code=status_code) from e - - if output[PSKeys.TYPE] == PSKeys.TABLE: - adapter_parent_data = ToolAdapter.get_adapter_config(util, output[PSKeys.LLM]) - llm_config = adapter_parent_data.get("adapter_metadata") - adapter_id = adapter_parent_data.get("adapter_id") - adapter_prefix = adapter_id.split("|")[0] - llm_provider = llm.kwargs.get("provider") - llm_adapter_config = {"adapter_id": adapter_prefix} - llm_adapter_config["provider"] = llm_provider - if adapter_prefix == "azureopenai": - llm_adapter_config["model"] = llm_config.get("model") - llm_adapter_config["api_key"] = llm_config.get("api_key") - llm_adapter_config["api_base"] = llm_config.get("azure_endpoint") - llm_adapter_config["max_retries"] = llm_config.get("max_retries") - llm_adapter_config["timeout"] = llm_config.get("timeout") - llm_adapter_config["deployment"] = llm_config.get("deployment_name") - llm_adapter_config["api_version"] = llm_config.get("api_version") - if adapter_prefix == "openai": - llm_adapter_config["model"] = llm_config.get("model") - llm_adapter_config["api_key"] = llm_config.get("api_key") - llm_adapter_config["api_base"] = llm_config.get("api_base") - llm_adapter_config["max_retries"] = llm_config.get("max_retries") - llm_adapter_config["timeout"] = llm_config.get("timeout") - if adapter_prefix == "anthropic": - llm_adapter_config["model"] = llm_config.get("model") - llm_adapter_config["api_key"] = llm_config.get("api_key") - llm_adapter_config["max_retries"] = llm_config.get("max_retries") - llm_adapter_config["timeout"] = llm_config.get("timeout") - if adapter_prefix == "bedrock": - llm_adapter_config["model"] = llm_config.get("model") - llm_adapter_config["aws_access_key_id"] = llm_config.get( - "aws_access_key_id" - ) - llm_adapter_config["aws_secret_access_key"] = llm_config.get( - "aws_secret_access_key" - ) - llm_adapter_config["max_retries"] = llm_config.get("max_retries") - llm_adapter_config["budget_tokens"] = llm_config.get("budget_tokens") - llm_adapter_config["max_tokens"] = llm_config.get("max_tokens") - llm_adapter_config["region_name"] = llm_config.get("region_name") - llm_adapter_config["timeout"] = llm_config.get("timeout") - table_settings = output[PSKeys.TABLE_SETTINGS] - document_type: str = table_settings.get(PSKeys.DOCUMENT_TYPE) - app.logger.info("Document type: %s", document_type) - if document_type == "rent_rolls": - # Create RentRollExtractor instance and process the extraction - extractor = RentRollExtractor() - fs_instance = FileUtils.get_fs_instance(execution_source=execution_source) - extracted_data = fs_instance.read(path=file_path, mode="r") - app.logger.info("Starting rent roll extraction run...") - try: - extraction_result = extractor.process( - extractor_settings=output, - extracted_data=extracted_data, - llm_config=llm_adapter_config, - schema=prompt_text, - ) - - # Update structured output with the extraction result - structured_output[output[PSKeys.NAME]] = extraction_result["data"] - response = { - PSKeys.OUTPUT: structured_output, - PSKeys.METADATA: extraction_result["metrics"], - PSKeys.METRICS: extraction_result["metrics"], - } - - # Track token usage by sending to the audit service - try: - from unstract.sdk1.utils.token_counter import TokenCounter - - # Get metrics from the extraction result - metrics = extraction_result.get("metrics", {}) - - # Create token counter adapter from metrics - token_usage = metrics.get("token_usage") or {} - token_counter = TokenCounter( - input_tokens=token_usage.get("prompt_tokens"), - output_tokens=token_usage.get("completion_tokens"), - ) - - # Extract model name from llm config - # Extract model name from llm config - model_info = metrics.get("model_info") or {} - model_name = model_info.get("model_name") - provider = model_info.get("provider") - - # Prepare usage data for audit - kwargs = { - "workflow_id": "", # Not applicable for rent rolls - "execution_id": "", # Not applicable for rent rolls - "adapter_instance_id": adapter_id, - "run_id": str(run_id), - "provider": provider, - "llm_usage_reason": "extraction", - } - - # Push usage data to audit service - UsageHelper.push_usage_data( - event_type="extraction", - kwargs=kwargs, - platform_api_key=platform_key, - token_counter=token_counter, - model_name=model_name, - ) - app.logger.info( - "Successfully pushed token usage data to audit service" - ) - except Exception as e: - app.logger.error(f"Failed to track token usage: {str(e)}") - app.logger.info("Rent roll extraction completed successfully") - return response - except Exception as e: - app.logger.error(f"Failed to process rent roll: {str(e)}") - raise e - if document_type != "rent_rolls": - try: - structured_output = AnswerPromptService.extract_table( - output=output, - structured_output=structured_output, - llm=llm, - execution_source=execution_source, - prompt=prompt_text, - ) - metadata = UsageHelper.query_usage_metadata( - token=platform_key, metadata=metadata - ) - response = { - PSKeys.METADATA: metadata, - PSKeys.OUTPUT: structured_output, - PSKeys.METRICS: metrics, - } - return response - except APIError as api_error: - app.logger.error( - "Failed to extract table for the prompt %s: %s", - output[PSKeys.NAME], - str(api_error), - ) - publish_log( - log_events_id, - { - "tool_id": tool_id, - "prompt_key": prompt_name, - "doc_name": doc_name, - }, - LogLevel.ERROR, - RunLevel.TABLE_EXTRACTION, - "Error while extracting table for the prompt", - ) - raise api_error - elif output[PSKeys.TYPE] == PSKeys.LINE_ITEM: - try: - structured_output = AnswerPromptService.extract_line_item( - tool_settings=tool_settings, - output=output, - structured_output=structured_output, - llm=llm, - file_path=file_path, - metadata=metadata, - execution_source=execution_source, - ) - continue - except APIError as e: - app.logger.error( - "Failed to extract line-item for the prompt %s: %s", - output[PSKeys.NAME], - str(e), - ) - publish_log( - log_events_id, - { - "tool_id": tool_id, - "prompt_key": prompt_name, - "doc_name": doc_name, - }, - LogLevel.ERROR, - RunLevel.RUN, - "Error while extracting line-item for the prompt", - ) - raise e - - try: - answer = "NA" - publish_log( - log_events_id, - { - "tool_id": tool_id, - "prompt_key": prompt_name, - "doc_name": doc_name, - }, - LogLevel.INFO, - RunLevel.RUN, - "Retrieving context from adapter", - ) - - retrieval_strategy = output.get(PSKeys.RETRIEVAL_STRATEGY) - - # Get all valid retrieval strategies - valid_strategies = {strategy.value for strategy in RetrievalStrategy} - - if retrieval_strategy in valid_strategies: - app.logger.info(f"[{tool_id}] Performing retrieval for : {file_path}") - answer, context = RetrievalService.perform_retrieval( - tool_settings=tool_settings, - output=output, - doc_id=doc_id, - llm=llm, - vector_db=vector_db, # This will be None when chunk_size is 0 - retrieval_type=retrieval_strategy, - metadata=metadata, - chunk_size=chunk_size, - execution_source=execution_source, - file_path=file_path, - context_retrieval_metrics=context_retrieval_metrics, - ) - metadata[PSKeys.CONTEXT][output[PSKeys.NAME]] = context - else: - app.logger.info( - "Invalid retrieval strategy passed: %s", - retrieval_strategy, - ) - - publish_log( - log_events_id, - { - "tool_id": tool_id, - "prompt_key": prompt_name, - "doc_name": doc_name, - }, - LogLevel.DEBUG, - RunLevel.RUN, - "Retrieved context from adapter", - ) - - publish_log( - log_events_id, - { - "tool_id": tool_id, - "prompt_key": prompt_name, - "doc_name": doc_name, - }, - LogLevel.INFO, - RunLevel.RUN, - f"Processing prompt type: {output[PSKeys.TYPE]}", - ) - - if output[PSKeys.TYPE] == PSKeys.NUMBER: - if answer.lower() == "na": - structured_output[output[PSKeys.NAME]] = None - else: - # Extract these prompts as constants after pkging - prompt = f"Extract the number from the following \ - text:\n{answer}\n\nOutput just the number. \ - If the number is expressed in millions \ - or thousands, expand the number to its numeric value \ - The number should be directly assignable\ - to a numeric variable.\ - It should not have any commas, \ - percentages or other grouping \ - characters. No explanation is required.\ - If you cannot extract the number, output 0." - answer = AnswerPromptService.run_completion( - llm=llm, - prompt=prompt, - ) - try: - structured_output[output[PSKeys.NAME]] = float(answer) - except Exception as e: - app.logger.info( - f"Error parsing response (to numeric, float): {e}", - LogLevel.ERROR, - ) - structured_output[output[PSKeys.NAME]] = None - elif output[PSKeys.TYPE] == PSKeys.EMAIL: - if answer.lower() == "na": - structured_output[output[PSKeys.NAME]] = None - else: - prompt = f'Extract the email from the following text:\n{answer}\n\nOutput just the email. \ - The email should be directly assignable to a string variable. \ - No explanation is required. If you cannot extract the email, output "NA".' # noqa - answer = AnswerPromptService.run_completion( - llm=llm, - prompt=prompt, - ) - structured_output[output[PSKeys.NAME]] = answer - elif output[PSKeys.TYPE] == PSKeys.DATE: - if answer.lower() == "na": - structured_output[output[PSKeys.NAME]] = None - else: - prompt = f'Extract the date from the following text:\n{answer}\n\nOutput just the date.\ - The date should be in ISO date time format. No explanation is required. \ - The date should be directly assignable to a date variable. \ - If you cannot convert the string into a date, output "NA".' # noqa - answer = AnswerPromptService.run_completion( - llm=llm, - prompt=prompt, - ) - structured_output[output[PSKeys.NAME]] = answer - - elif output[PSKeys.TYPE] == PSKeys.BOOLEAN: - if answer.lower() == "na": - structured_output[output[PSKeys.NAME]] = None - else: - prompt = f'Extract yes/no from the following text:\n{answer}\n\n\ - Output in single word.\ - If the context is trying to convey that the answer is true, \ - then return "yes", else return "no".' - answer = AnswerPromptService.run_completion( - llm=llm, - prompt=prompt, - ) - if answer.lower() == "yes": - structured_output[output[PSKeys.NAME]] = True - else: - structured_output[output[PSKeys.NAME]] = False - elif output[PSKeys.TYPE] == PSKeys.JSON: - AnswerPromptService.handle_json( - answer=answer, - structured_output=structured_output, - output=output, - log_events_id=log_events_id, - tool_id=tool_id, - doc_name=doc_name, - llm=llm, - enable_highlight=tool_settings.get(PSKeys.ENABLE_HIGHLIGHT, False), - enable_word_confidence=tool_settings.get( - PSKeys.ENABLE_WORD_CONFIDENCE, False - ), - execution_source=execution_source, - metadata=metadata, - file_path=file_path, - ) - else: - structured_output[output[PSKeys.NAME]] = answer - - # If there is a trailing '\n' remove it - if isinstance(structured_output[output[PSKeys.NAME]], str): - structured_output[output[PSKeys.NAME]] = structured_output[ - output[PSKeys.NAME] - ].rstrip("\n") - - # Challenge condition - if enable_challenge: - challenge_plugin: dict[str, Any] = PluginManager().get_plugin( - PSKeys.CHALLENGE - ) - try: - if challenge_plugin: - publish_log( - log_events_id, - { - "tool_id": tool_id, - "prompt_key": prompt_name, - "doc_name": doc_name, - }, - LogLevel.INFO, - RunLevel.CHALLENGE, - "Challenging response", - ) - challenge_llm = LLM( - adapter_instance_id=tool_settings[PSKeys.CHALLENGE_LLM], - tool=util, - usage_kwargs={ - **usage_kwargs, - PSKeys.LLM_USAGE_REASON: PSKeys.CHALLENGE, - }, - capture_metrics=True, - ) - challenge = challenge_plugin["entrypoint_cls"]( - llm=llm, - challenge_llm=challenge_llm, - run_id=run_id, - context="\n".join(context), - tool_settings=tool_settings, - output=output, - structured_output=structured_output, - platform_key=platform_key, - metadata=metadata, - ) - # Will inline replace the structured output passed. - challenge.run() - else: - app.logger.info( - "No challenge plugin found to evaluate prompt: %s", - output[PSKeys.NAME], - ) - except challenge_plugin["exception_cls"] as e: - app.logger.error( - "Failed to challenge prompt %s: %s", - output[PSKeys.NAME], - str(e), - ) - publish_log( - log_events_id, - { - "tool_id": tool_id, - "prompt_key": prompt_name, - "doc_name": doc_name, - }, - LogLevel.ERROR, - RunLevel.CHALLENGE, - "Error while challenging response", - ) - - # - # Evaluate the prompt. - # - if ( - PSKeys.EVAL_SETTINGS in output - and output[PSKeys.EVAL_SETTINGS][PSKeys.EVAL_SETTINGS_EVALUATE] - ): - eval_plugin: dict[str, Any] = PluginManager().get_plugin("evaluation") - if eval_plugin: - publish_log( - log_events_id, - { - "tool_id": tool_id, - "prompt_key": prompt_name, - "doc_name": doc_name, - }, - LogLevel.INFO, - RunLevel.EVAL, - "Evaluating response", - ) - try: - evaluator = eval_plugin["entrypoint_cls"]( - "", - "".join(context), - "", - "", - output, - structured_output, - platform_key, - ) - # Will inline replace the structured output passed. - evaluator.run() - except eval_plugin["exception_cls"] as e: - app.logger.error( - f"Failed to evaluate prompt {output[PSKeys.NAME]}: {str(e)}" - ) - publish_log( - log_events_id, - { - "tool_id": tool_id, - "prompt_key": prompt_name, - "doc_name": doc_name, - }, - LogLevel.ERROR, - RunLevel.EVAL, - "Error while evaluation", - ) - else: - publish_log( - log_events_id, - { - "tool_id": tool_id, - "prompt_key": prompt_name, - "doc_name": doc_name, - }, - LogLevel.DEBUG, - RunLevel.EVAL, - "Evaluation completed", - ) - else: - app.logger.info( - f"No eval plugin found to evaluate prompt: {output[PSKeys.NAME]}" # noqa: E501 - ) - finally: - challenge_metrics = ( - {f"{challenge_llm.get_usage_reason()}_llm": challenge_llm.get_metrics()} - if enable_challenge and challenge_llm - else {} - ) - metrics.setdefault(prompt_name, {}).update( - { - "context_retrieval": context_retrieval_metrics.get(prompt_name, {}), - f"{llm.get_usage_reason()}_llm": llm.get_metrics(), - **challenge_metrics, - } - ) - # Only close vector_db if it was created (chunk_size > 0) - if vector_db: - vector_db.close() - publish_log( - log_events_id, - {"tool_id": tool_id, "doc_name": doc_name}, - LogLevel.INFO, - RunLevel.RUN, - "Sanitizing null values", - ) - for k, v in structured_output.items(): - if isinstance(v, str) and v.lower() == "na": - structured_output[k] = None - elif isinstance(v, list): - for i in range(len(v)): - if isinstance(v[i], str) and v[i].lower() == "na": - v[i] = None - elif isinstance(v[i], dict): - for k1, v1 in v[i].items(): - if isinstance(v1, str) and v1.lower() == "na": - v[i][k1] = None - elif isinstance(v, dict): - for k1, v1 in v.items(): - if isinstance(v1, str) and v1.lower() == "na": - v[k1] = None - - publish_log( - log_events_id, - {"tool_id": tool_id, "doc_name": doc_name}, - LogLevel.INFO, - RunLevel.RUN, - "Execution complete", - ) - metadata = UsageHelper.query_usage_metadata(token=platform_key, metadata=metadata) - response = { - PSKeys.METADATA: metadata, - PSKeys.OUTPUT: structured_output, - PSKeys.METRICS: metrics, - } - return response diff --git a/prompt-service/src/unstract/prompt_service/controllers/extraction.py b/prompt-service/src/unstract/prompt_service/controllers/extraction.py deleted file mode 100644 index 516894f429..0000000000 --- a/prompt-service/src/unstract/prompt_service/controllers/extraction.py +++ /dev/null @@ -1,53 +0,0 @@ -from typing import Any - -from flask import Blueprint, request - -from unstract.prompt_service.constants import IndexingConstants as IKeys -from unstract.prompt_service.constants import PromptServiceConstants as PSKeys -from unstract.prompt_service.helpers.auth import AuthHelper -from unstract.prompt_service.services.extraction import ExtractionService -from unstract.prompt_service.utils.request import validate_request_payload - -extraction_bp = Blueprint("extract", __name__) - -REQUIRED_FIELDS = [ - "x2text_instance_id", - "file_path", - "execution_source", - "run_id", -] - - -@AuthHelper.auth_required -@extraction_bp.route("/extract", methods=["POST"]) -def extract() -> Any: - platform_key = AuthHelper.get_token_from_auth_header(request) - payload: dict[Any, Any] = request.json - validate_request_payload(payload, REQUIRED_FIELDS) - - x2text_instance_id: str = payload.get(IKeys.X2TEXT_INSTANCE_ID, "") - file_path: str = payload.get(IKeys.FILE_PATH, "") - output_file_path: str | None = payload.get(IKeys.OUTPUT_FILE_PATH, "") - enable_highlight: bool = payload.get(IKeys.ENABLE_HIGHLIGHT, False) - usage_kwargs: dict[Any, Any] = payload.get(IKeys.USAGE_KWARGS, {}) - run_id: str = payload.get(PSKeys.RUN_ID, "") - execution_source = payload.get(IKeys.EXECUTION_SOURCE, None) - tags: str = payload.get(IKeys.TAGS, "") - tool_exec_metadata = payload.get(IKeys.TOOL_EXECUTION_METATADA, {}) - execution_run_data_folder = payload.get(IKeys.EXECUTION_DATA_DIR, "") - - extracted_text = ExtractionService.perform_extraction( - file_path=file_path, - x2text_instance_id=x2text_instance_id, - output_file_path=output_file_path, - enable_highlight=enable_highlight, - usage_kwargs=usage_kwargs, - run_id=run_id, - execution_source=execution_source, - platform_key=platform_key, - tags=tags, - tool_exec_metadata=tool_exec_metadata, - execution_run_data_folder=execution_run_data_folder, - ) - response = {IKeys.EXTRACTED_TEXT: extracted_text} - return response diff --git a/prompt-service/src/unstract/prompt_service/controllers/health.py b/prompt-service/src/unstract/prompt_service/controllers/health.py deleted file mode 100644 index 59cc428210..0000000000 --- a/prompt-service/src/unstract/prompt_service/controllers/health.py +++ /dev/null @@ -1,8 +0,0 @@ -from flask import Blueprint - -health_bp = Blueprint("health", __name__) - - -@health_bp.route("/health", methods=["GET"]) -def health_check() -> str: - return "OK" diff --git a/prompt-service/src/unstract/prompt_service/controllers/indexing.py b/prompt-service/src/unstract/prompt_service/controllers/indexing.py deleted file mode 100644 index f9416a9c92..0000000000 --- a/prompt-service/src/unstract/prompt_service/controllers/indexing.py +++ /dev/null @@ -1,102 +0,0 @@ -import logging -from typing import Any - -from flask import Blueprint, request - -from unstract.prompt_service.constants import IndexingConstants as IKeys -from unstract.prompt_service.constants import PromptServiceConstants as PSKeys -from unstract.prompt_service.dto import ( - ChunkingConfig, - FileInfo, - InstanceIdentifiers, - ProcessingOptions, -) -from unstract.prompt_service.helpers.auth import AuthHelper -from unstract.prompt_service.services.indexing import IndexingService -from unstract.prompt_service.utils.request import validate_request_payload - -indexing_bp = Blueprint("index", __name__) -logger = logging.getLogger(__name__) - -REQUIRED_FIELDS = [ - "tool_id", - "extracted_text", - "embedding_instance_id", - "vector_db_instance_id", - "x2text_instance_id", - "file_path", - "chunk_size", - "chunk_overlap", - "execution_source", - "run_id", -] - - -@AuthHelper.auth_required -@indexing_bp.route("/index", methods=["POST"]) -def index() -> Any: - """Endpoint for indexing documents into the vector database. - - This API accepts a JSON payload containing document details, processes the - document, and stores it in the vector database for retrieval. - - Raises: - BadRequest: If the request payload is missing or invalid. - - Returns: - str: doc_id - """ - platform_key = AuthHelper.get_token_from_auth_header(request) - payload: dict[Any, Any] = request.json - validate_request_payload(payload, REQUIRED_FIELDS) - - tool_id: str = payload.get(IKeys.TOOL_ID, "") - embedding_instance_id: str = payload.get(IKeys.EMBEDDING_INSTANCE_ID, "") - vector_db_instance_id: str = payload.get(IKeys.VECTOR_DB_INSTANCE_ID, "") - x2text_instance_id: str = payload.get(IKeys.X2TEXT_INSTANCE_ID, "") - file_path: str = payload.get(IKeys.FILE_PATH, "") - file_hash: str | None = payload.get(IKeys.FILE_HASH) - chunk_size: int = payload.get(IKeys.CHUNK_SIZE, 512) # Default chunk size - chunk_overlap: int = payload.get(IKeys.CHUNK_OVERLAP, 128) # Default chunk overlap - reindex: bool = payload.get(IKeys.REINDEX, False) - enable_highlight: bool = payload.get(IKeys.ENABLE_HIGHLIGHT, False) - enable_word_confidence: bool = payload.get(IKeys.ENABLE_WORD_CONFIDENCE, False) - usage_kwargs: dict[Any, Any] = payload.get(IKeys.USAGE_KWARGS, {}) - extracted_text: str = payload.get(IKeys.EXTRACTED_TEXT, "") - tags: list[str] = payload.get(IKeys.TAGS, None) - execution_source = payload.get(IKeys.EXECUTION_SOURCE, None) - run_id: str = payload.get(PSKeys.RUN_ID, "") - - instance_identifiers = InstanceIdentifiers( - embedding_instance_id=embedding_instance_id, - vector_db_instance_id=vector_db_instance_id, - x2text_instance_id=x2text_instance_id, - tool_id=tool_id, - tags=tags, - llm_instance_id=None, - ) - - file_info = FileInfo(file_path=file_path, file_hash=file_hash) - - chunking_config = ChunkingConfig(chunk_size=chunk_size, chunk_overlap=chunk_overlap) - - processing_options = ProcessingOptions( - reindex=reindex, - enable_highlight=enable_highlight, - enable_word_confidence=enable_word_confidence, - usage_kwargs=usage_kwargs, - ) - doc_id = IndexingService.index( - chunking_config=chunking_config, - execution_source=execution_source, - run_id=run_id, - file_info=file_info, - instance_identifiers=instance_identifiers, - platform_key=platform_key, - processing_options=processing_options, - extracted_text=extracted_text, - ) - response = { - IKeys.DOC_ID: doc_id, - } - return response diff --git a/prompt-service/src/unstract/prompt_service/core/index_v2.py b/prompt-service/src/unstract/prompt_service/core/index_v2.py deleted file mode 100644 index 5f1703b320..0000000000 --- a/prompt-service/src/unstract/prompt_service/core/index_v2.py +++ /dev/null @@ -1,240 +0,0 @@ -import json -import logging -from typing import Any - -import openai -from llama_index.core import Document -from llama_index.core.vector_stores import ( - FilterOperator, - MetadataFilter, - MetadataFilters, - VectorStoreQuery, - VectorStoreQueryResult, -) - -from unstract.prompt_service.dto import ( - ChunkingConfig, - FileInfo, - InstanceIdentifiers, - ProcessingOptions, -) -from unstract.sdk1.adapters.vectordb.no_op.src.no_op_custom_vectordb import ( - NoOpCustomVectorDB, -) -from unstract.sdk1.constants import LogLevel -from unstract.sdk1.embedding import Embedding -from unstract.sdk1.exceptions import SdkError, parse_litellm_err -from unstract.sdk1.file_storage.impl import FileStorage -from unstract.sdk1.file_storage.provider import FileStorageProvider -from unstract.sdk1.platform import PlatformHelper as ToolAdapter -from unstract.sdk1.tool.stream import StreamMixin -from unstract.sdk1.utils.common import Utils, capture_metrics -from unstract.sdk1.utils.tool import ToolUtils -from unstract.sdk1.vector_db import VectorDB - -logger = logging.getLogger(__name__) - - -class Index: - def __init__( - self, - tool: StreamMixin, - instance_identifiers: InstanceIdentifiers, - chunking_config: ChunkingConfig, - processing_options: ProcessingOptions, - run_id: str | None = None, - capture_metrics: bool = False, - ): - self.tool = tool - self._run_id = run_id - self._capture_metrics = capture_metrics - self.instance_identifiers = instance_identifiers - self.chunking_config = chunking_config - self.processing_options = processing_options - self._metrics = {} - - def generate_index_key( - self, - file_info: FileInfo, - fs: FileStorage = FileStorage(provider=FileStorageProvider.LOCAL), - ) -> str: - """Generates a unique index key based on the provided configuration, - file information, instance identifiers, and processing options. - - Args: - chunking_config : ChunkingConfig - file_info (FileInfo): Contains file-related - information such as path and hash. - instance_identifiers (InstanceIdentifiers): Identifiers for - embedding, vector DB, tool, etc. - processing_options (ProcessingOptions): Options related to reindexing, - highlighting, and processing text. - fs (FileStorage, optional): File storage for remote storage. - - Returns: - str: A unique index key used for indexing the document. - """ - if not file_info.file_path and not file_info.file_hash: - raise ValueError("One of `file_path` or `file_hash` need to be provided") - - if not file_info.file_hash: - file_hash = fs.get_hash_from_file(path=file_info.file_path) - - # Whole adapter config is used currently even though it contains some keys - # which might not be relevant to indexing. This is easier for now than - # marking certain keys of the adapter config as necessary. - vector_db_config = ToolAdapter.get_adapter_config( - self.tool, self.instance_identifiers.vector_db_instance_id - ) - embedding_config = ToolAdapter.get_adapter_config( - self.tool, self.instance_identifiers.embedding_instance_id - ) - x2text_config = ToolAdapter.get_adapter_config( - self.tool, self.instance_identifiers.x2text_instance_id - ) - Utils.strip_adapter_name(vector_db_config, embedding_config, x2text_config) - index_key = { - "file_hash": file_hash, - "vector_db_config": vector_db_config, - "embedding_config": embedding_config, - "x2text_config": x2text_config, - # Typed and hashed as strings since the final hash is persisted - # and this is required to be backward compatible - "chunk_size": str(self.chunking_config.chunk_size), - "chunk_overlap": str(self.chunking_config.chunk_overlap), - } - # JSON keys are sorted to ensure that the same key gets hashed even in - # case where the fields are reordered. - hashed_index_key = ToolUtils.hash_str(json.dumps(index_key, sort_keys=True)) - return hashed_index_key - - @capture_metrics - def is_document_indexed( - self, - doc_id: str, - embedding: Embedding, - vector_db: VectorDB, - ) -> bool: - """Checks if nodes are already present in the vector database for a - given doc_id. - - Returns: - str: The document ID. - """ - # Checking if document is already indexed against doc_id - doc_id_eq_filter = MetadataFilter.from_dict( - {"key": "doc_id", "operator": FilterOperator.EQ, "value": doc_id} - ) - filters = MetadataFilters(filters=[doc_id_eq_filter]) - q = VectorStoreQuery( - query_embedding=embedding.get_query_embedding(" "), - doc_ids=[doc_id], - filters=filters, - ) - - doc_id_found = False - try: - n: VectorStoreQueryResult = vector_db.query(query=q) - if len(n.nodes) > 0: - doc_id_found = True - self.tool.stream_log(f"Found {len(n.nodes)} nodes for {doc_id}") - else: - self.tool.stream_log(f"No nodes found for {doc_id}") - except Exception as e: - logger.warning( - f"Error querying {self.instance_identifiers.vector_db_instance_id}:" - f" {str(e)}, proceeding to index", - exc_info=True, - ) - - if doc_id_found and not self.processing_options.reindex: - self.tool.stream_log(f"File was indexed already under {doc_id}") - return doc_id_found - - return doc_id_found - - @capture_metrics - def perform_indexing( - self, - vector_db: VectorDB, - doc_id: str, - extracted_text: str, - doc_id_found: bool, - ): - if isinstance( - vector_db.get_vector_db( - adapter_instance_id=self.instance_identifiers.vector_db_instance_id, - embedding_dimension=1, - ), - (NoOpCustomVectorDB), - ): - return doc_id - - self.tool.stream_log("Indexing file...") - full_text = [ - { - "section": "full", - "text_contents": str(extracted_text), - } - ] - # Convert raw text to llama index usage Document - documents = self._prepare_documents(doc_id, full_text) - if self.processing_options.reindex and doc_id_found: - self.delete_nodes(vector_db, doc_id) - self._trigger_indexing(vector_db, documents) - return doc_id - - def _trigger_indexing(self, vector_db, documents): - self.tool.stream_log("Adding nodes to vector db...") - try: - vector_db.index_document( - documents, - chunk_size=self.chunking_config.chunk_size, - chunk_overlap=self.chunking_config.chunk_overlap, - show_progress=True, - ) - self.tool.stream_log("File has been indexed successfully") - # Handle embedding errors from litellm - except openai.OpenAIError as e: - e = parse_litellm_err(e) - raise e - except Exception as e: - self.tool.stream_log( - f"Error adding nodes to vector db: {e}", - level=LogLevel.ERROR, - ) - raise e - - def delete_nodes(self, vector_db: VectorDB, doc_id: str): - try: - vector_db.delete(ref_doc_id=doc_id) - self.tool.stream_log(f"Deleted nodes for {doc_id}") - except Exception as e: - self.tool.stream_log( - f"Error deleting nodes for {doc_id}: {e}", - level=LogLevel.ERROR, - ) - raise SdkError(f"Error deleting nodes for {doc_id}: {e}") from e - - def _prepare_documents(self, doc_id: str, full_text: Any) -> list: - documents = [] - try: - for item in full_text: - text = item["text_contents"] - document = Document( - text=text, - doc_id=doc_id, - metadata={"section": item["section"]}, - ) - document.id_ = doc_id - documents.append(document) - self.tool.stream_log(f"Number of documents: {len(documents)}") - return documents - except Exception as e: - self.tool.stream_log( - f"Error while processing documents {doc_id}: {e}", - level=LogLevel.ERROR, - ) - raise SdkError( - f"Error while processing documents for indexing {doc_id}: {e}" - ) from e diff --git a/prompt-service/src/unstract/prompt_service/core/retrievers/automerging.py b/prompt-service/src/unstract/prompt_service/core/retrievers/automerging.py deleted file mode 100644 index 3df2a3776e..0000000000 --- a/prompt-service/src/unstract/prompt_service/core/retrievers/automerging.py +++ /dev/null @@ -1,85 +0,0 @@ -import logging - -from llama_index.core import VectorStoreIndex -from llama_index.core.retrievers import AutoMergingRetriever as LlamaAutoMergingRetriever -from llama_index.core.vector_stores import ExactMatchFilter, MetadataFilters - -from unstract.prompt_service.core.retrievers.base_retriever import BaseRetriever -from unstract.prompt_service.exceptions import RetrievalError - -logger = logging.getLogger(__name__) - - -class AutomergingRetriever(BaseRetriever): - """Automerging retrieval using LlamaIndex's native AutoMergingRetriever. - - This retriever merges smaller chunks into larger ones when the smaller chunks - don't contain enough information, providing better context for answers. - """ - - def retrieve(self) -> set[str]: - """Retrieve text chunks using LlamaIndex's native AutoMergingRetriever. - - Returns: - set[str]: A set of text chunks retrieved from the database. - """ - try: - logger.info( - f"Retrieving chunks for {self.doc_id} using LlamaIndex AutoMergingRetriever." - ) - - # Get the vector store index - vector_store_index: VectorStoreIndex = self.vector_db.get_vector_store_index() - - # Create base vector retriever with metadata filters - base_retriever = vector_store_index.as_retriever( - similarity_top_k=self.top_k, - filters=MetadataFilters( - filters=[ - ExactMatchFilter(key="doc_id", value=self.doc_id), - ], - ), - ) - - # Try to use native AutoMergingRetriever - try: - # Create AutoMergingRetriever with the base retriever - auto_merging_retriever = LlamaAutoMergingRetriever( - base_retriever, - storage_context=self.vector_db.get_storage_context() - if hasattr(self.vector_db, "get_storage_context") - else None, - verbose=False, - ) - - # Retrieve nodes using auto-merging - nodes = auto_merging_retriever.retrieve(self.prompt) - - except Exception as e: - logger.error(f"AutoMergingRetriever failed : {e}") - raise RetrievalError(f"AutoMergingRetriever failed: {str(e)}") from e - - # Extract unique text chunks - chunks: set[str] = set() - for node in nodes: - if node.score > 0: - chunks.add(node.get_content()) - else: - logger.info( - f"Node score is less than 0. " - f"Ignored: {node.node_id} with score {node.score}" - ) - - logger.info( - f"Successfully retrieved {len(chunks)} chunks using AutoMergingRetriever." - ) - return chunks - - except (ValueError, AttributeError, KeyError, ImportError) as e: - logger.error(f"Error during auto-merging retrieval for {self.doc_id}: {e}") - raise RetrievalError(str(e)) from e - except Exception as e: - logger.error( - f"Unexpected error during auto-merging retrieval for {self.doc_id}: {e}" - ) - raise RetrievalError(f"Unexpected error: {str(e)}") from e diff --git a/prompt-service/src/unstract/prompt_service/core/retrievers/base_retriever.py b/prompt-service/src/unstract/prompt_service/core/retrievers/base_retriever.py deleted file mode 100644 index 288223c00a..0000000000 --- a/prompt-service/src/unstract/prompt_service/core/retrievers/base_retriever.py +++ /dev/null @@ -1,61 +0,0 @@ -from unstract.prompt_service.core.retrievers.retriever_llm import RetrieverLLM -from unstract.sdk1.llm import LLM -from unstract.sdk1.vector_db import VectorDB - - -class BaseRetriever: - def __init__( - self, - vector_db: VectorDB, - prompt: str, - doc_id: str, - top_k: int, - llm: LLM | None = None, - ): - """Initialize the Retrieval class. - - Args: - vector_db (VectorDB): The vector database instance. - prompt (str): The query prompt. - doc_id (str): Document identifier for query context. - top_k (int): Number of top results to retrieve. - """ - self.vector_db = vector_db - self.prompt = prompt - self.doc_id = doc_id - self.top_k = top_k - self._llm: LLM | None = llm - self._retriever_llm: RetrieverLLM | None = None - - @property - def llm(self) -> RetrieverLLM | None: - """Return a llama-index compatible LLM, lazily created on first access. - - Avoids the cost of RetrieverLLM construction (adapter init, - CallbackManager setup) for retrievers that never use the LLM - (Simple, Automerging, Recursive). - """ - if self._llm is None: - return None - if self._retriever_llm is None: - self._retriever_llm = RetrieverLLM(llm=self._llm) - return self._retriever_llm - - def require_llm(self) -> RetrieverLLM: - """Return the llama-index LLM or raise if not configured. - - Call this in retrievers that need an LLM (KeywordTable, Fusion, - Subquestion) to fail early with a clear message instead of - letting llama-index silently fall back to its default OpenAI LLM. - """ - llm = self.llm - if llm is None: - raise ValueError( - f"{type(self).__name__} requires an LLM. " - "Pass llm= when constructing the retriever." - ) - return llm - - @staticmethod - def retrieve() -> set[str]: - return set() diff --git a/prompt-service/src/unstract/prompt_service/core/retrievers/fusion.py b/prompt-service/src/unstract/prompt_service/core/retrievers/fusion.py deleted file mode 100644 index 5fd7870f01..0000000000 --- a/prompt-service/src/unstract/prompt_service/core/retrievers/fusion.py +++ /dev/null @@ -1,95 +0,0 @@ -import logging - -from llama_index.core import VectorStoreIndex -from llama_index.core.retrievers import QueryFusionRetriever -from llama_index.core.vector_stores import ExactMatchFilter, MetadataFilters - -from unstract.prompt_service.core.retrievers.base_retriever import BaseRetriever -from unstract.prompt_service.exceptions import RetrievalError - -logger = logging.getLogger(__name__) - - -class FusionRetriever(BaseRetriever): - """Fusion retrieval class using LlamaIndex's native QueryFusionRetriever. - - This technique generates multiple query variations and combines results - using reciprocal rank fusion for improved relevance. - """ - - def retrieve(self) -> set[str]: - """Retrieve text chunks using LlamaIndex's QueryFusionRetriever. - - Returns: - set[str]: A set of text chunks retrieved from the database. - """ - try: - llm = self.require_llm() - logger.info( - f"Retrieving chunks for {self.doc_id} using LlamaIndex QueryFusionRetriever." - ) - - # Get the vector store index - vector_store_index: VectorStoreIndex = self.vector_db.get_vector_store_index() - - # Create multiple retrievers with different parameters for true fusion - filters = MetadataFilters( - filters=[ - ExactMatchFilter(key="doc_id", value=self.doc_id), - ], - ) - - # Retriever 1: Standard similarity search - retriever_1 = vector_store_index.as_retriever( - similarity_top_k=self.top_k, - filters=filters, - ) - - # Retriever 2: Broader search with more candidates - retriever_2 = vector_store_index.as_retriever( - similarity_top_k=self.top_k * 2, - filters=filters, - ) - - # Retriever 3: Focused search with fewer candidates - retriever_3 = vector_store_index.as_retriever( - similarity_top_k=max(1, self.top_k // 2), - filters=filters, - ) - - # Create LlamaIndex QueryFusionRetriever with multiple retrievers - fusion_retriever = QueryFusionRetriever( - [retriever_1, retriever_2, retriever_3], # Multiple retrievers for fusion - similarity_top_k=self.top_k, - num_queries=4, # Generate multiple query variations - mode="simple", # Use simple fusion mode (reciprocal rank fusion) - use_async=False, - verbose=True, - llm=llm, - ) - - # Retrieve nodes using fusion technique - nodes = fusion_retriever.retrieve(self.prompt) - - # Extract unique text chunks - chunks: set[str] = set() - for node in nodes: - if node.score > 0: - chunks.add(node.get_content()) - else: - logger.info( - f"Node score is less than 0. " - f"Ignored: {node.node_id} with score {node.score}" - ) - - logger.info(f"Successfully retrieved {len(chunks)} chunks using fusion.") - return chunks - - except (ValueError, AttributeError, KeyError, ImportError) as e: - logger.error(f"Error during fusion retrieval for {self.doc_id}: {e}") - raise RetrievalError(str(e)) from e - except Exception as e: - logger.error( - f"Unexpected error during fusion retrieval for {self.doc_id}: {e}" - ) - raise RetrievalError(f"Unexpected error: {str(e)}") from e diff --git a/prompt-service/src/unstract/prompt_service/core/retrievers/keyword_table.py b/prompt-service/src/unstract/prompt_service/core/retrievers/keyword_table.py deleted file mode 100644 index d17e507b50..0000000000 --- a/prompt-service/src/unstract/prompt_service/core/retrievers/keyword_table.py +++ /dev/null @@ -1,80 +0,0 @@ -import logging - -from llama_index.core import VectorStoreIndex -from llama_index.core.indices.keyword_table import KeywordTableIndex -from llama_index.core.vector_stores import ExactMatchFilter, MetadataFilters - -from unstract.prompt_service.core.retrievers.base_retriever import BaseRetriever -from unstract.prompt_service.exceptions import RetrievalError - -logger = logging.getLogger(__name__) - - -class KeywordTableRetriever(BaseRetriever): - """Keyword table retrieval using LlamaIndex's native KeywordTableIndex.""" - - def retrieve(self) -> set[str]: - """Retrieve text chunks using LlamaIndex's native KeywordTableIndex. - - Returns: - set[str]: A set of text chunks retrieved from the database. - """ - try: - llm = self.require_llm() - logger.info( - f"Retrieving chunks for {self.doc_id} using LlamaIndex KeywordTableIndex." - ) - - # Get documents from vector index for keyword indexing - vector_store_index: VectorStoreIndex = self.vector_db.get_vector_store_index() - - # Get all nodes for the document - all_retriever = vector_store_index.as_retriever( - similarity_top_k=1000, # Get all nodes - filters=MetadataFilters( - filters=[ - ExactMatchFilter(key="doc_id", value=self.doc_id), - ], - ), - ) - - # Retrieve all nodes to build keyword index - all_nodes = all_retriever.retrieve(" ") - - if not all_nodes: - logger.warning(f"No nodes found for doc_id: {self.doc_id}") - return set() - - # Create KeywordTableIndex from nodes using our provided LLM - keyword_index = KeywordTableIndex( - nodes=[node.node for node in all_nodes], - show_progress=True, - llm=llm, - ) - - # Create retriever from keyword index - keyword_retriever = keyword_index.as_retriever( - similarity_top_k=self.top_k, - ) - - # Retrieve nodes using keyword matching - nodes = keyword_retriever.retrieve(self.prompt) - - # Extract unique text chunks - chunks: set[str] = set() - for node in nodes: - chunks.add(node.get_content()) - - logger.info( - f"Successfully retrieved {len(chunks)} chunks using KeywordTableIndex." - ) - return chunks - - except (ValueError, AttributeError, KeyError, ImportError) as e: - logger.error(f"Error during keyword retrieval for {self.doc_id}: {e}") - raise RetrievalError(str(e)) from e - except Exception as e: - logger.error( - f"Unexpected error during keyword retrieval for {self.doc_id}: {e}" - ) - raise RetrievalError(f"Unexpected error: {str(e)}") from e diff --git a/prompt-service/src/unstract/prompt_service/core/retrievers/recursive.py b/prompt-service/src/unstract/prompt_service/core/retrievers/recursive.py deleted file mode 100644 index dcec3d6176..0000000000 --- a/prompt-service/src/unstract/prompt_service/core/retrievers/recursive.py +++ /dev/null @@ -1,77 +0,0 @@ -import logging - -from llama_index.core import VectorStoreIndex -from llama_index.core.retrievers import RecursiveRetriever -from llama_index.core.vector_stores import ExactMatchFilter, MetadataFilters - -from unstract.prompt_service.core.retrievers.base_retriever import BaseRetriever -from unstract.prompt_service.exceptions import RetrievalError - -logger = logging.getLogger(__name__) - - -class RecursiveRetrieval(BaseRetriever): - """Recursive retrieval using LlamaIndex's native RecursiveRetriever. - - This retriever performs recursive retrieval by breaking down queries - and refining results through multiple retrieval steps. - """ - - def retrieve(self) -> set[str]: - """Retrieve text chunks using LlamaIndex's native RecursiveRetriever. - - Returns: - set[str]: A set of text chunks retrieved from the database. - """ - try: - logger.info( - f"Retrieving chunks for {self.doc_id} using LlamaIndex RecursiveRetriever." - ) - - # Get the vector store index - vector_store_index: VectorStoreIndex = self.vector_db.get_vector_store_index() - - # Create base retriever with metadata filters - base_retriever = vector_store_index.as_retriever( - similarity_top_k=self.top_k, - filters=MetadataFilters( - filters=[ - ExactMatchFilter(key="doc_id", value=self.doc_id), - ], - ), - ) - - # Create RecursiveRetriever - recursive_retriever = RecursiveRetriever( - "vector", # root retriever key - retriever_dict={"vector": base_retriever}, - verbose=True, - ) - - # Retrieve nodes using RecursiveRetriever - nodes = recursive_retriever.retrieve(self.prompt) - - # Extract unique text chunks - chunks: set[str] = set() - for node in nodes: - if node.score > 0: - chunks.add(node.get_content()) - else: - logger.info( - f"Node score is less than 0. " - f"Ignored: {node.node_id} with score {node.score}" - ) - - logger.info( - f"Successfully retrieved {len(chunks)} chunks using RecursiveRetriever." - ) - return chunks - - except (ValueError, AttributeError, KeyError, ImportError) as e: - logger.error(f"Error during recursive retrieval for {self.doc_id}: {e}") - raise RetrievalError(str(e)) from e - except Exception as e: - logger.error( - f"Unexpected error during recursive retrieval for {self.doc_id}: {e}" - ) - raise RetrievalError(f"Unexpected error: {str(e)}") from e diff --git a/prompt-service/src/unstract/prompt_service/core/retrievers/retriever_llm.py b/prompt-service/src/unstract/prompt_service/core/retrievers/retriever_llm.py deleted file mode 100644 index c2038f9181..0000000000 --- a/prompt-service/src/unstract/prompt_service/core/retrievers/retriever_llm.py +++ /dev/null @@ -1,126 +0,0 @@ -from collections.abc import Sequence -from typing import Any - -from llama_index.core.base.llms.types import ( - ChatMessage, - ChatResponse, - ChatResponseAsyncGen, - ChatResponseGen, - CompletionResponse, - CompletionResponseAsyncGen, - CompletionResponseGen, - LLMMetadata, - MessageRole, -) -from llama_index.core.llms.llm import LLM as LlamaIndexBaseLLM # noqa: N811 -from pydantic import PrivateAttr - -from unstract.sdk1.llm import LLM, LLMCompat - - -class RetrieverLLM(LlamaIndexBaseLLM): - """Bridges SDK1's LLMCompat with llama-index's LLM for retriever use. - - Llama-index's ``resolve_llm()`` asserts ``isinstance(llm, LLM)`` - where ``LLM`` is ``llama_index.core.llms.llm.LLM``. Since SDK1's - ``LLMCompat`` is a plain class without llama-index inheritance, - it fails this check. - - ``RetrieverLLM`` inherits from llama-index's ``LLM`` base class - (passing the isinstance check) and delegates all LLM calls to an - internal ``LLMCompat`` instance. - """ - - _compat: LLMCompat = PrivateAttr() - - def __init__(self, llm: LLM, **kwargs: Any) -> None: # noqa: ANN401 - """Initialize with an SDK1 LLM instance.""" - super().__init__(**kwargs) - self._compat = LLMCompat.from_llm(llm) - - @property - def metadata(self) -> LLMMetadata: - return LLMMetadata( - is_chat_model=True, - model_name=self._compat.get_model_name(), - ) - - # ── Sync ───────────────────────────────────────────────────────────────── - - def chat( - self, - messages: Sequence[ChatMessage], - **kwargs: Any, # noqa: ANN401 - ) -> ChatResponse: - result = self._compat.chat(messages, **kwargs) - return ChatResponse( - message=ChatMessage( - role=MessageRole.ASSISTANT, - content=result.message.content, - ), - raw=result.raw, - ) - - def complete( - self, - prompt: str, - formatted: bool = False, - **kwargs: Any, # noqa: ANN401 - ) -> CompletionResponse: - result = self._compat.complete(prompt, formatted=formatted, **kwargs) - return CompletionResponse(text=result.text, raw=result.raw) - - def stream_chat( - self, - messages: Sequence[ChatMessage], - **kwargs: Any, # noqa: ANN401 - ) -> ChatResponseGen: - raise NotImplementedError("stream_chat is not supported.") - - def stream_complete( - self, - prompt: str, - formatted: bool = False, - **kwargs: Any, # noqa: ANN401 - ) -> CompletionResponseGen: - raise NotImplementedError("stream_complete is not supported.") - - # ── Async ──────────────────────────────────────────────────────────────── - - async def achat( - self, - messages: Sequence[ChatMessage], - **kwargs: Any, # noqa: ANN401 - ) -> ChatResponse: - result = await self._compat.achat(messages, **kwargs) - return ChatResponse( - message=ChatMessage( - role=MessageRole.ASSISTANT, - content=result.message.content, - ), - raw=result.raw, - ) - - async def acomplete( - self, - prompt: str, - formatted: bool = False, - **kwargs: Any, # noqa: ANN401 - ) -> CompletionResponse: - result = await self._compat.acomplete(prompt, formatted=formatted, **kwargs) - return CompletionResponse(text=result.text, raw=result.raw) - - async def astream_chat( - self, - messages: Sequence[ChatMessage], - **kwargs: Any, # noqa: ANN401 - ) -> ChatResponseAsyncGen: - raise NotImplementedError("astream_chat is not supported.") - - async def astream_complete( - self, - prompt: str, - formatted: bool = False, - **kwargs: Any, # noqa: ANN401 - ) -> CompletionResponseAsyncGen: - raise NotImplementedError("astream_complete is not supported.") diff --git a/prompt-service/src/unstract/prompt_service/core/retrievers/router.py b/prompt-service/src/unstract/prompt_service/core/retrievers/router.py deleted file mode 100644 index b5bc4efe1d..0000000000 --- a/prompt-service/src/unstract/prompt_service/core/retrievers/router.py +++ /dev/null @@ -1,157 +0,0 @@ -import logging - -from llama_index.core import VectorStoreIndex -from llama_index.core.query_engine import RouterQueryEngine -from llama_index.core.selectors import LLMSingleSelector -from llama_index.core.tools import QueryEngineTool, ToolMetadata -from llama_index.core.vector_stores import ExactMatchFilter, MetadataFilters - -from unstract.prompt_service.core.retrievers.base_retriever import BaseRetriever -from unstract.prompt_service.exceptions import RetrievalError - -logger = logging.getLogger(__name__) - - -class RouterRetriever(BaseRetriever): - """Router retrieval class using LlamaIndex's native RouterQueryEngine. - - This technique intelligently routes queries to different retrieval strategies - based on query analysis. - """ - - def _create_metadata_filters(self): - """Create metadata filters for doc_id.""" - return MetadataFilters( - filters=[ - ExactMatchFilter(key="doc_id", value=self.doc_id), - ], - ) - - def _create_base_query_engine(self, vector_store_index, filters): - """Create the base vector query engine.""" - return vector_store_index.as_query_engine( - similarity_top_k=self.top_k, - filters=filters, - llm=self.llm, - ) - - def _add_keyword_search_tool(self, query_engine_tools, vector_store_index, filters): - """Add keyword search tool to query engine tools list.""" - try: - keyword_query_engine = vector_store_index.as_query_engine( - similarity_top_k=self.top_k * 2, - filters=filters, - llm=self.llm, - ) - query_engine_tools.append( - QueryEngineTool( - query_engine=keyword_query_engine, - metadata=ToolMetadata( - name="keyword_search", - description=( - "Best for finding specific terms, names, numbers, dates, " - "or exact phrases. Use when looking for precise matches." - ), - ), - ) - ) - except Exception as e: - logger.debug(f"Could not create keyword search engine: {e}") - - def _add_broad_search_tool(self, query_engine_tools, vector_store_index, filters): - """Add broad search tool to query engine tools list.""" - try: - broad_query_engine = vector_store_index.as_query_engine( - similarity_top_k=self.top_k * 3, - filters=filters, - llm=self.llm, - ) - query_engine_tools.append( - QueryEngineTool( - query_engine=broad_query_engine, - metadata=ToolMetadata( - name="broad_search", - description=( - "Useful for general questions, exploratory queries, " - "or when you need comprehensive information on a topic." - ), - ), - ) - ) - except Exception as e: - logger.debug(f"Could not create broad search engine: {e}") - - def _extract_chunks_from_response(self, response): - """Extract chunks from router query response.""" - chunks: set[str] = set() - if hasattr(response, "source_nodes"): - for node in response.source_nodes: - if node.score > 0: - chunks.add(node.get_content()) - else: - logger.info( - f"Node score is less than 0. " - f"Ignored: {node.node_id} with score {node.score}" - ) - return chunks - - def retrieve(self) -> set[str]: - """Retrieve text chunks using LlamaIndex's RouterQueryEngine. - - Returns: - set[str]: A set of text chunks retrieved from the database. - """ - try: - logger.info( - f"Retrieving chunks for {self.doc_id} using LlamaIndex RouterQueryEngine." - ) - - vector_store_index: VectorStoreIndex = self.vector_db.get_vector_store_index() - filters = self._create_metadata_filters() - vector_query_engine = self._create_base_query_engine( - vector_store_index, filters - ) - - if not self.llm: - return set() - - # Create base query engine tools - query_engine_tools = [ - QueryEngineTool( - query_engine=vector_query_engine, - metadata=ToolMetadata( - name="vector_search", - description=( - "Useful for semantic similarity search, conceptual questions, " - "and finding information based on meaning and context." - ), - ), - ), - ] - - # Add additional search strategies - self._add_keyword_search_tool(query_engine_tools, vector_store_index, filters) - self._add_broad_search_tool(query_engine_tools, vector_store_index, filters) - - # Create and execute router query - router_query_engine = RouterQueryEngine.from_defaults( - selector=LLMSingleSelector.from_defaults(llm=self.llm), - query_engine_tools=query_engine_tools, - verbose=True, - llm=self.llm, - ) - - response = router_query_engine.query(self.prompt) - chunks = self._extract_chunks_from_response(response) - - logger.info(f"Successfully retrieved {len(chunks)} chunks using router.") - return chunks - - except (ValueError, AttributeError, KeyError, ImportError) as e: - logger.error(f"Error during router retrieval for {self.doc_id}: {e}") - raise RetrievalError(str(e)) from e - except Exception as e: - logger.error( - f"Unexpected error during router retrieval for {self.doc_id}: {e}" - ) - raise RetrievalError(f"Unexpected error: {str(e)}") from e diff --git a/prompt-service/src/unstract/prompt_service/core/retrievers/simple.py b/prompt-service/src/unstract/prompt_service/core/retrievers/simple.py deleted file mode 100644 index 15f25db1bb..0000000000 --- a/prompt-service/src/unstract/prompt_service/core/retrievers/simple.py +++ /dev/null @@ -1,51 +0,0 @@ -import time - -from flask import current_app as app -from llama_index.core import VectorStoreIndex -from llama_index.core.vector_stores import ExactMatchFilter, MetadataFilters - -from unstract.prompt_service.core.retrievers.base_retriever import BaseRetriever - - -class SimpleRetriever(BaseRetriever): - def retrieve(self) -> set[str]: - context = self._simple_retrieval() - if not context: - # UN-1288 For Pinecone, we are seeing an inconsistent case where - # query with doc_id fails even though indexing just happened. - # This causes the following retrieve to return no text. - # To rule out any lag on the Pinecone vector DB write, - # the following sleep is added - # Note: This will not fix the issue. Since this issue is inconsistent - # and not reproducible easily, this is just a safety net. - app.logger.info( - f"[doc_id: {self.doc_id}] Could not retrieve context, " - "retrying after 2 secs to handle issues due to lag" - ) - time.sleep(2) - context = self._simple_retrieval() - return context - - def _simple_retrieval(self): - vector_query_engine: VectorStoreIndex = self.vector_db.get_vector_store_index() - retriever = vector_query_engine.as_retriever( - similarity_top_k=self.top_k, - filters=MetadataFilters( - filters=[ - ExactMatchFilter(key="doc_id", value=self.doc_id), - ], - ), - ) - nodes = retriever.retrieve(self.prompt) - context: set[str] = set() - for node in nodes: - # May have to fine-tune this value for node score or keep it - # configurable at the adapter level - if node.score > 0: - context.add(node.get_content()) - else: - app.logger.info( - "Node score is less than 0. " - f"Ignored: {node.node_id} with score {node.score}" - ) - return context diff --git a/prompt-service/src/unstract/prompt_service/core/retrievers/subquestion.py b/prompt-service/src/unstract/prompt_service/core/retrievers/subquestion.py deleted file mode 100644 index 6930bc131b..0000000000 --- a/prompt-service/src/unstract/prompt_service/core/retrievers/subquestion.py +++ /dev/null @@ -1,65 +0,0 @@ -import logging - -from llama_index.core.query_engine import SubQuestionQueryEngine -from llama_index.core.question_gen.llm_generators import LLMQuestionGenerator -from llama_index.core.schema import QueryBundle -from llama_index.core.tools import QueryEngineTool, ToolMetadata - -from unstract.prompt_service.core.retrievers.base_retriever import BaseRetriever -from unstract.prompt_service.exceptions import RetrievalError - -logger = logging.getLogger(__name__) - - -class SubquestionRetriever(BaseRetriever): - """SubquestionRetrieval class for querying VectorDB using LlamaIndex's - SubQuestionQueryEngine. - """ - - def retrieve(self) -> set[str]: - """Retrieve text chunks from the VectorDB based on the provided prompt. - - Returns: - set[str]: A set of text chunks retrieved from the database. - """ - try: - llm = self.require_llm() - logger.info("Initialising vector query engine...") - vector_query_engine = self.vector_db.get_vector_store_index().as_query_engine( - llm=llm, similarity_top_k=self.top_k - ) - logger.info( - f"Retrieving chunks for {self.doc_id} using SubQuestionQueryEngine." - ) - query_engine_tools = [ - QueryEngineTool( - query_engine=vector_query_engine, - metadata=ToolMetadata( - name=self.doc_id, description=f"Nodes for {self.doc_id}" - ), - ), - ] - query_bundle = QueryBundle(query_str=self.prompt) - - question_gen = LLMQuestionGenerator.from_defaults( - llm=llm, - ) - query_engine = SubQuestionQueryEngine.from_defaults( - query_engine_tools=query_engine_tools, - question_gen=question_gen, - use_async=True, - llm=llm, - ) - - response = query_engine.query(str_or_query_bundle=query_bundle) - - chunks: set[str] = {node.text for node in response.source_nodes} - logger.info(f"Successfully retrieved {len(chunks)} chunks.") - return chunks - - except (ValueError, AttributeError, KeyError, ImportError) as e: - logger.error(f"Error during retrieving chunks {self.doc_id}: {e}") - raise RetrievalError(str(e)) from e - except Exception as e: - logger.error(f"Unexpected error during retrieving chunks {self.doc_id}: {e}") - raise RetrievalError(f"Unexpected error: {str(e)}") from e diff --git a/prompt-service/src/unstract/prompt_service/dto.py b/prompt-service/src/unstract/prompt_service/dto.py deleted file mode 100644 index 8c9e4f3d3c..0000000000 --- a/prompt-service/src/unstract/prompt_service/dto.py +++ /dev/null @@ -1,39 +0,0 @@ -from dataclasses import dataclass, field -from typing import Any - - -@dataclass -class InstanceIdentifiers: - embedding_instance_id: str - vector_db_instance_id: str - x2text_instance_id: str - llm_instance_id: str - tool_id: str - tags: list[str] | None = None - - -@dataclass -class FileInfo: - file_path: str - file_hash: str - - -@dataclass -class ChunkingConfig: - chunk_size: int - chunk_overlap: int - - def __post_init__(self) -> None: - if self.chunk_size == 0: - raise ValueError( - "Indexing cannot be done for zero chunks." - "Please provide a valid chunk_size." - ) - - -@dataclass -class ProcessingOptions: - reindex: bool = False - enable_highlight: bool = False - enable_word_confidence: bool = False - usage_kwargs: dict[Any, Any] = field(default_factory=dict) diff --git a/prompt-service/src/unstract/prompt_service/exceptions.py b/prompt-service/src/unstract/prompt_service/exceptions.py deleted file mode 100644 index 98634ed5fd..0000000000 --- a/prompt-service/src/unstract/prompt_service/exceptions.py +++ /dev/null @@ -1,56 +0,0 @@ -from unstract.core.flask.exceptions import APIError - - -class BadRequest(APIError): - code = 400 - message = "Bad Request / No payload" - - -class RateLimitError(APIError): - code = 429 - message = "Running into rate limit errors, please try again later" - - -class MissingFieldError(APIError): - """Custom error for missing fields.""" - - def __init__(self, missing_fields: list[str]): - message = f"Missing required fields: {', '.join(missing_fields)}" - super().__init__(message=message) - - -class RetrievalError(APIError): - """Custom exception raised for errors during retrieval from VectorDB.""" - - DEFAULT_MESSAGE = ( - "Error while retrieving data from the VectorDB. " - "Please contact the admin for further assistance." - ) - - -class ExtractionError(APIError): - DEFAULT_MESSAGE = "Error while extracting from a document" - - -class UnprocessableEntity(APIError): - code = 422 - message = "Unprocessable Entity" - - -class CustomDataError(APIError): - """Custom exception raised for errors with custom_data variables.""" - - code = 400 - - def __init__(self, variable: str, reason: str, is_ide: bool = True): - if is_ide: - help_text = "Please define this key in Prompt Studio Settings > Custom Data." - else: - help_text = ( - "Please include this key in the 'custom_data' field of your API request." - ) - variable_display = "{{custom_data." + variable + "}}" - message = ( - f"Custom data error for variable '{variable_display}': {reason} {help_text}" - ) - super().__init__(message=message) diff --git a/prompt-service/src/unstract/prompt_service/extensions.py b/prompt-service/src/unstract/prompt_service/extensions.py deleted file mode 100644 index 1626591dc0..0000000000 --- a/prompt-service/src/unstract/prompt_service/extensions.py +++ /dev/null @@ -1,36 +0,0 @@ -from collections.abc import Generator -from contextlib import contextmanager -from os import environ as env -from typing import Any - -from playhouse.postgres_ext import PostgresqlExtDatabase - -from unstract.prompt_service.utils.env_loader import get_env_or_die - -# Load required environment variables -db_host = get_env_or_die("PG_BE_HOST") -db_port = get_env_or_die("PG_BE_PORT") -db_user = get_env_or_die("PG_BE_USERNAME") -db_pass = get_env_or_die("PG_BE_PASSWORD") -db_name = get_env_or_die("PG_BE_DATABASE") -application_name = env.get("APPLICATION_NAME", "unstract-prompt-service") - -# Initialize and connect to the database -db = PostgresqlExtDatabase( - database=db_name, - user=db_user, - host=db_host, - password=db_pass, - port=db_port, - options=f"-c application_name={application_name}", -) - - -@contextmanager -def db_context() -> Generator[PostgresqlExtDatabase, Any, None]: - try: - db.connect() - yield db - finally: - if not db.is_closed(): - db.close() diff --git a/prompt-service/src/unstract/prompt_service/helpers/__init__.py b/prompt-service/src/unstract/prompt_service/helpers/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/prompt-service/src/unstract/prompt_service/helpers/auth.py b/prompt-service/src/unstract/prompt_service/helpers/auth.py deleted file mode 100644 index cd9fd58128..0000000000 --- a/prompt-service/src/unstract/prompt_service/helpers/auth.py +++ /dev/null @@ -1,85 +0,0 @@ -from typing import Any - -from flask import Request, request -from flask import current_app as app - -from unstract.prompt_service.constants import DBTableV2 -from unstract.prompt_service.extensions import db, db_context -from unstract.prompt_service.utils.db_utils import DBUtils -from unstract.prompt_service.utils.env_loader import get_env_or_die - -DB_SCHEMA = get_env_or_die("DB_SCHEMA", "unstract") - - -class AuthHelper: - @staticmethod - def validate_bearer_token(token: str | None) -> bool: - try: - if token is None: - app.logger.error("Authentication failed. Empty bearer token") - return False - - platform_key_table = f'"{DB_SCHEMA}".{DBTableV2.PLATFORM_KEY}' - with db_context(): - query = f"SELECT * FROM {platform_key_table} WHERE key = %s" - cursor = db.execute_sql(query, (token,)) - result_row = cursor.fetchone() - cursor.close() - if not result_row or len(result_row) == 0: - app.logger.error(f"Authentication failed. bearer token not found {token}") - return False - platform_key = str(result_row[1]) - is_active = bool(result_row[2]) - if not is_active: - app.logger.error( - f"Token is not active. Activate \ - before using it. token {token}" - ) - return False - if platform_key != token: - app.logger.error(f"Authentication failed. Invalid bearer token: {token}") - return False - - except Exception as e: - app.logger.error( - f"Error while validating bearer token: {e}", - stack_info=True, - exc_info=True, - ) - return False - return True - - @staticmethod - def get_token_from_auth_header(request: Request) -> str | None: - try: - bearer_token = request.headers.get("Authorization") - if not bearer_token: - return None - token: str = bearer_token.strip().replace("Bearer ", "") - return token - except Exception as e: - app.logger.info(f"Exception while getting token {e}") - return None - - @staticmethod - def get_account_from_bearer_token(token: str | None) -> str: - platform_key_table = DBTableV2.PLATFORM_KEY - organization_table = DBTableV2.ORGANIZATION - - query = f"SELECT organization_id FROM {platform_key_table} WHERE key=%s" - organization = DBUtils.execute_query(query, (token,)) - query_org = f"SELECT schema_name FROM {organization_table} WHERE id=%s" - schema_name: str = DBUtils.execute_query(query_org, (organization,)) - return schema_name - - @staticmethod - def auth_required(func: Any) -> Any: - def wrapper(*args: Any, **kwargs: Any) -> Any: - token = AuthHelper.get_token_from_auth_header(request) - # Check if bearer token exists and validate it - if not token or not AuthHelper.validate_bearer_token(token): - return "Unauthorized", 401 - request.token = token - return func(*args, **kwargs) - - return wrapper diff --git a/prompt-service/src/unstract/prompt_service/helpers/postprocessor.py b/prompt-service/src/unstract/prompt_service/helpers/postprocessor.py deleted file mode 100644 index 1e7aad72d6..0000000000 --- a/prompt-service/src/unstract/prompt_service/helpers/postprocessor.py +++ /dev/null @@ -1,114 +0,0 @@ -import json -import logging -from typing import Any - -import requests - -logger = logging.getLogger(__name__) - - -def _validate_structured_output(data: Any) -> bool: - """Validate that structured output is a dict or list.""" - return isinstance(data, (dict, list)) - - -def _validate_highlight_data(updated_data: Any, original_data: Any) -> Any: - """Validate highlight data and return appropriate value.""" - if ( - updated_data is not None - and updated_data != original_data - and not isinstance(updated_data, list) - ): - logger.warning( - "Ignoring webhook highlight_data due to invalid type (expected list)" - ) - return original_data - return updated_data - - -def _process_successful_response( - response_data: dict, parsed_data: dict, highlight_data: list | None -) -> tuple[dict[str, Any], list | None]: - """Process successful webhook response.""" - if "structured_output" not in response_data: - logger.warning("Response missing 'structured_output' key") - return parsed_data, highlight_data - - updated_parsed_data = response_data["structured_output"] - - if not _validate_structured_output(updated_parsed_data): - logger.warning("Ignoring postprocessing due to invalid structured_output type") - return parsed_data, highlight_data - - updated_highlight_data = response_data.get("highlight_data", highlight_data) - updated_highlight_data = _validate_highlight_data( - updated_highlight_data, highlight_data - ) - - return updated_parsed_data, updated_highlight_data - - -def _make_webhook_request( - webhook_url: str, payload: dict, timeout: float -) -> tuple[dict[str, Any], list | None] | None: - """Make webhook request and return processed response or None on failure.""" - try: - response = requests.post( - webhook_url, - json=payload, - timeout=timeout, - headers={"Content-Type": "application/json"}, - allow_redirects=False, # Prevent redirect-based SSRF - ) - - if response.status_code != 200: - logger.warning( - f"Postprocessing server returned status code: {response.status_code}" - ) - return None - - return response.json() - - except json.JSONDecodeError as e: - logger.warning(f"Invalid JSON response from postprocessing server: {e}") - except requests.exceptions.Timeout: - logger.warning(f"Postprocessing server request timed out after {timeout}s") - except requests.exceptions.RequestException as e: - logger.warning(f"Postprocessing server request failed: {e}") - except Exception as e: - logger.warning(f"Unexpected error during postprocessing: {e}") - - return None - - -def postprocess_data( - parsed_data: dict[str, Any], - webhook_enabled: bool = False, - webhook_url: str | None = None, - timeout: float = 2.0, - highlight_data: list | None = None, -) -> tuple[dict[str, Any], list | None]: - """Post-process parsed data by sending it to an external server. - - Args: - parsed_data: The parsed data to be post-processed - webhook_enabled: Whether webhook postprocessing is enabled - webhook_url: URL endpoint for the webhook - timeout: Request timeout in seconds (default: 2.0) - highlight_data: Highlight data from metadata to send to webhook - - Returns: - tuple: (postprocessed_data, updated_highlight_data) if successful, otherwise (original_parsed_data, original_highlight_data) - """ - if not webhook_enabled or not webhook_url: - return parsed_data, highlight_data - - payload = {"structured_output": parsed_data} - if highlight_data is not None: - payload["highlight_data"] = highlight_data - - response_data = _make_webhook_request(webhook_url, payload, timeout) - if response_data is None: - return parsed_data, highlight_data - - return _process_successful_response(response_data, parsed_data, highlight_data) diff --git a/prompt-service/src/unstract/prompt_service/helpers/prompt_ide_base_tool.py b/prompt-service/src/unstract/prompt_service/helpers/prompt_ide_base_tool.py deleted file mode 100644 index e2a30a0e2e..0000000000 --- a/prompt-service/src/unstract/prompt_service/helpers/prompt_ide_base_tool.py +++ /dev/null @@ -1,57 +0,0 @@ -import os -import warnings - -from flask import current_app - -from unstract.prompt_service.constants import PromptServiceConstants -from unstract.sdk1.constants import LogLevel -from unstract.sdk1.tool.stream import StreamMixin -from unstract.sdk1.utils.common import PY_TO_UNSTRACT_LOG_LEVEL - - -class PromptServiceBaseTool(StreamMixin): - # Remove unused log_level arg - def __init__(self, log_level: LogLevel | None = None, platform_key: str = "") -> None: - """Args: - tool (UnstractAbstractTool): Instance of UnstractAbstractTool. - Deprecated, will be removed in future versions - Notes: - - PLATFORM_SERVICE_API_KEY environment variable is required. - """ - if log_level: - warnings.warn( - "The 'log_level' argument is deprecated and will be " - "removed in a future version.", - DeprecationWarning, - stacklevel=2, # ensures the warning points to the caller of the method - ) - self.log_level = PY_TO_UNSTRACT_LOG_LEVEL.get( - current_app.logger.getEffectiveLevel(), LogLevel.INFO - ) - self.platform_key = platform_key - super().__init__(log_level=self.log_level) - - def get_env_or_die(self, env_key: str) -> str: - """Returns the value of an env variable. - - If it is None, raises an error and exits - - Args: - env_key (str): Key to retrieve - - Returns: - str: Value of the env - """ - # HACK: Adding platform key for multitenancy - if env_key == PromptServiceConstants.PLATFORM_SERVICE_API_KEY: - if not self.platform_key: - current_app.logger.error(f"{env_key} is required") - else: - key: str = self.platform_key - return key - else: - env_value = os.environ.get(env_key) - if env_value is None: - current_app.logger.error(f"Env variable {env_key} is required") - return env_value # type:ignore - return "" diff --git a/prompt-service/src/unstract/prompt_service/helpers/usage.py b/prompt-service/src/unstract/prompt_service/helpers/usage.py deleted file mode 100644 index 63a14c4336..0000000000 --- a/prompt-service/src/unstract/prompt_service/helpers/usage.py +++ /dev/null @@ -1,140 +0,0 @@ -import logging -import traceback -from logging import Logger -from typing import Any - -from flask import current_app as app - -from unstract.prompt_service.constants import DBTableV2 -from unstract.prompt_service.extensions import db, db_context -from unstract.prompt_service.utils.db_utils import DBUtils -from unstract.prompt_service.utils.env_loader import get_env_or_die -from unstract.sdk1.audit import Audit - -logger = logging.getLogger(__name__) - - -class UsageHelper: - @staticmethod - def query_usage_metadata(token: str, metadata: dict[str, Any]) -> dict[str, Any]: - DB_SCHEMA = get_env_or_die("DB_SCHEMA", "unstract") - organization_uid, org_id = DBUtils.get_organization_from_bearer_token(token) - run_id: str = metadata["run_id"] - query: str = f""" - SELECT - usage_type, - llm_usage_reason, - model_name, - SUM(prompt_tokens) AS input_tokens, - SUM(completion_tokens) AS output_tokens, - SUM(total_tokens) AS total_tokens, - SUM(embedding_tokens) AS embedding_tokens, - SUM(cost_in_dollars) AS cost_in_dollars - FROM "{DB_SCHEMA}"."{DBTableV2.TOKEN_USAGE}" - WHERE run_id = %s and organization_id = %s - GROUP BY usage_type, llm_usage_reason, model_name; - """ - logger: Logger = app.logger - try: - logger.info( - "Querying usage metadata for org_id: %s, run_id: %s", org_id, run_id - ) - with db_context(): - with db.execute_sql(query, (run_id, organization_uid)) as cursor: - results: list[tuple] = cursor.fetchall() - # Process results as needed - for row in results: - key, item = UsageHelper._get_key_and_item(row) - # Initialize the key as an empty list if it doesn't exist - if key not in metadata: - metadata[key] = [] - # Append the item to the list associated with the key - metadata[key].append(item) - except Exception as e: - logger.error(f"Error while querying usage metadata: {e}") - return metadata - - @staticmethod - def _get_key_and_item(row: tuple) -> tuple[str, dict[str, Any]]: - ( - usage_type, - llm_usage_reason, - model_name, - input_tokens, - output_tokens, - total_tokens, - embedding_tokens, - cost_in_dollars, - ) = row - cost_in_dollars: str = UsageHelper._format_float_positional(cost_in_dollars) - key: str = usage_type - item: dict[str, Any] = { - "model_name": model_name, - "cost_in_dollars": cost_in_dollars, - } - if llm_usage_reason: - key = f"{llm_usage_reason}_{key}" - item["input_tokens"] = input_tokens - item["output_tokens"] = output_tokens - item["total_tokens"] = total_tokens - else: - item["embedding_tokens"] = embedding_tokens - return key, item - - @staticmethod - def _format_float_positional(value: float, precision: int = 10) -> str: - formatted: str = f"{value:.{precision}f}" - return formatted.rstrip("0").rstrip(".") if "." in formatted else formatted - - @staticmethod - def push_usage_data( - event_type: str, - kwargs: dict[str, Any], - platform_api_key: str, - token_counter=None, - model_name: str = "", - ) -> bool: - """Push usage data to the audit service. - - Args: - event_type: Type of usage event being recorded - kwargs: Additional data to include with the event - platform_api_key: API key for authentication with the audit service - token_counter: Token counter object with token usage metrics - model_name: Name of the model used (if applicable) - - Returns: - bool: True if successful, False otherwise - Note: - This method handles all exceptions internally and returns False on failure - rather than propagating exceptions to the caller. - """ - if not kwargs or not isinstance(kwargs, dict): - logger.error("Invalid kwargs provided to push_usage_data") - return False - - if not platform_api_key or not isinstance(platform_api_key, str): - logger.error("Invalid platform_api_key provided to push_usage_data") - return False - - try: - logger.debug( - f"Pushing usage data for event_type: {event_type}, model: {model_name}" - ) - - # Call the Audit SDK with the appropriate parameters - Audit().push_usage_data( - platform_api_key=platform_api_key, - token_counter=token_counter, - model_name=model_name, - event_type=event_type, - kwargs=kwargs, - ) - - logger.info(f"Successfully pushed usage data for {model_name}") - return True - except Exception as e: - logger.exception( - f"Error pushing usage data: {str(e)} - {traceback.format_exc()}" - ) - return False diff --git a/prompt-service/src/unstract/prompt_service/helpers/variable_replacement.py b/prompt-service/src/unstract/prompt_service/helpers/variable_replacement.py deleted file mode 100644 index cd794ce590..0000000000 --- a/prompt-service/src/unstract/prompt_service/helpers/variable_replacement.py +++ /dev/null @@ -1,204 +0,0 @@ -import json -import re -from functools import lru_cache -from typing import Any - -from flask import current_app as app - -from unstract.prompt_service.constants import VariableConstants, VariableType -from unstract.prompt_service.exceptions import CustomDataError -from unstract.prompt_service.utils.request import HTTPMethod, make_http_request - - -class VariableReplacementHelper: - @staticmethod - def replace_static_variable( - prompt: str, structured_output: dict[str, Any], variable: str - ) -> str: - output_value = VariableReplacementHelper.check_static_variable_run_status( - structure_output=structured_output, variable=variable - ) - if not output_value: - return prompt - static_variable_marker_string = "".join(["{{", variable, "}}"]) - - replaced_prompt: str = VariableReplacementHelper.replace_generic_string_value( - prompt=prompt, variable=static_variable_marker_string, value=output_value - ) - - return replaced_prompt - - @staticmethod - def check_static_variable_run_status( - structure_output: dict[str, Any], variable: str - ) -> Any: - output = None - try: - output = structure_output[variable] - except KeyError: - app.logger.warning( - f"Prompt with {variable} is not executed yet." - " Unable to replace the variable" - ) - return output - - @staticmethod - def replace_generic_string_value(prompt: str, variable: str, value: Any) -> str: - formatted_value: str = value - if not isinstance(value, str): - formatted_value = VariableReplacementHelper.handle_json_and_str_types(value) - replaced_prompt = prompt.replace(variable, formatted_value) - return replaced_prompt - - @staticmethod - def handle_json_and_str_types(value: Any) -> str: - try: - formatted_value = json.dumps(value) - except ValueError: - formatted_value = str(value) - return formatted_value - - @staticmethod - def identify_variable_type(variable: str) -> VariableType: - variable_type: VariableType - - # Check for custom_data variable type first - custom_data_pattern = re.compile(VariableConstants.CUSTOM_DATA_VARIABLE_REGEX) - if re.findall(custom_data_pattern, variable): - variable_type = VariableType.CUSTOM_DATA - else: - # Check for dynamic variable type - dynamic_pattern = re.compile(VariableConstants.DYNAMIC_VARIABLE_URL_REGEX) - if re.findall(dynamic_pattern, variable): - variable_type = VariableType.DYNAMIC - else: - variable_type = VariableType.STATIC - return variable_type - - @staticmethod - def replace_dynamic_variable( - prompt: str, variable: str, structured_output: dict[str, Any] - ) -> str: - url = re.search(VariableConstants.DYNAMIC_VARIABLE_URL_REGEX, variable).group(0) - data = re.findall(VariableConstants.DYNAMIC_VARIABLE_DATA_REGEX, variable)[0] - output_value = VariableReplacementHelper.check_static_variable_run_status( - structure_output=structured_output, variable=data - ) - if not output_value: - return prompt - api_response: Any = VariableReplacementHelper.fetch_dynamic_variable_value( - url=url, data=output_value - ) - formatted_api_response: str = VariableReplacementHelper.handle_json_and_str_types( - api_response - ) - static_variable_marker_string = "".join(["{{", variable, "}}"]) - replaced_prompt: str = VariableReplacementHelper.replace_generic_string_value( - prompt=prompt, - variable=static_variable_marker_string, - value=formatted_api_response, - ) - return replaced_prompt - - @staticmethod - def replace_custom_data_variable( - prompt: str, - variable: str, - custom_data: dict[str, Any], - is_ide: bool = True, - ) -> str: - """Replace custom_data variable in prompt. - - Args: - prompt: The prompt containing variables - variable: The variable to replace (e.g., "custom_data.name") - custom_data: The custom_data data dictionary - is_ide: Whether this is running from Prompt Studio IDE (affects error messages) - - Returns: - prompt with variable replaced - """ - # Extract the path from custom_data.path.to.value - custom_data_match = re.search( - VariableConstants.CUSTOM_DATA_VARIABLE_REGEX, variable - ) - if not custom_data_match: - error_msg = "Invalid variable format." - app.logger.error(f"{error_msg}: {variable}") - raise CustomDataError(variable=variable, reason=error_msg, is_ide=is_ide) - - path_str = custom_data_match.group(1) - path_parts = path_str.split(".") - - if not custom_data: - error_msg = "Custom data is not configured." - app.logger.error(error_msg) - raise CustomDataError(variable=path_str, reason=error_msg, is_ide=is_ide) - - # Navigate through the nested dictionary - try: - value = custom_data - for part in path_parts: - value = value[part] - except (KeyError, TypeError) as e: - error_msg = f"Key '{path_str}' not found in custom data." - app.logger.error(error_msg) - raise CustomDataError( - variable=path_str, reason=error_msg, is_ide=is_ide - ) from e - - # Replace in prompt - let replace_generic_string_value handle formatting - # (it only applies json.dumps for non-string values) - variable_marker_string = "".join(["{{", variable, "}}"]) - - replaced_prompt = VariableReplacementHelper.replace_generic_string_value( - prompt=prompt, - variable=variable_marker_string, - value=value, - ) - - return replaced_prompt - - @staticmethod - @lru_cache(maxsize=128) - def _extract_variables_cached(prompt_text: str) -> tuple[str, ...]: - """Internal cached extraction - returns tuple for lru_cache compatibility.""" - return tuple(re.findall(VariableConstants.VARIABLE_REGEX, prompt_text)) - - @staticmethod - def extract_variables_from_prompt(prompt_text: str) -> list[str]: - """Extract variables from prompt with caching and stats logging. - - Uses lru_cache internally and logs cache statistics periodically - to help determine if caching is beneficial. - """ - result = VariableReplacementHelper._extract_variables_cached(prompt_text) - - # Log stats periodically (every 50 calls) - info_after = VariableReplacementHelper._extract_variables_cached.cache_info() - total_calls = info_after.hits + info_after.misses - - if total_calls % 50 == 0 and total_calls > 0: - hit_rate = info_after.hits / total_calls * 100 - app.logger.info( - f"[VariableCache] total={total_calls} hits={info_after.hits} " - f"misses={info_after.misses} hit_rate={hit_rate:.1f}% " - f"size={info_after.currsize}/{info_after.maxsize} " - f"prompt_chars={len(prompt_text)}" - ) - - return list(result) - - @staticmethod - def fetch_dynamic_variable_value(url: str, data: str) -> Any: - # This prototype method currently supports - # only endpoints that do not require authentication. - # Additionally, it only accepts plain text - # inputs for POST requests in this version. - # Future versions may include support for - # authentication and other input formats. - - verb: HTTPMethod = HTTPMethod.POST - headers = {"Content-Type": "text/plain"} - response: Any = make_http_request(verb=verb, url=url, data=data, headers=headers) - return response diff --git a/prompt-service/src/unstract/prompt_service/run.py b/prompt-service/src/unstract/prompt_service/run.py deleted file mode 100644 index b50e972506..0000000000 --- a/prompt-service/src/unstract/prompt_service/run.py +++ /dev/null @@ -1,3 +0,0 @@ -from unstract.prompt_service.config import create_app - -app = create_app() diff --git a/prompt-service/src/unstract/prompt_service/services/__init__.py b/prompt-service/src/unstract/prompt_service/services/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/prompt-service/src/unstract/prompt_service/services/answer_prompt.py b/prompt-service/src/unstract/prompt_service/services/answer_prompt.py deleted file mode 100644 index 9f8cbf9c28..0000000000 --- a/prompt-service/src/unstract/prompt_service/services/answer_prompt.py +++ /dev/null @@ -1,521 +0,0 @@ -import ipaddress -import socket -from logging import Logger -from typing import Any -from urllib.parse import urlparse - -from flask import current_app as app - -from unstract.core.flask import PluginManager -from unstract.core.flask.exceptions import APIError -from unstract.prompt_service.constants import ExecutionSource, FileStorageKeys, RunLevel -from unstract.prompt_service.constants import PromptServiceConstants as PSKeys -from unstract.prompt_service.exceptions import RateLimitError -from unstract.prompt_service.helpers.postprocessor import postprocess_data -from unstract.prompt_service.utils.env_loader import get_env_or_die -from unstract.prompt_service.utils.json_repair_helper import ( - repair_json_with_best_structure, -) -from unstract.prompt_service.utils.log import publish_log -from unstract.sdk1.constants import LogLevel -from unstract.sdk1.exceptions import RateLimitError as SdkRateLimitError -from unstract.sdk1.exceptions import SdkError -from unstract.sdk1.file_storage import FileStorage, FileStorageProvider -from unstract.sdk1.file_storage.constants import StorageType -from unstract.sdk1.file_storage.env_helper import EnvHelper -from unstract.sdk1.llm import LLM - - -def _is_safe_public_url(url: str) -> bool: - """Validate webhook URL for SSRF protection. - - Only allows HTTPS and blocks private/loopback/internal addresses. - Resolves all DNS records (A/AAAA) to prevent DNS rebinding attacks. - """ - try: - p = urlparse(url) - if p.scheme not in ("https",): # Only allow HTTPS for security - return False - host = p.hostname or "" - # Block obvious local hosts - if host in ("localhost",): - return False - - addrs: set[str] = set() - # If literal IP, validate directly; else resolve all records (A/AAAA) - try: - ipaddress.ip_address(host) - addrs.add(host) - except ValueError: - try: - for family, _type, _proto, _canonname, sockaddr in socket.getaddrinfo( - host, None, type=socket.SOCK_STREAM - ): - addr = sockaddr[0] - addrs.add(addr) - except Exception: - return False - - if not addrs: - return False - - # Validate all resolved addresses - for addr in addrs: - try: - ip = ipaddress.ip_address(addr) - except ValueError: - return False - if ( - ip.is_private - or ip.is_loopback - or ip.is_link_local - or ip.is_reserved - or ip.is_multicast - ): - return False - return True - except Exception: - return False - - -class AnswerPromptService: - @staticmethod - def extract_variable( - structured_output: dict[str, Any], - variable_names: list[Any], - output: dict[str, Any], - promptx: str, - ) -> str: - logger: Logger = app.logger - for variable_name in variable_names: - if promptx.find(f"%{variable_name}%") >= 0: - if variable_name in structured_output: - promptx = promptx.replace( - f"%{variable_name}%", - str(structured_output[variable_name]), - ) - else: - raise ValueError( - f"Variable {variable_name} not found in structured output" - ) - - if promptx != output[PSKeys.PROMPT]: - logger.info(f"Prompt after variable replacement: {promptx}") - return promptx - - @staticmethod - def construct_and_run_prompt( - tool_settings: dict[str, Any], - output: dict[str, Any], - llm: LLM, - context: str, - prompt: str, - metadata: dict[str, Any], - file_path: str = "", - execution_source: str | None = ExecutionSource.IDE.value, - ) -> str: - platform_postamble = tool_settings.get(PSKeys.PLATFORM_POSTAMBLE, "") - word_confidence_postamble = tool_settings.get( - PSKeys.WORD_CONFIDENCE_POSTAMBLE, "" - ) - summarize_as_source = tool_settings.get(PSKeys.SUMMARIZE_AS_SOURCE) - enable_highlight = tool_settings.get(PSKeys.ENABLE_HIGHLIGHT, False) - enable_word_confidence = tool_settings.get(PSKeys.ENABLE_WORD_CONFIDENCE, False) - # Dependency: enable_word_confidence only works if enable_highlight is enabled - if not enable_highlight: - enable_word_confidence = False - prompt_type = output.get(PSKeys.TYPE, PSKeys.TEXT) - if not enable_highlight or summarize_as_source: - platform_postamble = "" - if not enable_word_confidence or summarize_as_source: - word_confidence_postamble = "" - plugin = PluginManager().get_plugin("json-extraction") - if plugin and hasattr(plugin["entrypoint_cls"], "update_settings"): - plugin["entrypoint_cls"].update_settings(tool_settings, output) - prompt = AnswerPromptService.construct_prompt( - preamble=tool_settings.get(PSKeys.PREAMBLE, ""), - prompt=output[prompt], - postamble=tool_settings.get(PSKeys.POSTAMBLE, ""), - grammar_list=tool_settings.get(PSKeys.GRAMMAR, []), - context=context, - platform_postamble=platform_postamble, - word_confidence_postamble=word_confidence_postamble, - prompt_type=prompt_type, - ) - output[PSKeys.COMBINED_PROMPT] = prompt - return AnswerPromptService.run_completion( - llm=llm, - prompt=prompt, - metadata=metadata, - prompt_key=output[PSKeys.NAME], - prompt_type=prompt_type, - enable_highlight=enable_highlight, - enable_word_confidence=enable_word_confidence, - file_path=file_path, - execution_source=execution_source, - ) - - @staticmethod - def construct_prompt( - preamble: str, - prompt: str, - postamble: str, - grammar_list: list[dict[str, Any]], - context: str, - platform_postamble: str, - word_confidence_postamble: str, - prompt_type: str = PSKeys.TEXT, - ) -> str: - prompt = f"{preamble}\n\nQuestion or Instruction: {prompt}" - if grammar_list is not None and len(grammar_list) > 0: - prompt += "\n" - for grammar in grammar_list: - word = "" - synonyms = [] - if PSKeys.WORD in grammar: - word = grammar[PSKeys.WORD] - if PSKeys.SYNONYMS in grammar: - synonyms = grammar[PSKeys.SYNONYMS] - if len(synonyms) > 0 and word != "": - prompt += ( - f"\nNote: You can consider that the word '{word}' " - f"is the same as {', '.join(synonyms)} in both the question and the context." - ) # noqa - if prompt_type == PSKeys.JSON: - json_postamble = get_env_or_die( - PSKeys.JSON_POSTAMBLE, PSKeys.DEFAULT_JSON_POSTAMBLE - ) - postamble += f"\n{json_postamble}" - if platform_postamble: - platform_postamble += "\n\n" - if word_confidence_postamble: - platform_postamble += f"{word_confidence_postamble}\n\n" - prompt += ( - f"\n\n{postamble}\n\nContext:\n---------------\n{context}\n" - f"-----------------\n\n{platform_postamble}Answer:" - ) - return prompt - - @staticmethod - def run_completion( - llm: LLM, - prompt: str, - metadata: dict[str, str] | None = None, - prompt_key: str | None = None, - prompt_type: str | None = PSKeys.TEXT, - enable_highlight: bool = False, - enable_word_confidence: bool = False, - file_path: str = "", - execution_source: str | None = None, - ) -> str: - logger: Logger = app.logger - try: - highlight_data_plugin: dict[str, Any] = PluginManager().get_plugin( - PSKeys.HIGHLIGHT_DATA_PLUGIN - ) - highlight_data = None - if highlight_data_plugin and enable_highlight: - fs_instance: FileStorage = FileStorage(FileStorageProvider.LOCAL) - if execution_source == ExecutionSource.IDE.value: - fs_instance = EnvHelper.get_storage( - storage_type=StorageType.PERMANENT, - env_name=FileStorageKeys.PERMANENT_REMOTE_STORAGE, - ) - if execution_source == ExecutionSource.TOOL.value: - fs_instance = EnvHelper.get_storage( - storage_type=StorageType.SHARED_TEMPORARY, - env_name=FileStorageKeys.TEMPORARY_REMOTE_STORAGE, - ) - logger.info( - f"Initializing highlight plugin with: " - f"file_path={file_path}, " - f"execution_source={execution_source}, " - f"fs_instance={fs_instance}" - ) - highlight_data = highlight_data_plugin["entrypoint_cls"]( - file_path=file_path, - fs_instance=fs_instance, - enable_word_confidence=enable_word_confidence, - ).run - completion = llm.complete( - prompt=prompt, - process_text=highlight_data, - extract_json=prompt_type.lower() != PSKeys.TEXT, - ) - answer: str = completion[PSKeys.RESPONSE].text - highlight_data = completion.get(PSKeys.HIGHLIGHT_DATA, []) - confidence_data = completion.get(PSKeys.CONFIDENCE_DATA) - word_confidence_data = completion.get(PSKeys.WORD_CONFIDENCE_DATA) - line_numbers = completion.get(PSKeys.LINE_NUMBERS, []) - whisper_hash = completion.get(PSKeys.WHISPER_HASH, "") - if metadata is not None and prompt_key: - metadata.setdefault(PSKeys.HIGHLIGHT_DATA, {})[prompt_key] = ( - highlight_data - ) - metadata.setdefault(PSKeys.LINE_NUMBERS, {})[prompt_key] = line_numbers - metadata[PSKeys.WHISPER_HASH] = whisper_hash - if confidence_data: - metadata.setdefault(PSKeys.CONFIDENCE_DATA, {})[prompt_key] = ( - confidence_data - ) - if enable_word_confidence and word_confidence_data: - metadata.setdefault(PSKeys.WORD_CONFIDENCE_DATA, {})[prompt_key] = ( - word_confidence_data - ) - return answer - except SdkRateLimitError as e: - raise RateLimitError(f"Rate limit error. {str(e)}") from e - except SdkError as e: - logger.error(f"Error fetching response for prompt: {e}.") - # Preserve the status code from the SDK error for proper HTTP response - status_code = getattr(e, "status_code", None) or 500 - raise APIError(message=str(e), code=status_code) from e - - @staticmethod - def extract_table( - output: dict[str, Any], - structured_output: dict[str, Any], - llm: LLM, - execution_source: str, - prompt: str, - ) -> dict[str, Any]: - table_settings = output[PSKeys.TABLE_SETTINGS] - - # Check if prompt has valid schema data using json_repair - has_valid_schema = False - schema_data = None - - if prompt and isinstance(prompt, str): - try: - # Try to repair and parse the prompt as JSON - schema_data = repair_json_with_best_structure(prompt) - # Check if the result is a valid dict (schema object) - if isinstance(schema_data, dict) and schema_data: - has_valid_schema = True - app.logger.info( - "Valid schema detected in prompt, using Smart Table Extractor" - ) - except Exception as e: - app.logger.debug(f"Prompt does not contain valid schema: {e}") - - # If we have a valid schema, use the smart table extractor - if has_valid_schema: - smart_table_plugin: dict[str, Any] = PluginManager().get_plugin( - "smart-table-extractor" - ) - - if smart_table_plugin: - fs_instance = AnswerPromptService._get_file_storage_instance( - execution_source - ) - - try: - # Get the input file from table settings - input_file = table_settings.get("input_file") - - # Run the smart table extractor - result = smart_table_plugin["entrypoint_cls"].run( - llm=llm, - table_settings=table_settings, - fs_instance=fs_instance, - prompt=prompt, - input_file=input_file, - ) - - # Extract the data from the result - answer = result.get("data", []) - structured_output[output[PSKeys.NAME]] = answer - - # We do not support summary and eval for table. - # Hence returning the result - return structured_output - except Exception as e: - app.logger.error(f"Smart Table Extractor failed: {e}") - # Fall back to regular table extractor - app.logger.info("Falling back to regular table extractor") - - # Use regular table extractor (original code) - table_extractor: dict[str, Any] = PluginManager().get_plugin("table-extractor") - if not table_extractor: - raise APIError( - "Unable to extract table details. " - "Please contact admin to resolve this issue." - ) - fs_instance = AnswerPromptService._get_file_storage_instance(execution_source) - - try: - answer = table_extractor["entrypoint_cls"].run_table_extraction( - llm=llm, - table_settings=table_settings, - fs_instance=fs_instance, - prompt=prompt, - ) - structured_output[output[PSKeys.NAME]] = answer - # We do not support summary and eval for table. - # Hence returning the result - return structured_output - except table_extractor["exception_cls"] as e: - msg = f"Couldn't extract table. {e}" - raise APIError(message=msg) - - @staticmethod - def _get_file_storage_instance(execution_source) -> FileStorage: - fs_instance: FileStorage = FileStorage(FileStorageProvider.LOCAL) - if execution_source == ExecutionSource.IDE.value: - fs_instance = EnvHelper.get_storage( - storage_type=StorageType.PERMANENT, - env_name=FileStorageKeys.PERMANENT_REMOTE_STORAGE, - ) - if execution_source == ExecutionSource.TOOL.value: - fs_instance = EnvHelper.get_storage( - storage_type=StorageType.SHARED_TEMPORARY, - env_name=FileStorageKeys.TEMPORARY_REMOTE_STORAGE, - ) - - return fs_instance - - @staticmethod - def handle_json( - answer: str, - structured_output: dict[str, Any], - output: dict[str, Any], - log_events_id: str, - tool_id: str, - doc_name: str, - llm: LLM, - enable_highlight: bool = False, - enable_word_confidence: bool = False, - execution_source: str = ExecutionSource.IDE.value, - metadata: dict[str, Any] | None = None, - file_path: str = "", - ) -> None: - """Handle JSON responses from the LLM.""" - prompt_key = output[PSKeys.NAME] - if answer.lower() == "na": - structured_output[prompt_key] = None - else: - # Use the utility function to repair JSON with the best structure - parsed_data = repair_json_with_best_structure(answer) - - if isinstance(parsed_data, str): - err_msg = "Error parsing response to JSON" - app.logger.error(err_msg) - publish_log( - log_events_id, - { - "tool_id": tool_id, - "prompt_key": prompt_key, - "doc_name": doc_name, - }, - LogLevel.WARN, - RunLevel.RUN, - "Unable to parse JSON response from LLM, try using our" - " cloud / enterprise feature 'record' or 'table' type", - ) - structured_output[prompt_key] = {} - else: - # Get webhook configuration from output settings - webhook_enabled = output.get(PSKeys.ENABLE_POSTPROCESSING_WEBHOOK, False) - webhook_url = output.get(PSKeys.POSTPROCESSING_WEBHOOK_URL) - - # Get highlight data from metadata if available and highlighting is enabled - highlight_data = None - if enable_highlight and metadata and PSKeys.HIGHLIGHT_DATA in metadata: - highlight_data = metadata[PSKeys.HIGHLIGHT_DATA].get(prompt_key) - - # Process data (optionally via webhook) with safety guards and fallback - processed_data = parsed_data - updated_highlight_data = None - - if webhook_enabled: - if not webhook_url: - app.logger.warning( - "Postprocessing webhook enabled but URL missing; skipping." - ) - elif not _is_safe_public_url(webhook_url): - app.logger.warning( - "Postprocessing webhook URL is not allowed; skipping." - ) - else: - try: - processed_data, updated_highlight_data = postprocess_data( - parsed_data, - webhook_enabled=True, - webhook_url=webhook_url, - highlight_data=highlight_data, - timeout=60, - ) - except Exception as e: - app.logger.warning( - f"Postprocessing webhook failed: {e}. Using unprocessed data." - ) - - structured_output[prompt_key] = processed_data - - # Update metadata with processed highlight data if available and highlighting is enabled - if enable_highlight and metadata and updated_highlight_data is not None: - metadata.setdefault(PSKeys.HIGHLIGHT_DATA, {})[prompt_key] = ( - updated_highlight_data - ) - - @staticmethod - def extract_line_item( - tool_settings: dict[str, Any], - output: dict[str, Any], - structured_output: dict[str, Any], - llm: LLM, - file_path: str, - metadata: dict[str, str] | None, - execution_source: str, - ) -> dict[str, Any]: - line_item_extraction_plugin: dict[str, Any] = PluginManager().get_plugin( - "line-item-extraction" - ) - if not line_item_extraction_plugin: - raise APIError(PSKeys.PAID_FEATURE_MSG) - - extract_file_path = file_path - - # Read file content into context - fs_instance: FileStorage = FileStorage(FileStorageProvider.LOCAL) - if execution_source == ExecutionSource.IDE.value: - fs_instance = EnvHelper.get_storage( - storage_type=StorageType.PERMANENT, - env_name=FileStorageKeys.PERMANENT_REMOTE_STORAGE, - ) - if execution_source == ExecutionSource.TOOL.value: - fs_instance = EnvHelper.get_storage( - storage_type=StorageType.SHARED_TEMPORARY, - env_name=FileStorageKeys.TEMPORARY_REMOTE_STORAGE, - ) - - if not fs_instance.exists(extract_file_path): - raise FileNotFoundError( - f"The file at path '{extract_file_path}' does not exist." - ) - context = fs_instance.read(path=extract_file_path, encoding="utf-8", mode="r") - - prompt = AnswerPromptService.construct_prompt( - preamble=tool_settings.get(PSKeys.PREAMBLE, ""), - prompt=output["promptx"], - postamble=tool_settings.get(PSKeys.POSTAMBLE, ""), - grammar_list=tool_settings.get(PSKeys.GRAMMAR, []), - context=context, - platform_postamble="", - word_confidence_postamble="", - ) - try: - line_item_extraction = line_item_extraction_plugin["entrypoint_cls"]( - llm=llm, - tool_settings=tool_settings, - output=output, - prompt=prompt, - structured_output=structured_output, - ) - answer = line_item_extraction.run() - structured_output[output[PSKeys.NAME]] = answer - metadata[PSKeys.CONTEXT][output[PSKeys.NAME]] = [context] - return structured_output - except line_item_extraction_plugin["exception_cls"] as e: - msg = f"Couldn't extract table. {e}" - raise APIError(message=msg) diff --git a/prompt-service/src/unstract/prompt_service/services/extraction.py b/prompt-service/src/unstract/prompt_service/services/extraction.py deleted file mode 100644 index 76430657f9..0000000000 --- a/prompt-service/src/unstract/prompt_service/services/extraction.py +++ /dev/null @@ -1,92 +0,0 @@ -from pathlib import Path -from typing import Any - -from unstract.prompt_service.constants import ExecutionSource -from unstract.prompt_service.constants import IndexingConstants as IKeys -from unstract.prompt_service.exceptions import ExtractionError -from unstract.prompt_service.helpers.prompt_ide_base_tool import PromptServiceBaseTool -from unstract.prompt_service.utils.file_utils import FileUtils -from unstract.sdk1.adapters.exceptions import AdapterError -from unstract.sdk1.adapters.x2text.constants import X2TextConstants -from unstract.sdk1.adapters.x2text.llm_whisperer.src import LLMWhisperer -from unstract.sdk1.adapters.x2text.llm_whisperer_v2.src import LLMWhispererV2 -from unstract.sdk1.utils.common import log_elapsed -from unstract.sdk1.utils.tool import ToolUtils -from unstract.sdk1.x2txt import TextExtractionResult, X2Text - - -class ExtractionService: - @staticmethod - @log_elapsed(operation="EXTRACTION") - def perform_extraction( - x2text_instance_id: str, - file_path: str, - run_id: str, - platform_key: str, - output_file_path: str | None = None, - enable_highlight: bool = False, - usage_kwargs: dict[Any, Any] = {}, - tags: list[str] | None = None, - execution_source: str | None = None, - tool_exec_metadata: dict[str, Any] | None = None, - execution_run_data_folder: str | None = None, - ) -> str: - extracted_text = "" - util = PromptServiceBaseTool(platform_key=platform_key) - x2text = X2Text( - tool=util, adapter_instance_id=x2text_instance_id, usage_kwargs=usage_kwargs - ) - fs = FileUtils.get_fs_instance(execution_source=execution_source) - try: - if enable_highlight and ( - isinstance(x2text.x2text_instance, LLMWhisperer) - or isinstance(x2text.x2text_instance, LLMWhispererV2) - ): - process_response: TextExtractionResult = x2text.process( - input_file_path=file_path, - output_file_path=output_file_path, - enable_highlight=enable_highlight, - tags=tags, - fs=fs, - ) - ExtractionService.update_exec_metadata( - fs, - execution_source, - tool_exec_metadata, - execution_run_data_folder, - process_response, - ) - else: - process_response: TextExtractionResult = x2text.process( - input_file_path=file_path, - output_file_path=output_file_path, - tags=tags, - fs=fs, - ) - extracted_text = process_response.extracted_text - return extracted_text - except AdapterError as e: - msg = f"Error from text extractor '{x2text.x2text_instance.get_name()}'. " - msg += str(e) - code = e.status_code if e.status_code != -1 else 500 - raise ExtractionError(msg, code=code) from e - - @staticmethod - def update_exec_metadata( - fs, - execution_source, - tool_exec_metadata, - execution_run_data_folder, - process_response, - ): - if execution_source == ExecutionSource.TOOL.value: - whisper_hash_value = process_response.extraction_metadata.whisper_hash - metadata = {X2TextConstants.WHISPER_HASH: whisper_hash_value} - for key, value in metadata.items(): - tool_exec_metadata[key] = value - metadata_path = str(Path(execution_run_data_folder) / IKeys.METADATA_FILE) - ToolUtils.dump_json( - file_to_dump=metadata_path, - json_to_dump=metadata, - fs=fs, - ) diff --git a/prompt-service/src/unstract/prompt_service/services/indexing.py b/prompt-service/src/unstract/prompt_service/services/indexing.py deleted file mode 100644 index b71a4f9897..0000000000 --- a/prompt-service/src/unstract/prompt_service/services/indexing.py +++ /dev/null @@ -1,94 +0,0 @@ -import logging - -from unstract.prompt_service.core.index_v2 import Index -from unstract.prompt_service.dto import ( - ChunkingConfig, - FileInfo, - InstanceIdentifiers, - ProcessingOptions, -) -from unstract.prompt_service.exceptions import APIError -from unstract.prompt_service.helpers.prompt_ide_base_tool import PromptServiceBaseTool -from unstract.prompt_service.utils.file_utils import FileUtils -from unstract.sdk1.embedding import EmbeddingCompat -from unstract.sdk1.utils.indexing import IndexingUtils -from unstract.sdk1.vector_db import VectorDB - -logger = logging.getLogger(__name__) - - -class IndexingService: - @staticmethod - def index( - execution_source: str, - chunking_config: ChunkingConfig, - file_info: FileInfo, - instance_identifiers: InstanceIdentifiers, - processing_options: ProcessingOptions, - platform_key: str, - run_id: str, - extracted_text: str, - ) -> str: - try: - fs_instance = FileUtils.get_fs_instance(execution_source=execution_source) - util = PromptServiceBaseTool(platform_key=platform_key) - index: Index = Index( - tool=util, - run_id=run_id, - capture_metrics=True, - instance_identifiers=instance_identifiers, - chunking_config=chunking_config, - processing_options=processing_options, - ) - doc_id: str = IndexingUtils.generate_index_key( - vector_db=instance_identifiers.vector_db_instance_id, - embedding=instance_identifiers.embedding_instance_id, - file_path=file_info.file_path, - file_hash=file_info.file_hash, - x2text=instance_identifiers.x2text_instance_id, - chunk_size=str(chunking_config.chunk_size), - chunk_overlap=str(chunking_config.chunk_overlap), - tool=util, - fs=fs_instance, - ) - - # Skip embedding creation and indexing when chunk_size is 0 - # When chunk_size is 0, we don't need vector operations - if chunking_config.chunk_size == 0: - logger.info(f"Skipping indexing for chunk_size=0. Doc ID: {doc_id}") - return doc_id - - embedding = EmbeddingCompat( - adapter_instance_id=instance_identifiers.embedding_instance_id, - tool=util, - kwargs={ - **processing_options.usage_kwargs, - }, - ) - - vector_db = VectorDB( - tool=util, - adapter_instance_id=instance_identifiers.vector_db_instance_id, - embedding=embedding, - ) - - doc_id_found = index.is_document_indexed( - doc_id=doc_id, - embedding=embedding, - vector_db=vector_db, - ) - - # Index and return doc_id - index.perform_indexing( - vector_db=vector_db, - doc_id=doc_id, - extracted_text=extracted_text, - doc_id_found=doc_id_found, - ) - return doc_id - except Exception as e: - status_code = getattr(e, "status_code", 500) - raise APIError(f"Error while indexing: {str(e)}", code=status_code) from e - finally: - if "vector_db" in locals(): - vector_db.close() diff --git a/prompt-service/src/unstract/prompt_service/services/rentrolls_extractor/interface.py b/prompt-service/src/unstract/prompt_service/services/rentrolls_extractor/interface.py deleted file mode 100644 index 4bf6914211..0000000000 --- a/prompt-service/src/unstract/prompt_service/services/rentrolls_extractor/interface.py +++ /dev/null @@ -1,70 +0,0 @@ -import logging -import os -from typing import Any - -import requests - -logger = logging.getLogger(__name__) - - -class RentRollExtractor: - """Main class for rent roll extraction.""" - - def __init__(self): - """Initialize the rent roll extractor.""" - logger.info("Initialized RentRollExtractor") - pass - - def process( - self, - extractor_settings: dict[str, Any], - extracted_data: str, - llm_config: dict[str, Any], - schema: str, - ) -> dict[str, Any]: - """Process the extraction with the given settings. - - Args: - extractor_settings: Dictionary containing extraction settings. - extracted_data: Extracted data from the document. - llm_config: Dictionary containing LLM configuration. - schema: Schema for the extraction. - - Returns: - Dict containing the extraction results. - """ - logger.info("Starting rent roll extraction process") - try: - # Load environment variables for rent roll service - rentroll_host = os.environ.get("RENTROLL_SERVICE_HOST", "http://localhost") - rentroll_port = os.environ.get("RENTROLL_SERVICE_PORT", "5003") - rentroll_url = f"{rentroll_host}:{rentroll_port}/api/extract-rentrolls" - - # Prepare the request payload - payload = { - "text": extracted_data, - "settings": extractor_settings, - "llm_config": llm_config, - "schema": schema, - } - - # Make HTTP call to rent roll service - logger.info(f"Calling rent roll service at: {rentroll_url}") - response = requests.post(rentroll_url, json=payload) - - if response.status_code == 200: - result = response.json() - - logger.info("Successfully received response from rent roll service") - return result - else: - error_text = response.text - logger.error( - f"Rent roll service returned status {response.status_code}: {error_text}" - ) - raise Exception( - f"Rent roll service error: {response.status_code} - {error_text}" - ) - except Exception as e: - logger.error(f"Error in rent roll extraction: {str(e)}", exc_info=True) - raise diff --git a/prompt-service/src/unstract/prompt_service/services/retrieval.py b/prompt-service/src/unstract/prompt_service/services/retrieval.py deleted file mode 100644 index 92ba46c930..0000000000 --- a/prompt-service/src/unstract/prompt_service/services/retrieval.py +++ /dev/null @@ -1,156 +0,0 @@ -import datetime -from typing import Any - -from flask import current_app as app - -from unstract.prompt_service.constants import PromptServiceConstants as PSKeys -from unstract.prompt_service.constants import RetrievalStrategy -from unstract.prompt_service.core.retrievers.automerging import AutomergingRetriever -from unstract.prompt_service.core.retrievers.fusion import FusionRetriever -from unstract.prompt_service.core.retrievers.keyword_table import KeywordTableRetriever -from unstract.prompt_service.core.retrievers.recursive import RecursiveRetrieval -from unstract.prompt_service.core.retrievers.router import RouterRetriever -from unstract.prompt_service.core.retrievers.simple import SimpleRetriever -from unstract.prompt_service.core.retrievers.subquestion import SubquestionRetriever -from unstract.prompt_service.services.answer_prompt import AnswerPromptService -from unstract.prompt_service.utils.file_utils import FileUtils -from unstract.prompt_service.utils.metrics import Metrics -from unstract.sdk1.llm import LLM -from unstract.sdk1.vector_db import VectorDB - - -class RetrievalService: - @staticmethod - def perform_retrieval( # type:ignore - tool_settings: dict[str, Any], - output: dict[str, Any], - doc_id: str, - llm: LLM, - vector_db: VectorDB, - retrieval_type: str, - metadata: dict[str, Any], - chunk_size: int, - execution_source: str, - file_path: str, - context_retrieval_metrics: dict[str, Any], - ) -> tuple[str, list[str]]: - prompt_name = output.get(PSKeys.NAME, "") - vector_db_id = ( - getattr(vector_db, "_adapter_instance_id", None) if vector_db else None - ) - app.logger.info( - f"[Retrieval] prompt='{prompt_name}' doc_id={doc_id} " - f"chunk_size={chunk_size} method={'complete_context' if chunk_size == 0 else 'chunked'}" - + (f" vector_db={vector_db_id}" if vector_db_id else "") - ) - - context: list[str] - if chunk_size == 0: - context = RetrievalService.retrieve_complete_context( - execution_source=execution_source, - file_path=file_path, - context_retrieval_metrics=context_retrieval_metrics, - prompt_key=prompt_name, - ) - else: - context = RetrievalService.run_retrieval( - output=output, - doc_id=doc_id, - llm=llm, - vector_db=vector_db, - retrieval_type=retrieval_type, - context_retrieval_metrics=context_retrieval_metrics, - ) - answer = AnswerPromptService.construct_and_run_prompt( # type:ignore - tool_settings=tool_settings, - output=output, - llm=llm, - context="\n".join(context), - prompt="promptx", - metadata=metadata, - execution_source=execution_source, - file_path=file_path, - ) - return answer, context - - @staticmethod - def run_retrieval( # type:ignore - output: dict[str, Any], - doc_id: str, - llm: LLM, - vector_db: VectorDB, - retrieval_type: str, - context_retrieval_metrics: dict[str, Any], - ) -> list[str]: - context: set[str] - prompt = output[PSKeys.PROMPTX] - top_k = output[PSKeys.SIMILARITY_TOP_K] - prompt_key = output[PSKeys.NAME] - retrieval_start_time = datetime.datetime.now() - - # Map retrieval type to retriever class - retriever_map = { - RetrievalStrategy.SIMPLE.value: SimpleRetriever, - RetrievalStrategy.SUBQUESTION.value: SubquestionRetriever, - RetrievalStrategy.FUSION.value: FusionRetriever, - RetrievalStrategy.RECURSIVE.value: RecursiveRetrieval, - RetrievalStrategy.ROUTER.value: RouterRetriever, - RetrievalStrategy.KEYWORD_TABLE.value: KeywordTableRetriever, - RetrievalStrategy.AUTOMERGING.value: AutomergingRetriever, - } - - # Get the appropriate retriever class - retriever_class = retriever_map.get(retrieval_type) - if not retriever_class: - raise ValueError(f"Unknown retrieval type: {retrieval_type}") - - # Create and execute retriever - retriever = retriever_class( - vector_db=vector_db, - doc_id=doc_id, - prompt=prompt, - top_k=top_k, - llm=llm, - ) - context = retriever.retrieve() - elapsed = Metrics.elapsed_time(start_time=retrieval_start_time) - context_retrieval_metrics[prompt_key] = {"time_taken(s)": elapsed} - - app.logger.info( - f"[Retrieval] prompt='{prompt_key}' doc_id={doc_id} " - f"strategy='{retrieval_type}' top_k={top_k} chunks={len(context)} time={elapsed:.3f}s" - ) - - return list(context) - - @staticmethod - def retrieve_complete_context( - execution_source: str, - file_path: str, - context_retrieval_metrics: dict[str, Any], - prompt_key: str, - ) -> list[str]: - """Loads full context from raw file for zero chunk size retrieval. - - Args: - execution_source: Source of execution (e.g., "api", "workflow"). - file_path: Path to the extracted text file. - context_retrieval_metrics: Dict to store retrieval timing metrics - (modified in-place). - prompt_key: Name/identifier of the prompt for metrics tracking. - - Returns: - List containing the complete file content as a single string. - """ - fs_instance = FileUtils.get_fs_instance(execution_source=execution_source) - retrieval_start_time = datetime.datetime.now() - context = fs_instance.read(path=file_path, mode="r") - elapsed = Metrics.elapsed_time(start_time=retrieval_start_time) - context_retrieval_metrics[prompt_key] = {"time_taken(s)": elapsed} - - app.logger.info( - f"[Retrieval] prompt='{prompt_key}' complete_context " - f"chars={len(context)} time={elapsed:.3f}s" - ) - - return [context] diff --git a/prompt-service/src/unstract/prompt_service/services/variable_replacement.py b/prompt-service/src/unstract/prompt_service/services/variable_replacement.py deleted file mode 100644 index 41ba342ac2..0000000000 --- a/prompt-service/src/unstract/prompt_service/services/variable_replacement.py +++ /dev/null @@ -1,134 +0,0 @@ -from typing import Any - -from flask import current_app as app - -from unstract.prompt_service.constants import PromptServiceConstants as PSKeys -from unstract.prompt_service.constants import RunLevel, VariableType -from unstract.prompt_service.helpers.variable_replacement import ( - VariableReplacementHelper, -) -from unstract.prompt_service.utils.log import publish_log -from unstract.sdk1.constants import LogLevel - - -class VariableReplacementService: - @staticmethod - def is_variables_present(prompt_text: str) -> bool: - """Determines if variables are present in the prompt. - - Args: - prompt (str): Prompt to check - - Returns: - bool: True if variables are present else False - """ - return bool( - len(VariableReplacementHelper.extract_variables_from_prompt(prompt_text)) - ) - - @staticmethod - def replace_variables_in_prompt( - prompt: dict[str, Any], - structured_output: dict[str, Any], - log_events_id: str, - tool_id: str, - prompt_name: str, - doc_name: str, - custom_data: dict[str, Any] = None, - is_ide: bool = True, - ) -> str: - """Replaces variables in prompt. - - Args: - prompt (dict[str, Any]): Dict representing the prompt card - structured_output (dict[str, Any]): Structured data used for variable - replacement when variable map is not present in `prompt`. - log_events_id (str): UUID for the WS communication - tool_id (str): UUID for the prompt studio project - prompt_name (str): Name of the prompt being run - doc_name (str): Name of the document being run with - Returns: - prompt_text (str): Prompt with variables replaced - """ - app.logger.info(f"[{tool_id}] Replacing variables in prompt : {prompt_name}") - publish_log( - log_events_id, - { - "tool_id": tool_id, - "prompt_key": prompt_name, - "doc_name": doc_name, - }, - LogLevel.DEBUG, - RunLevel.RUN, - "Replacing variables in prompt", - ) - # Initialized with the prompt because - # it is used in finally block - prompt_text = prompt[PSKeys.PROMPT] - try: - variable_map = prompt[PSKeys.VARIABLE_MAP] - prompt_text = VariableReplacementService._execute_variable_replacement( - prompt_text=prompt[PSKeys.PROMPT], - variable_map=variable_map, - custom_data=custom_data, - is_ide=is_ide, - ) - except KeyError: - # Executed incase of structured tool and - # APIs where we do not set the variable map - prompt_text = VariableReplacementService._execute_variable_replacement( - prompt_text=prompt_text, - variable_map=structured_output, - custom_data=custom_data, - is_ide=is_ide, - ) - finally: - publish_log( - log_events_id, - { - "tool_id": tool_id, - "prompt_key": prompt_name, - "doc_name": doc_name, - }, - LogLevel.DEBUG, - RunLevel.RUN, - f"Variables replaced in prompt with key :{prompt_name}", - ) - return prompt_text - - @staticmethod - def _execute_variable_replacement( - prompt_text: str, - variable_map: dict[str, Any], - custom_data: dict[str, Any] = None, - is_ide: bool = True, - ) -> str: - variables: list[str] = VariableReplacementHelper.extract_variables_from_prompt( - prompt_text=prompt_text - ) - for variable in variables: - variable_type = VariableReplacementHelper.identify_variable_type( - variable=variable - ) - if variable_type == VariableType.STATIC: - prompt_text = VariableReplacementHelper.replace_static_variable( - prompt=prompt_text, - structured_output=variable_map, - variable=variable, - ) - - elif variable_type == VariableType.DYNAMIC: - prompt_text = VariableReplacementHelper.replace_dynamic_variable( - prompt=prompt_text, - variable=variable, - structured_output=variable_map, - ) - - elif variable_type == VariableType.CUSTOM_DATA: - prompt_text = VariableReplacementHelper.replace_custom_data_variable( - prompt=prompt_text, - variable=variable, - custom_data=custom_data or {}, - is_ide=is_ide, - ) - return prompt_text diff --git a/prompt-service/src/unstract/prompt_service/tests/conftest.py b/prompt-service/src/unstract/prompt_service/tests/conftest.py deleted file mode 100644 index c04fa27d3a..0000000000 --- a/prompt-service/src/unstract/prompt_service/tests/conftest.py +++ /dev/null @@ -1,29 +0,0 @@ -import pytest -from dotenv import load_dotenv -from flask import Flask -from flask_wtf.csrf import CSRFProtect - -from unstract.prompt_service.controllers.extraction import extraction_bp -from unstract.prompt_service.controllers.indexing import indexing_bp - -# Load test environment variables -load_dotenv(".env.test") - - -@pytest.fixture -def client(): - app = Flask(__name__) - app.register_blueprint(indexing_bp) - app.register_blueprint(extraction_bp) - app.config.update( - { - "TESTING": True, - "DEBUG": True, - "PROPAGATE_EXCEPTIONS": True, - "WTF_CSRF_ENABLED": False, - } - ) - csrf = CSRFProtect() - csrf.init_app(app) # Compliant - with app.test_client() as client: - yield client diff --git a/prompt-service/src/unstract/prompt_service/tests/integration/input/sample1.pdf b/prompt-service/src/unstract/prompt_service/tests/integration/input/sample1.pdf deleted file mode 100644 index 960486fddb..0000000000 Binary files a/prompt-service/src/unstract/prompt_service/tests/integration/input/sample1.pdf and /dev/null differ diff --git a/prompt-service/src/unstract/prompt_service/tests/integration/test_api_endpoints.py b/prompt-service/src/unstract/prompt_service/tests/integration/test_api_endpoints.py deleted file mode 100644 index 3adaa820d5..0000000000 --- a/prompt-service/src/unstract/prompt_service/tests/integration/test_api_endpoints.py +++ /dev/null @@ -1,85 +0,0 @@ -import json -import os - -from dotenv import load_dotenv -from flask.testing import FlaskClient - -from unstract.prompt_service.helpers.auth import AuthHelper - -load_dotenv(".env.test") - - -def test_index(client: FlaskClient, mocker): - # Mock the AuthHelper to bypass authentication - mocker.patch.object(AuthHelper, "auth_required", lambda x: x) - mocker.patch.object( - AuthHelper, - "get_token_from_auth_header", - return_value=os.getenv("TEST_PLATFORM_KEY"), - ) - - # Define the payload - payload = { - "tool_id": "mock_tool_id", - "embedding_instance_id": os.getenv("TEST_EMBEDDING_ID"), - "vector_db_instance_id": os.getenv("TEST_VECTOR_DB_ID"), - "x2text_instance_id": os.getenv("TEST_X2TEXT_ID"), - "chunk_size": os.getenv("TEST_CHUNK_SIZE"), - "chunk_overlap": os.getenv("TEST_CHUNK_OVERLAP"), - "execution_source": "ide", - "run_id": os.getenv("TEST_RUN_ID"), - "extracted_text": os.getenv("TEST_EXTRACTED_TEXT"), - "usage_kwargs": {}, - "tags": os.getenv("TEST_TAGS"), - "reindex": False, - "enable_highlight": False, - } - - # Send a POST request to the index endpoint - try: - response = client.post( - "/index", data=json.dumps(payload), content_type="application/json" - ) - - except Exception as e: - import traceback - - traceback.print_exc() - assert False, f"Test failed due to: {e}" - # Assert the response - print(response.data.decode()) # Full response content - assert response.status_code == 200 - response_data = json.loads(response.data) - assert "doc_id" in response_data - - -def test_extract(client: FlaskClient, mocker): - # Mock the AuthHelper to bypass authentication - mocker.patch.object(AuthHelper, "auth_required", lambda x: x) - mocker.patch.object( - AuthHelper, - "get_token_from_auth_header", - return_value=os.getenv("TEST_PLATFORM_KEY"), - ) - - # Define the payload - payload = { - "x2text_instance_id": os.getenv("X2TEXT_INSTANCE_ID"), - "file_path": os.getenv("TEST_SOURCE_FILE_PATH"), - "execution_source": "ide", - "run_id": "mock_run_id", - "tags": "mock_tag", - "output_file_path": os.getenv("TEST_OUTPUT_FILE_PATH"), - "enable_highlight": False, - "usage_kwargs": {}, - } - - # Send a POST request to the extract endpoint - response = client.post( - "/extract", data=json.dumps(payload), content_type="application/json" - ) - - # Assert the response - assert response.status_code == 200 - response_data = json.loads(response.data) - assert "extracted_text" in response_data diff --git a/prompt-service/src/unstract/prompt_service/tests/sample.env.test b/prompt-service/src/unstract/prompt_service/tests/sample.env.test deleted file mode 100644 index 7554cc2157..0000000000 --- a/prompt-service/src/unstract/prompt_service/tests/sample.env.test +++ /dev/null @@ -1,24 +0,0 @@ -TEST_PLATFORM_KEY= -TEST_EMBEDDING_ID=d -TEST_VECTOR_DB_ID= -TEST_X2TEXT_ID= -TEST_TOOL_ID=mock_tool_id -TEST_FILE_PATH=unstract/prompt-studio-data/sample1.pdf -TEST_CHUNK_SIZE=512 -TEST_CHUNK_OVERLAP=128 -TEST_EXECUTION_SOURCE=ide -TEST_RUN_ID=mock_run_id -TEST_EXTRACTED_TEXT=mock_extracted_text -TEST_TAGS=mock_tag1,mock_tag2 -TEST_SOURCE_FILE_PATH=/unstract/prompt-service/src/unstract/prompt_service/tests/integration/input/sample1.pdf -TEST_OUTPUT_FILE_PATH=/unstract/prompt-service/src/unstract/prompt_service/tests/integration/output/output.txt - -# Platform Service -# Platform service is expected to be up and running for this integration test to run -PLATFORM_SERVICE_HOST=http://127.0.0.1/ -PLATFORM_SERVICE_PORT=3001 - -#Remote storage related envs -PERMANENT_REMOTE_STORAGE='{"provider": "minio", "credentials": {"endpoint_url": "http://localhost:9000", "key": "minio", "secret": "minio123"}}' -TEMPORARY_REMOTE_STORAGE='{"provider": "minio", "credentials": {"endpoint_url": "http://unstract-minio:9000", "key": "minio", "secret": "minio123"}}' -REMOTE_PROMPT_STUDIO_FILE_PATH="unstract/prompt-studio-data/" diff --git a/prompt-service/src/unstract/prompt_service/tests/unit/__init__.py b/prompt-service/src/unstract/prompt_service/tests/unit/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/prompt-service/src/unstract/prompt_service/tests/unit/conftest.py b/prompt-service/src/unstract/prompt_service/tests/unit/conftest.py deleted file mode 100644 index b78a6985a1..0000000000 --- a/prompt-service/src/unstract/prompt_service/tests/unit/conftest.py +++ /dev/null @@ -1,12 +0,0 @@ -"""Pytest configuration for unit tests. - -Unit tests should not require external dependencies or the full app. -This conftest intentionally does NOT import Flask app components. - -WARNING: This file is NOT auto-loaded when running via tox because ---noconftest is used to skip the parent tests/conftest.py (which -imports Flask blueprints and triggers the full adapter import chain). -If you add shared fixtures here, either remove --noconftest from -tox.ini and fix the parent conftest's eager imports, or define -fixtures directly in test files. -""" diff --git a/prompt-service/src/unstract/prompt_service/tests/unit/test_retriever_llm.py b/prompt-service/src/unstract/prompt_service/tests/unit/test_retriever_llm.py deleted file mode 100644 index 377988b20c..0000000000 --- a/prompt-service/src/unstract/prompt_service/tests/unit/test_retriever_llm.py +++ /dev/null @@ -1,315 +0,0 @@ -"""Unit tests for RetrieverLLM bridge class and BaseRetriever.llm property.""" - -import sys -from unittest.mock import AsyncMock, MagicMock, patch - -import pytest -from llama_index.core.base.llms.types import ( - ChatMessage, - ChatResponse, - CompletionResponse, - LLMMetadata, - MessageRole, -) -from llama_index.core.llms.llm import LLM as LlamaIndexBaseLLM # noqa: N811 -from unstract.prompt_service.core.retrievers.retriever_llm import RetrieverLLM -from unstract.sdk1.llm import LLM, LLMCompat -from unstract.sdk1.llm import ChatMessage as EmulatedChatMessage -from unstract.sdk1.llm import ChatResponse as EmulatedChatResponse -from unstract.sdk1.llm import CompletionResponse as EmulatedCompletionResponse -from unstract.sdk1.llm import LLMMetadata as EmulatedLLMMetadata -from unstract.sdk1.llm import MessageRole as EmulatedMessageRole - -# ── Fixtures ───────────────────────────────────────────────────────────────── - - -@pytest.fixture -def mock_sdk1_llm(): - """Create a mock SDK1 LLM instance with adapter attributes.""" - llm = MagicMock(spec=LLM) - llm._adapter_id = "openai" - llm._adapter_metadata = {"api_key": "test-key"} - llm._adapter_instance_id = "inst-123" - llm._tool = MagicMock() - llm._usage_kwargs = {"run_id": "test-run"} - llm._capture_metrics = False - llm.get_model_name.return_value = "gpt-4" - llm.platform_kwargs = {"run_id": "test-run"} - return llm - - -@pytest.fixture -def mock_compat(): - """Create a mock LLMCompat instance.""" - compat = MagicMock(spec=LLMCompat) - compat.get_model_name.return_value = "gpt-4" - compat.metadata = EmulatedLLMMetadata( - is_chat_model=True, model_name="gpt-4" - ) - return compat - - -# ── BaseRetriever.llm property tests ──────────────────────────────────────── -# BaseRetriever imports VectorDB which triggers the full adapter registration -# chain (Pinecone, Milvus, etc.). We stub unstract.sdk1.vector_db in -# sys.modules so that BaseRetriever can be imported without those heavy deps. - - -@pytest.fixture -def _stub_vector_db(): - """Temporarily stub VectorDB module for BaseRetriever import.""" - stub = MagicMock() - key = "unstract.sdk1.vector_db" - original = sys.modules.get(key) - sys.modules[key] = stub - # Also ensure the base_retriever module is re-imported with the stub. - mod_key = ( - "unstract.prompt_service.core.retrievers.base_retriever" - ) - sys.modules.pop(mod_key, None) - yield stub - # Restore original module (or remove stub). - if original is not None: - sys.modules[key] = original - else: - sys.modules.pop(key, None) - sys.modules.pop(mod_key, None) - - -@pytest.fixture -def base_retriever_cls(_stub_vector_db): - """Import and return BaseRetriever with VectorDB stubbed.""" - from unstract.prompt_service.core.retrievers.base_retriever import ( - BaseRetriever, - ) - - return BaseRetriever - - -class TestBaseRetrieverLlmProperty: - """Tests for BaseRetriever.llm lazy property.""" - - def test_returns_none_when_no_llm_provided( - self, base_retriever_cls - ): - """llm property should return None when constructed without LLM.""" - retriever = base_retriever_cls( - vector_db=MagicMock(), - prompt="test", - doc_id="doc-1", - top_k=5, - ) - assert retriever.llm is None - - def test_returns_retriever_llm_instance( - self, base_retriever_cls, mock_sdk1_llm - ): - """llm property should return a RetrieverLLM wrapping SDK1 LLM.""" - with patch.object(LLMCompat, "from_llm", return_value=MagicMock()): - retriever = base_retriever_cls( - vector_db=MagicMock(), - prompt="test", - doc_id="doc-1", - top_k=5, - llm=mock_sdk1_llm, - ) - result = retriever.llm - - assert isinstance(result, RetrieverLLM) - assert isinstance(result, LlamaIndexBaseLLM) - - def test_lazily_creates_retriever_llm( - self, base_retriever_cls, mock_sdk1_llm - ): - """RetrieverLLM should not be created until .llm is accessed.""" - retriever = base_retriever_cls( - vector_db=MagicMock(), - prompt="test", - doc_id="doc-1", - top_k=5, - llm=mock_sdk1_llm, - ) - # Before accessing .llm, the internal cache should be None. - assert retriever._retriever_llm is None - - with patch.object(LLMCompat, "from_llm", return_value=MagicMock()): - _ = retriever.llm - - # After access, it should be populated. - assert retriever._retriever_llm is not None - - def test_caches_retriever_llm_across_accesses( - self, base_retriever_cls, mock_sdk1_llm - ): - """Repeated .llm accesses should return the same instance.""" - with patch.object(LLMCompat, "from_llm", return_value=MagicMock()): - retriever = base_retriever_cls( - vector_db=MagicMock(), - prompt="test", - doc_id="doc-1", - top_k=5, - llm=mock_sdk1_llm, - ) - first = retriever.llm - second = retriever.llm - - assert first is second - - def test_from_llm_called_once_on_repeated_access( - self, base_retriever_cls, mock_sdk1_llm - ): - """LLMCompat.from_llm should only be called once across accesses.""" - with patch.object( - LLMCompat, "from_llm", return_value=MagicMock() - ) as mock_factory: - retriever = base_retriever_cls( - vector_db=MagicMock(), - prompt="test", - doc_id="doc-1", - top_k=5, - llm=mock_sdk1_llm, - ) - _ = retriever.llm - _ = retriever.llm - _ = retriever.llm - - mock_factory.assert_called_once() - - def test_raw_llm_still_accessible( - self, base_retriever_cls, mock_sdk1_llm - ): - """The raw SDK1 LLM should remain accessible via _llm.""" - retriever = base_retriever_cls( - vector_db=MagicMock(), - prompt="test", - doc_id="doc-1", - top_k=5, - llm=mock_sdk1_llm, - ) - assert retriever._llm is mock_sdk1_llm - - -# ── RetrieverLLM tests ─────────────────────────────────────────────────────── - - -class TestRetrieverLLM: - """Tests for the RetrieverLLM bridge class.""" - - def test_isinstance_llama_index_llm(self, mock_sdk1_llm): - """RetrieverLLM must pass llama-index's isinstance check.""" - with patch.object(LLMCompat, "from_llm", return_value=MagicMock()): - retriever_llm = RetrieverLLM(llm=mock_sdk1_llm) - assert isinstance(retriever_llm, LlamaIndexBaseLLM) - - def test_uses_from_llm_factory(self, mock_sdk1_llm): - """RetrieverLLM should use LLMCompat.from_llm, not private attrs.""" - with patch.object( - LLMCompat, "from_llm", return_value=MagicMock() - ) as mock_factory: - RetrieverLLM(llm=mock_sdk1_llm) - mock_factory.assert_called_once_with(mock_sdk1_llm) - - def test_metadata_returns_llama_index_type( - self, mock_sdk1_llm, mock_compat - ): - """Metadata property should return llama-index LLMMetadata.""" - with patch.object(LLMCompat, "from_llm", return_value=mock_compat): - retriever_llm = RetrieverLLM(llm=mock_sdk1_llm) - meta = retriever_llm.metadata - assert isinstance(meta, LLMMetadata) - assert meta.is_chat_model is True - assert meta.model_name == "gpt-4" - - def test_chat_delegates_and_converts_types( - self, mock_sdk1_llm, mock_compat - ): - """chat() should delegate to compat and return llama-index types.""" - mock_compat.chat.return_value = EmulatedChatResponse( - message=EmulatedChatMessage( - role=EmulatedMessageRole.ASSISTANT, - content="Hello from LLM", - ), - raw={"id": "resp-1"}, - ) - with patch.object(LLMCompat, "from_llm", return_value=mock_compat): - retriever_llm = RetrieverLLM(llm=mock_sdk1_llm) - messages = [ - ChatMessage(role=MessageRole.USER, content="Hi") - ] - result = retriever_llm.chat(messages) - - assert isinstance(result, ChatResponse) - assert result.message.content == "Hello from LLM" - assert result.raw == {"id": "resp-1"} - mock_compat.chat.assert_called_once() - - def test_complete_delegates_and_converts_types( - self, mock_sdk1_llm, mock_compat - ): - """complete() should delegate to compat and return llama-index types.""" - mock_compat.complete.return_value = EmulatedCompletionResponse( - text="Completed text", raw={"id": "resp-2"} - ) - with patch.object(LLMCompat, "from_llm", return_value=mock_compat): - retriever_llm = RetrieverLLM(llm=mock_sdk1_llm) - result = retriever_llm.complete("Test prompt") - - assert isinstance(result, CompletionResponse) - assert result.text == "Completed text" - assert result.raw == {"id": "resp-2"} - mock_compat.complete.assert_called_once() - - @pytest.mark.asyncio - async def test_achat_delegates_and_converts_types( - self, mock_sdk1_llm, mock_compat - ): - """achat() should delegate to compat and return llama-index types.""" - mock_compat.achat = AsyncMock( - return_value=EmulatedChatResponse( - message=EmulatedChatMessage( - role=EmulatedMessageRole.ASSISTANT, - content="Async hello", - ), - raw={"id": "resp-3"}, - ) - ) - with patch.object(LLMCompat, "from_llm", return_value=mock_compat): - retriever_llm = RetrieverLLM(llm=mock_sdk1_llm) - messages = [ - ChatMessage(role=MessageRole.USER, content="Hi async") - ] - result = await retriever_llm.achat(messages) - - assert isinstance(result, ChatResponse) - assert result.message.content == "Async hello" - - @pytest.mark.asyncio - async def test_acomplete_delegates_and_converts_types( - self, mock_sdk1_llm, mock_compat - ): - """acomplete() should delegate to compat and return llama-index types.""" - mock_compat.acomplete = AsyncMock( - return_value=EmulatedCompletionResponse( - text="Async completed", raw={"id": "resp-4"} - ) - ) - with patch.object(LLMCompat, "from_llm", return_value=mock_compat): - retriever_llm = RetrieverLLM(llm=mock_sdk1_llm) - result = await retriever_llm.acomplete("Async prompt") - - assert isinstance(result, CompletionResponse) - assert result.text == "Async completed" - - def test_stream_chat_not_implemented(self, mock_sdk1_llm): - """stream_chat() should raise NotImplementedError.""" - with patch.object(LLMCompat, "from_llm", return_value=MagicMock()): - retriever_llm = RetrieverLLM(llm=mock_sdk1_llm) - with pytest.raises(NotImplementedError): - retriever_llm.stream_chat([]) - - def test_stream_complete_not_implemented(self, mock_sdk1_llm): - """stream_complete() should raise NotImplementedError.""" - with patch.object(LLMCompat, "from_llm", return_value=MagicMock()): - retriever_llm = RetrieverLLM(llm=mock_sdk1_llm) - with pytest.raises(NotImplementedError): - retriever_llm.stream_complete("prompt") diff --git a/prompt-service/src/unstract/prompt_service/utils/__init__.py b/prompt-service/src/unstract/prompt_service/utils/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/prompt-service/src/unstract/prompt_service/utils/db_utils.py b/prompt-service/src/unstract/prompt_service/utils/db_utils.py deleted file mode 100644 index a350f28061..0000000000 --- a/prompt-service/src/unstract/prompt_service/utils/db_utils.py +++ /dev/null @@ -1,44 +0,0 @@ -from typing import Any - -from unstract.prompt_service.constants import DBTableV2 -from unstract.prompt_service.extensions import db, db_context -from unstract.prompt_service.utils.env_loader import get_env_or_die - -DB_SCHEMA = get_env_or_die("DB_SCHEMA", "unstract") - - -class DBUtils: - @classmethod - def get_organization_from_bearer_token(cls, token: str) -> tuple[int | None, str]: - """Retrieve organization ID and identifier using a bearer token. - - Args: - token (str): The bearer token (platform key). - - Returns: - tuple[int, str]: organization uid and organization identifier - """ - platform_key_table = f'"{DB_SCHEMA}".{DBTableV2.PLATFORM_KEY}' - organization_table = f'"{DB_SCHEMA}".{DBTableV2.ORGANIZATION}' - - organization_uid: int | None = cls.execute_query( - f"SELECT organization_id FROM {platform_key_table} WHERE key=%s", (token,) - ) - if organization_uid is None: - return None, None - - organization_identifier: str | None = cls.execute_query( - f"SELECT organization_id FROM {organization_table} WHERE id=%s", - (organization_uid,), - ) - return organization_uid, organization_identifier - - @classmethod - def execute_query(cls, query: str, params: tuple = ()) -> Any: - with db_context(): - cursor = db.execute_sql(query, params) - result_row = cursor.fetchone() - cursor.close() - if not result_row or len(result_row) == 0: - return None - return result_row[0] diff --git a/prompt-service/src/unstract/prompt_service/utils/env_loader.py b/prompt-service/src/unstract/prompt_service/utils/env_loader.py deleted file mode 100644 index 481a3eb34a..0000000000 --- a/prompt-service/src/unstract/prompt_service/utils/env_loader.py +++ /dev/null @@ -1,20 +0,0 @@ -import os - - -def get_env_or_die(env_key: str, default: str | None = None) -> str: - """Get the value of an environment variable or raise an error if it is not set. - - Args: - env_key (str): Name of the environment variable. - default (Optional[str]): Default value if the variable is not set. - - Returns: - str: The value of the environment variable. - - Raises: - ValueError: If the variable is not set. - """ - env_value = os.environ.get(env_key, default=default) - if env_value is None: - raise ValueError(f"Env variable {env_key} is required") - return env_value diff --git a/prompt-service/src/unstract/prompt_service/utils/file_utils.py b/prompt-service/src/unstract/prompt_service/utils/file_utils.py deleted file mode 100644 index ceb32acab8..0000000000 --- a/prompt-service/src/unstract/prompt_service/utils/file_utils.py +++ /dev/null @@ -1,33 +0,0 @@ -from unstract.prompt_service.constants import ExecutionSource, FileStorageKeys -from unstract.sdk1.file_storage import FileStorage -from unstract.sdk1.file_storage.constants import StorageType -from unstract.sdk1.file_storage.env_helper import EnvHelper - - -class FileUtils: - @staticmethod - def get_fs_instance(execution_source: str) -> FileStorage: - """Returns a FileStorage instance based on the execution source. - - Args: - execution_source (str): The source from which the execution is triggered. - - Returns: - FileStorage: The file storage instance - Permanent/Shared temporary - - Raises: - ValueError: If the execution source is invalid. - """ - if execution_source == ExecutionSource.IDE.value: - return EnvHelper.get_storage( - storage_type=StorageType.PERMANENT, - env_name=FileStorageKeys.PERMANENT_REMOTE_STORAGE, - ) - - if execution_source == ExecutionSource.TOOL.value: - return EnvHelper.get_storage( - storage_type=StorageType.SHARED_TEMPORARY, - env_name=FileStorageKeys.TEMPORARY_REMOTE_STORAGE, - ) - - raise ValueError(f"Invalid execution source: {execution_source}") diff --git a/prompt-service/src/unstract/prompt_service/utils/json_repair_helper.py b/prompt-service/src/unstract/prompt_service/utils/json_repair_helper.py deleted file mode 100644 index c15918dc26..0000000000 --- a/prompt-service/src/unstract/prompt_service/utils/json_repair_helper.py +++ /dev/null @@ -1,64 +0,0 @@ -"""JSON repair utility functions.""" - -from typing import Any - -from json_repair import repair_json - - -def repair_json_with_best_structure(json_str: str) -> Any: - """Intelligently repair JSON string using the best parsing strategy. - - This function attempts to parse JSON in two ways: - 1. As-is (could be valid object, array, or partial JSON) - 2. With array wrapping (useful for comma-separated objects) - - It chooses the result based on structural integrity rather than string length. - - Args: - json_str: The JSON string to repair - - Returns: - The parsed JSON object with the best structure - """ - # Attempt parsing as-is - parsed_as_is = repair_json(json_str=json_str, return_objects=True, ensure_ascii=False) - - # Attempt parsing with array wrap - parsed_with_wrap = repair_json( - json_str="[" + json_str, return_objects=True, ensure_ascii=False - ) - - # If both results are strings, return the as-is result - if isinstance(parsed_as_is, str) and isinstance(parsed_with_wrap, str): - return parsed_as_is - - # If only one is a string, return the non-string result - if isinstance(parsed_as_is, str): - return parsed_with_wrap - if isinstance(parsed_with_wrap, str): - return parsed_as_is - - # Both are valid structures - choose based on structure analysis - # If parsed_with_wrap is a list with exactly one element that equals parsed_as_is, - # then the original was already valid and wrapping just added unnecessary array - if ( - isinstance(parsed_with_wrap, list) - and len(parsed_with_wrap) == 1 - and parsed_with_wrap[0] == parsed_as_is - ): - return parsed_as_is - - # If parsed_as_is is a valid structure (dict or list), prefer it - # unless parsed_with_wrap provides a more complete structure - if isinstance(parsed_as_is, (dict, list)): - # Check if the wrapped version provides multiple objects that were - # incorrectly concatenated in the original (e.g., {},{},{}) - if isinstance(parsed_with_wrap, list) and len(parsed_with_wrap) > 1: - # The original likely had multiple comma-separated objects - return parsed_with_wrap - else: - # The original was already a valid structure - return parsed_as_is - - # Default to wrapped version if we can't determine otherwise - return parsed_with_wrap diff --git a/prompt-service/src/unstract/prompt_service/utils/log.py b/prompt-service/src/unstract/prompt_service/utils/log.py deleted file mode 100644 index a6ff8236a6..0000000000 --- a/prompt-service/src/unstract/prompt_service/utils/log.py +++ /dev/null @@ -1,26 +0,0 @@ -from unstract.core.pubsub_helper import LogPublisher -from unstract.prompt_service.constants import RunLevel -from unstract.sdk1.constants import LogLevel - - -def publish_log( - log_events_id: str, - component: dict[str, str], - level: LogLevel, - state: RunLevel, - message: str, -) -> None: - """Publishes a log to the web socket. - - Args: - log_events_id (str): UUID for the connection - component (dict[str, str]): Dict of tool_id, doc_name and prompt_name to - log context - level (LogLevel): Log level, one of INFO, WARNING, DEBUG, ERROR - state (RunLevel): Run level, one of EVAL, RUN, CHALLENGE, TABLE_EXTRACTION - message (str): Message to log - """ - LogPublisher.publish( - log_events_id, - LogPublisher.log_prompt(component, level.value, state.value, message), - ) diff --git a/prompt-service/src/unstract/prompt_service/utils/metrics.py b/prompt-service/src/unstract/prompt_service/utils/metrics.py deleted file mode 100644 index b7b419d337..0000000000 --- a/prompt-service/src/unstract/prompt_service/utils/metrics.py +++ /dev/null @@ -1,8 +0,0 @@ -import datetime - - -class Metrics: - @staticmethod - def elapsed_time(start_time) -> float: - """Returns the elapsed time since the process was started.""" - return (datetime.datetime.now() - start_time).total_seconds() diff --git a/prompt-service/src/unstract/prompt_service/utils/request.py b/prompt-service/src/unstract/prompt_service/utils/request.py deleted file mode 100644 index 8a5a82d462..0000000000 --- a/prompt-service/src/unstract/prompt_service/utils/request.py +++ /dev/null @@ -1,66 +0,0 @@ -from enum import Enum -from typing import Any - -import requests as pyrequests -from flask import current_app as app -from requests.exceptions import RequestException - -from unstract.prompt_service.exceptions import APIError, BadRequest, MissingFieldError - - -class HTTPMethod(str, Enum): - GET = "GET" - POST = "POST" - DELETE = "DELETE" - PUT = "PUT" - PATCH = "PATCH" - - -def make_http_request( - verb: HTTPMethod, - url: str, - data: dict[str, Any] | None = None, - headers: dict[str, Any] | None = None, - params: dict[str, Any] | None = None, -) -> str: - """Generic helper function to help make a HTTP request.""" - try: - if verb == HTTPMethod.GET: - response = pyrequests.get(url, params=params, headers=headers) - elif verb == HTTPMethod.POST: - response = pyrequests.post(url, json=data, params=params, headers=headers) - elif verb == HTTPMethod.DELETE: - response = pyrequests.delete(url, params=params, headers=headers) - else: - raise ValueError("Invalid HTTP verb. Supported verbs: GET, POST, DELETE") - - response.raise_for_status() - return_val: str = ( - response.json() - if response.headers.get("content-type") == "application/json" - else response.text - ) - return return_val - except RequestException as e: - app.logger.error(f"HTTP request error: {e}") - # Extract status code from requests exception if available - status_code = None - if getattr(e, "response", None) is not None: - status_code = getattr(e.response, "status_code", None) - raise APIError( - f"HTTP {verb.value} request to {url} failed: {e!s}", - code=status_code or 500, - ) from e - except Exception as e: - app.logger.error(f"An unexpected error occurred: {e}") - raise e - - -def validate_request_payload(payload, required_fields): - if not payload: - raise BadRequest() - - # Validate required fields - missing_fields = [field for field in required_fields if field not in payload] - if missing_fields: - raise MissingFieldError(missing_fields) diff --git a/prompt-service/uv.lock b/prompt-service/uv.lock deleted file mode 100644 index b3a8cf1a68..0000000000 --- a/prompt-service/uv.lock +++ /dev/null @@ -1,3037 +0,0 @@ -version = 1 -revision = 2 -requires-python = "==3.12.*" - -[[package]] -name = "adlfs" -version = "2024.7.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "aiohttp" }, - { name = "azure-core" }, - { name = "azure-datalake-store" }, - { name = "azure-identity" }, - { name = "azure-storage-blob" }, - { name = "fsspec" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b4/1e/6d5146676044247af566fa5843b335b1a647e6446070cec9c8b61c31b369/adlfs-2024.7.0.tar.gz", hash = "sha256:106995b91f0eb5e775bcd5957d180d9a14faef3271a063b1f65c66fd5ab05ddf", size = 48588, upload-time = "2024-07-22T12:10:33.849Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6f/51/a71c457bd0bc8af3e522b6999ff300852c7c446e384fd9904b0794f875df/adlfs-2024.7.0-py3-none-any.whl", hash = "sha256:2005c8e124fda3948f2a6abb2dbebb2c936d2d821acaca6afd61932edfa9bc07", size = 41349, upload-time = "2024-07-22T12:10:32.226Z" }, -] - -[[package]] -name = "aiobotocore" -version = "2.13.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "aiohttp" }, - { name = "aioitertools" }, - { name = "botocore" }, - { name = "wrapt" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cd/d2/d7e46bcc4c0b5b8e751092824d6ca9af5928adae0f864336e43c7f7a436a/aiobotocore-2.13.1.tar.gz", hash = "sha256:134f9606c2f91abde38cbc61c3241113e26ff244633e0c31abb7e09da3581c9b", size = 104475, upload-time = "2024-06-24T18:30:36.509Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/30/07/42f884c1600169e4267575cdd261c75dea31782d8fd877bbea358d559416/aiobotocore-2.13.1-py3-none-any.whl", hash = "sha256:1bef121b99841ee3cc788e4ed97c332ba32353b1f00e886d1beb3aae95520858", size = 76864, upload-time = "2024-06-24T18:30:33.379Z" }, -] - -[package.optional-dependencies] -boto3 = [ - { name = "boto3" }, -] - -[[package]] -name = "aiohappyeyeballs" -version = "2.6.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload-time = "2025-03-12T01:42:48.764Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" }, -] - -[[package]] -name = "aiohttp" -version = "3.13.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "aiohappyeyeballs" }, - { name = "aiosignal" }, - { name = "attrs" }, - { name = "frozenlist" }, - { name = "multidict" }, - { name = "propcache" }, - { name = "yarl" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/50/42/32cf8e7704ceb4481406eb87161349abb46a57fee3f008ba9cb610968646/aiohttp-3.13.3.tar.gz", hash = "sha256:a949eee43d3782f2daae4f4a2819b2cb9b0c5d3b7f7a927067cc84dafdbb9f88", size = 7844556, upload-time = "2026-01-03T17:33:05.204Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/be/4fc11f202955a69e0db803a12a062b8379c970c7c84f4882b6da17337cc1/aiohttp-3.13.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b903a4dfee7d347e2d87697d0713be59e0b87925be030c9178c5faa58ea58d5c", size = 739732, upload-time = "2026-01-03T17:30:14.23Z" }, - { url = "https://files.pythonhosted.org/packages/97/2c/621d5b851f94fa0bb7430d6089b3aa970a9d9b75196bc93bb624b0db237a/aiohttp-3.13.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a45530014d7a1e09f4a55f4f43097ba0fd155089372e105e4bff4ca76cb1b168", size = 494293, upload-time = "2026-01-03T17:30:15.96Z" }, - { url = "https://files.pythonhosted.org/packages/5d/43/4be01406b78e1be8320bb8316dc9c42dbab553d281c40364e0f862d5661c/aiohttp-3.13.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:27234ef6d85c914f9efeb77ff616dbf4ad2380be0cda40b4db086ffc7ddd1b7d", size = 493533, upload-time = "2026-01-03T17:30:17.431Z" }, - { url = "https://files.pythonhosted.org/packages/8d/a8/5a35dc56a06a2c90d4742cbf35294396907027f80eea696637945a106f25/aiohttp-3.13.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d32764c6c9aafb7fb55366a224756387cd50bfa720f32b88e0e6fa45b27dcf29", size = 1737839, upload-time = "2026-01-03T17:30:19.422Z" }, - { url = "https://files.pythonhosted.org/packages/bf/62/4b9eeb331da56530bf2e198a297e5303e1c1ebdceeb00fe9b568a65c5a0c/aiohttp-3.13.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b1a6102b4d3ebc07dad44fbf07b45bb600300f15b552ddf1851b5390202ea2e3", size = 1703932, upload-time = "2026-01-03T17:30:21.756Z" }, - { url = "https://files.pythonhosted.org/packages/7c/f6/af16887b5d419e6a367095994c0b1332d154f647e7dc2bd50e61876e8e3d/aiohttp-3.13.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c014c7ea7fb775dd015b2d3137378b7be0249a448a1612268b5a90c2d81de04d", size = 1771906, upload-time = "2026-01-03T17:30:23.932Z" }, - { url = "https://files.pythonhosted.org/packages/ce/83/397c634b1bcc24292fa1e0c7822800f9f6569e32934bdeef09dae7992dfb/aiohttp-3.13.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2b8d8ddba8f95ba17582226f80e2de99c7a7948e66490ef8d947e272a93e9463", size = 1871020, upload-time = "2026-01-03T17:30:26Z" }, - { url = "https://files.pythonhosted.org/packages/86/f6/a62cbbf13f0ac80a70f71b1672feba90fdb21fd7abd8dbf25c0105fb6fa3/aiohttp-3.13.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ae8dd55c8e6c4257eae3a20fd2c8f41edaea5992ed67156642493b8daf3cecc", size = 1755181, upload-time = "2026-01-03T17:30:27.554Z" }, - { url = "https://files.pythonhosted.org/packages/0a/87/20a35ad487efdd3fba93d5843efdfaa62d2f1479eaafa7453398a44faf13/aiohttp-3.13.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:01ad2529d4b5035578f5081606a465f3b814c542882804e2e8cda61adf5c71bf", size = 1561794, upload-time = "2026-01-03T17:30:29.254Z" }, - { url = "https://files.pythonhosted.org/packages/de/95/8fd69a66682012f6716e1bc09ef8a1a2a91922c5725cb904689f112309c4/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bb4f7475e359992b580559e008c598091c45b5088f28614e855e42d39c2f1033", size = 1697900, upload-time = "2026-01-03T17:30:31.033Z" }, - { url = "https://files.pythonhosted.org/packages/e5/66/7b94b3b5ba70e955ff597672dad1691333080e37f50280178967aff68657/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c19b90316ad3b24c69cd78d5c9b4f3aa4497643685901185b65166293d36a00f", size = 1728239, upload-time = "2026-01-03T17:30:32.703Z" }, - { url = "https://files.pythonhosted.org/packages/47/71/6f72f77f9f7d74719692ab65a2a0252584bf8d5f301e2ecb4c0da734530a/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:96d604498a7c782cb15a51c406acaea70d8c027ee6b90c569baa6e7b93073679", size = 1740527, upload-time = "2026-01-03T17:30:34.695Z" }, - { url = "https://files.pythonhosted.org/packages/fa/b4/75ec16cbbd5c01bdaf4a05b19e103e78d7ce1ef7c80867eb0ace42ff4488/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:084911a532763e9d3dd95adf78a78f4096cd5f58cdc18e6fdbc1b58417a45423", size = 1554489, upload-time = "2026-01-03T17:30:36.864Z" }, - { url = "https://files.pythonhosted.org/packages/52/8f/bc518c0eea29f8406dcf7ed1f96c9b48e3bc3995a96159b3fc11f9e08321/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7a4a94eb787e606d0a09404b9c38c113d3b099d508021faa615d70a0131907ce", size = 1767852, upload-time = "2026-01-03T17:30:39.433Z" }, - { url = "https://files.pythonhosted.org/packages/9d/f2/a07a75173124f31f11ea6f863dc44e6f09afe2bca45dd4e64979490deab1/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:87797e645d9d8e222e04160ee32aa06bc5c163e8499f24db719e7852ec23093a", size = 1722379, upload-time = "2026-01-03T17:30:41.081Z" }, - { url = "https://files.pythonhosted.org/packages/3c/4a/1a3fee7c21350cac78e5c5cef711bac1b94feca07399f3d406972e2d8fcd/aiohttp-3.13.3-cp312-cp312-win32.whl", hash = "sha256:b04be762396457bef43f3597c991e192ee7da460a4953d7e647ee4b1c28e7046", size = 428253, upload-time = "2026-01-03T17:30:42.644Z" }, - { url = "https://files.pythonhosted.org/packages/d9/b7/76175c7cb4eb73d91ad63c34e29fc4f77c9386bba4a65b53ba8e05ee3c39/aiohttp-3.13.3-cp312-cp312-win_amd64.whl", hash = "sha256:e3531d63d3bdfa7e3ac5e9b27b2dd7ec9df3206a98e0b3445fa906f233264c57", size = 455407, upload-time = "2026-01-03T17:30:44.195Z" }, -] - -[[package]] -name = "aioitertools" -version = "0.12.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/06/de/38491a84ab323b47c7f86e94d2830e748780525f7a10c8600b67ead7e9ea/aioitertools-0.12.0.tar.gz", hash = "sha256:c2a9055b4fbb7705f561b9d86053e8af5d10cc845d22c32008c43490b2d8dd6b", size = 19369, upload-time = "2024-09-02T03:33:40.349Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/85/13/58b70a580de00893223d61de8fea167877a3aed97d4a5e1405c9159ef925/aioitertools-0.12.0-py3-none-any.whl", hash = "sha256:fc1f5fac3d737354de8831cbba3eb04f79dd649d8f3afb4c5b114925e662a796", size = 24345, upload-time = "2024-09-02T03:34:59.454Z" }, -] - -[[package]] -name = "aiosignal" -version = "1.4.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "frozenlist" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } -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.21.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/13/7d/8bca2bf9a247c2c5dfeec1d7a5f40db6518f88d314b8bca9da29670d2671/aiosqlite-0.21.0.tar.gz", hash = "sha256:131bb8056daa3bc875608c631c678cda73922a2d4ba8aec373b19f18c17e7aa3", size = 13454, upload-time = "2025-02-03T07:30:16.235Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f5/10/6c25ed6de94c49f88a91fa5018cb4c0f3625f31d5be9f771ebe5cc7cd506/aiosqlite-0.21.0-py3-none-any.whl", hash = "sha256:2549cf4057f95f53dcba16f2b64e8e2791d7e1adedb13197dd8ed77bb226d7d0", size = 15792, upload-time = "2025-02-03T07:30:13.6Z" }, -] - -[[package]] -name = "amqp" -version = "5.3.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "vine" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/79/fc/ec94a357dfc6683d8c86f8b4cfa5416a4c36b28052ec8260c77aca96a443/amqp-5.3.1.tar.gz", hash = "sha256:cddc00c725449522023bad949f70fff7b48f0b1ade74d170a6f10ab044739432", size = 129013, upload-time = "2024-11-12T19:55:44.051Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/26/99/fc813cd978842c26c82534010ea849eee9ab3a13ea2b74e95cb9c99e747b/amqp-5.3.1-py3-none-any.whl", hash = "sha256:43b3319e1b4e7d1251833a93d672b4af1e40f3d632d479b98661a95f117880a2", size = 50944, upload-time = "2024-11-12T19:55:41.782Z" }, -] - -[[package]] -name = "annotated-types" -version = "0.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } -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 = "anyio" -version = "4.11.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "idna" }, - { name = "sniffio" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c6/78/7d432127c41b50bccba979505f272c16cbcadcc33645d5fa3a738110ae75/anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4", size = 219094, upload-time = "2025-09-23T09:19:12.58Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc", size = 109097, upload-time = "2025-09-23T09:19:10.601Z" }, -] - -[[package]] -name = "asyncpg" -version = "0.30.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2f/4c/7c991e080e106d854809030d8584e15b2e996e26f16aee6d757e387bc17d/asyncpg-0.30.0.tar.gz", hash = "sha256:c551e9928ab6707602f44811817f82ba3c446e018bfe1d3abecc8ba5f3eac851", size = 957746, upload-time = "2024-10-20T00:30:41.127Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/64/9d3e887bb7b01535fdbc45fbd5f0a8447539833b97ee69ecdbb7a79d0cb4/asyncpg-0.30.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c902a60b52e506d38d7e80e0dd5399f657220f24635fee368117b8b5fce1142e", size = 673162, upload-time = "2024-10-20T00:29:41.88Z" }, - { url = "https://files.pythonhosted.org/packages/6e/eb/8b236663f06984f212a087b3e849731f917ab80f84450e943900e8ca4052/asyncpg-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aca1548e43bbb9f0f627a04666fedaca23db0a31a84136ad1f868cb15deb6e3a", size = 637025, upload-time = "2024-10-20T00:29:43.352Z" }, - { url = "https://files.pythonhosted.org/packages/cc/57/2dc240bb263d58786cfaa60920779af6e8d32da63ab9ffc09f8312bd7a14/asyncpg-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c2a2ef565400234a633da0eafdce27e843836256d40705d83ab7ec42074efb3", size = 3496243, upload-time = "2024-10-20T00:29:44.922Z" }, - { url = "https://files.pythonhosted.org/packages/f4/40/0ae9d061d278b10713ea9021ef6b703ec44698fe32178715a501ac696c6b/asyncpg-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1292b84ee06ac8a2ad8e51c7475aa309245874b61333d97411aab835c4a2f737", size = 3575059, upload-time = "2024-10-20T00:29:46.891Z" }, - { url = "https://files.pythonhosted.org/packages/c3/75/d6b895a35a2c6506952247640178e5f768eeb28b2e20299b6a6f1d743ba0/asyncpg-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0f5712350388d0cd0615caec629ad53c81e506b1abaaf8d14c93f54b35e3595a", size = 3473596, upload-time = "2024-10-20T00:29:49.201Z" }, - { url = "https://files.pythonhosted.org/packages/c8/e7/3693392d3e168ab0aebb2d361431375bd22ffc7b4a586a0fc060d519fae7/asyncpg-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:db9891e2d76e6f425746c5d2da01921e9a16b5a71a1c905b13f30e12a257c4af", size = 3641632, upload-time = "2024-10-20T00:29:50.768Z" }, - { url = "https://files.pythonhosted.org/packages/32/ea/15670cea95745bba3f0352341db55f506a820b21c619ee66b7d12ea7867d/asyncpg-0.30.0-cp312-cp312-win32.whl", hash = "sha256:68d71a1be3d83d0570049cd1654a9bdfe506e794ecc98ad0873304a9f35e411e", size = 560186, upload-time = "2024-10-20T00:29:52.394Z" }, - { url = "https://files.pythonhosted.org/packages/7e/6b/fe1fad5cee79ca5f5c27aed7bd95baee529c1bf8a387435c8ba4fe53d5c1/asyncpg-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:9a0292c6af5c500523949155ec17b7fe01a00ace33b68a476d6b5059f9630305", size = 621064, upload-time = "2024-10-20T00:29:53.757Z" }, -] - -[[package]] -name = "attrs" -version = "25.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, -] - -[[package]] -name = "authlib" -version = "1.6.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cryptography" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ce/bb/73a1f1c64ee527877f64122422dafe5b87a846ccf4ac933fe21bcbb8fee8/authlib-1.6.4.tar.gz", hash = "sha256:104b0442a43061dc8bc23b133d1d06a2b0a9c2e3e33f34c4338929e816287649", size = 164046, upload-time = "2025-09-17T09:59:23.897Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/aa/91355b5f539caf1b94f0e66ff1e4ee39373b757fce08204981f7829ede51/authlib-1.6.4-py2.py3-none-any.whl", hash = "sha256:39313d2a2caac3ecf6d8f95fbebdfd30ae6ea6ae6a6db794d976405fdd9aa796", size = 243076, upload-time = "2025-09-17T09:59:22.259Z" }, -] - -[[package]] -name = "azure-core" -version = "1.35.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "requests" }, - { name = "six" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/15/6b/2653adc0f33adba8f11b1903701e6b1c10d34ce5d8e25dfa13a422f832b0/azure_core-1.35.1.tar.gz", hash = "sha256:435d05d6df0fff2f73fb3c15493bb4721ede14203f1ff1382aa6b6b2bdd7e562", size = 345290, upload-time = "2025-09-11T22:58:04.481Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/27/52/805980aa1ba18282077c484dba634ef0ede1e84eec8be9c92b2e162d0ed6/azure_core-1.35.1-py3-none-any.whl", hash = "sha256:12da0c9e08e48e198f9158b56ddbe33b421477e1dc98c2e1c8f9e254d92c468b", size = 211800, upload-time = "2025-09-11T22:58:06.281Z" }, -] - -[[package]] -name = "azure-datalake-store" -version = "0.0.53" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cffi" }, - { name = "msal" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/22/ff/61369d06422b5ac48067215ff404841342651b14a89b46c8d8e1507c8f17/azure-datalake-store-0.0.53.tar.gz", hash = "sha256:05b6de62ee3f2a0a6e6941e6933b792b800c3e7f6ffce2fc324bc19875757393", size = 71430, upload-time = "2023-05-10T21:17:05.665Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/88/2a/75f56b14f115189155cf12e46b366ad1fe3357af5a1a7c09f7446662d617/azure_datalake_store-0.0.53-py2.py3-none-any.whl", hash = "sha256:a30c902a6e360aa47d7f69f086b426729784e71c536f330b691647a51dc42b2b", size = 55308, upload-time = "2023-05-10T21:17:02.629Z" }, -] - -[[package]] -name = "azure-identity" -version = "1.25.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "azure-core" }, - { name = "cryptography" }, - { name = "msal" }, - { name = "msal-extensions" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/4e/9e/4c9682a286c3c89e437579bd9f64f311020e5125c1321fd3a653166b5716/azure_identity-1.25.0.tar.gz", hash = "sha256:4177df34d684cddc026e6cf684e1abb57767aa9d84e7f2129b080ec45eee7733", size = 278507, upload-time = "2025-09-12T01:30:04.418Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/75/54/81683b6756676a22e037b209695b08008258e603f7e47c56834029c5922a/azure_identity-1.25.0-py3-none-any.whl", hash = "sha256:becaec086bbdf8d1a6aa4fb080c2772a0f824a97d50c29637ec8cc4933f1e82d", size = 190861, upload-time = "2025-09-12T01:30:06.474Z" }, -] - -[[package]] -name = "azure-storage-blob" -version = "12.26.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "azure-core" }, - { name = "cryptography" }, - { name = "isodate" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/96/95/3e3414491ce45025a1cde107b6ae72bf72049e6021597c201cd6a3029b9a/azure_storage_blob-12.26.0.tar.gz", hash = "sha256:5dd7d7824224f7de00bfeb032753601c982655173061e242f13be6e26d78d71f", size = 583332, upload-time = "2025-07-16T21:34:07.644Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5b/64/63dbfdd83b31200ac58820a7951ddfdeed1fbee9285b0f3eae12d1357155/azure_storage_blob-12.26.0-py3-none-any.whl", hash = "sha256:8c5631b8b22b4f53ec5fff2f3bededf34cfef111e2af613ad42c9e6de00a77fe", size = 412907, upload-time = "2025-07-16T21:34:09.367Z" }, -] - -[[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 = "banks" -version = "2.2.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "deprecated" }, - { name = "griffe" }, - { name = "jinja2" }, - { name = "platformdirs" }, - { name = "pydantic" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7d/f8/25ef24814f77f3fd7f0fd3bd1ef3749e38a9dbd23502fbb53034de49900c/banks-2.2.0.tar.gz", hash = "sha256:d1446280ce6e00301e3e952dd754fd8cee23ff277d29ed160994a84d0d7ffe62", size = 179052, upload-time = "2025-07-18T16:28:26.892Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/d6/f9168956276934162ec8d48232f9920f2985ee45aa7602e3c6b4bc203613/banks-2.2.0-py3-none-any.whl", hash = "sha256:963cd5c85a587b122abde4f4064078def35c50c688c1b9d36f43c92503854e7d", size = 29244, upload-time = "2025-07-18T16:28:27.835Z" }, -] - -[[package]] -name = "beautifulsoup4" -version = "4.14.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "soupsieve" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/77/e9/df2358efd7659577435e2177bfa69cba6c33216681af51a707193dec162a/beautifulsoup4-4.14.2.tar.gz", hash = "sha256:2a98ab9f944a11acee9cc848508ec28d9228abfd522ef0fad6a02a72e0ded69e", size = 625822, upload-time = "2025-09-29T10:05:42.613Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl", hash = "sha256:5ef6fa3a8cbece8488d66985560f97ed091e22bbc4e9c2338508a9d5de6d4515", size = 106392, upload-time = "2025-09-29T10:05:43.771Z" }, -] - -[[package]] -name = "blinker" -version = "1.9.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460, upload-time = "2024-11-08T17:25:47.436Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" }, -] - -[[package]] -name = "boto3" -version = "1.34.131" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "botocore" }, - { name = "jmespath" }, - { name = "s3transfer" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/1f/d9/35978a20f6f9a585ff83afb384faf71526a1b25c4131755b1cdb6687b1d9/boto3-1.34.131.tar.gz", hash = "sha256:dab8f72a6c4e62b4fd70da09e08a6b2a65ea2115b27dd63737142005776ef216", size = 108719, upload-time = "2024-06-20T19:34:56.629Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3e/ce/f5e3fdab6012f5fa4a8f5e97e86cc42549729382a98faffbc1785f85e89f/boto3-1.34.131-py3-none-any.whl", hash = "sha256:05e388cb937e82be70bfd7eb0c84cf8011ff35cf582a593873ac21675268683b", size = 139172, upload-time = "2024-06-20T19:34:44.219Z" }, -] - -[[package]] -name = "botocore" -version = "1.34.131" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jmespath" }, - { name = "python-dateutil" }, - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/41/40/74bda5977985383b8ed403dced9d76ad5e1146db7b6c32089726b3130c8b/botocore-1.34.131.tar.gz", hash = "sha256:502ddafe1d627fcf1e4c007c86454e5dd011dba7c58bd8e8a5368a79f3e387dc", size = 12544482, upload-time = "2024-06-20T19:34:04.853Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/46/1a/01785fad12a9b1dbeffebd97cd226ea5923114057c64a610dd4eb8a28c7b/botocore-1.34.131-py3-none-any.whl", hash = "sha256:13b011d7b206ce00727dcee26548fa3b550db9046d5a0e90ac25a6e6c8fde6ef", size = 12332729, upload-time = "2024-06-20T19:33:51.589Z" }, -] - -[[package]] -name = "cachetools" -version = "6.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9d/61/e4fad8155db4a04bfb4734c7c8ff0882f078f24294d42798b3568eb63bff/cachetools-6.2.0.tar.gz", hash = "sha256:38b328c0889450f05f5e120f56ab68c8abaf424e1275522b138ffc93253f7e32", size = 30988, upload-time = "2025-08-25T18:57:30.924Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/56/3124f61d37a7a4e7cc96afc5492c78ba0cb551151e530b54669ddd1436ef/cachetools-6.2.0-py3-none-any.whl", hash = "sha256:1c76a8960c0041fcc21097e357f882197c79da0dbff766e7317890a65d7d8ba6", size = 11276, upload-time = "2025-08-25T18:57:29.684Z" }, -] - -[[package]] -name = "certifi" -version = "2025.8.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" }, -] - -[[package]] -name = "cffi" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pycparser", marker = "implementation_name != 'PyPy'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, - { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, - { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, - { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, - { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, - { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, - { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, - { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, - { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, - { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, - { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, - { 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.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", size = 122371, upload-time = "2025-08-09T07:57:28.46Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/5e/14c94999e418d9b87682734589404a25854d5f5d0408df68bc15b6ff54bb/charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1", size = 205655, upload-time = "2025-08-09T07:56:08.475Z" }, - { url = "https://files.pythonhosted.org/packages/7d/a8/c6ec5d389672521f644505a257f50544c074cf5fc292d5390331cd6fc9c3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884", size = 146223, upload-time = "2025-08-09T07:56:09.708Z" }, - { url = "https://files.pythonhosted.org/packages/fc/eb/a2ffb08547f4e1e5415fb69eb7db25932c52a52bed371429648db4d84fb1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018", size = 159366, upload-time = "2025-08-09T07:56:11.326Z" }, - { url = "https://files.pythonhosted.org/packages/82/10/0fd19f20c624b278dddaf83b8464dcddc2456cb4b02bb902a6da126b87a1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392", size = 157104, upload-time = "2025-08-09T07:56:13.014Z" }, - { url = "https://files.pythonhosted.org/packages/16/ab/0233c3231af734f5dfcf0844aa9582d5a1466c985bbed6cedab85af9bfe3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f", size = 151830, upload-time = "2025-08-09T07:56:14.428Z" }, - { url = "https://files.pythonhosted.org/packages/ae/02/e29e22b4e02839a0e4a06557b1999d0a47db3567e82989b5bb21f3fbbd9f/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154", size = 148854, upload-time = "2025-08-09T07:56:16.051Z" }, - { url = "https://files.pythonhosted.org/packages/05/6b/e2539a0a4be302b481e8cafb5af8792da8093b486885a1ae4d15d452bcec/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491", size = 160670, upload-time = "2025-08-09T07:56:17.314Z" }, - { url = "https://files.pythonhosted.org/packages/31/e7/883ee5676a2ef217a40ce0bffcc3d0dfbf9e64cbcfbdf822c52981c3304b/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93", size = 158501, upload-time = "2025-08-09T07:56:18.641Z" }, - { url = "https://files.pythonhosted.org/packages/c1/35/6525b21aa0db614cf8b5792d232021dca3df7f90a1944db934efa5d20bb1/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f", size = 153173, upload-time = "2025-08-09T07:56:20.289Z" }, - { url = "https://files.pythonhosted.org/packages/50/ee/f4704bad8201de513fdc8aac1cabc87e38c5818c93857140e06e772b5892/charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37", size = 99822, upload-time = "2025-08-09T07:56:21.551Z" }, - { url = "https://files.pythonhosted.org/packages/39/f5/3b3836ca6064d0992c58c7561c6b6eee1b3892e9665d650c803bd5614522/charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc", size = 107543, upload-time = "2025-08-09T07:56:23.115Z" }, - { url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" }, -] - -[[package]] -name = "click" -version = "8.1.8" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" }, -] - -[[package]] -name = "colorama" -version = "0.4.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, -] - -[[package]] -name = "cryptography" -version = "46.0.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a9/62/e3664e6ffd7743e1694b244dde70b43a394f6f7fbcacf7014a8ff5197c73/cryptography-46.0.1.tar.gz", hash = "sha256:ed570874e88f213437f5cf758f9ef26cbfc3f336d889b1e592ee11283bb8d1c7", size = 749198, upload-time = "2025-09-17T00:10:35.797Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4c/8c/44ee01267ec01e26e43ebfdae3f120ec2312aa72fa4c0507ebe41a26739f/cryptography-46.0.1-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:1cd6d50c1a8b79af1a6f703709d8973845f677c8e97b1268f5ff323d38ce8475", size = 7285044, upload-time = "2025-09-17T00:08:36.807Z" }, - { url = "https://files.pythonhosted.org/packages/22/59/9ae689a25047e0601adfcb159ec4f83c0b4149fdb5c3030cc94cd218141d/cryptography-46.0.1-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0ff483716be32690c14636e54a1f6e2e1b7bf8e22ca50b989f88fa1b2d287080", size = 4308182, upload-time = "2025-09-17T00:08:39.388Z" }, - { url = "https://files.pythonhosted.org/packages/c4/ee/ca6cc9df7118f2fcd142c76b1da0f14340d77518c05b1ebfbbabca6b9e7d/cryptography-46.0.1-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9873bf7c1f2a6330bdfe8621e7ce64b725784f9f0c3a6a55c3047af5849f920e", size = 4572393, upload-time = "2025-09-17T00:08:41.663Z" }, - { url = "https://files.pythonhosted.org/packages/7f/a3/0f5296f63815d8e985922b05c31f77ce44787b3127a67c0b7f70f115c45f/cryptography-46.0.1-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0dfb7c88d4462a0cfdd0d87a3c245a7bc3feb59de101f6ff88194f740f72eda6", size = 4308400, upload-time = "2025-09-17T00:08:43.559Z" }, - { url = "https://files.pythonhosted.org/packages/5d/8c/74fcda3e4e01be1d32775d5b4dd841acaac3c1b8fa4d0774c7ac8d52463d/cryptography-46.0.1-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e22801b61613ebdebf7deb18b507919e107547a1d39a3b57f5f855032dd7cfb8", size = 4015786, upload-time = "2025-09-17T00:08:45.758Z" }, - { url = "https://files.pythonhosted.org/packages/dc/b8/85d23287baeef273b0834481a3dd55bbed3a53587e3b8d9f0898235b8f91/cryptography-46.0.1-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:757af4f6341ce7a1e47c326ca2a81f41d236070217e5fbbad61bbfe299d55d28", size = 4982606, upload-time = "2025-09-17T00:08:47.602Z" }, - { url = "https://files.pythonhosted.org/packages/e5/d3/de61ad5b52433b389afca0bc70f02a7a1f074651221f599ce368da0fe437/cryptography-46.0.1-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f7a24ea78de345cfa7f6a8d3bde8b242c7fac27f2bd78fa23474ca38dfaeeab9", size = 4604234, upload-time = "2025-09-17T00:08:49.879Z" }, - { url = "https://files.pythonhosted.org/packages/dc/1f/dbd4d6570d84748439237a7478d124ee0134bf166ad129267b7ed8ea6d22/cryptography-46.0.1-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9e8776dac9e660c22241b6587fae51a67b4b0147daa4d176b172c3ff768ad736", size = 4307669, upload-time = "2025-09-17T00:08:52.321Z" }, - { url = "https://files.pythonhosted.org/packages/ec/fd/ca0a14ce7f0bfe92fa727aacaf2217eb25eb7e4ed513b14d8e03b26e63ed/cryptography-46.0.1-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9f40642a140c0c8649987027867242b801486865277cbabc8c6059ddef16dc8b", size = 4947579, upload-time = "2025-09-17T00:08:54.697Z" }, - { url = "https://files.pythonhosted.org/packages/89/6b/09c30543bb93401f6f88fce556b3bdbb21e55ae14912c04b7bf355f5f96c/cryptography-46.0.1-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:449ef2b321bec7d97ef2c944173275ebdab78f3abdd005400cc409e27cd159ab", size = 4603669, upload-time = "2025-09-17T00:08:57.16Z" }, - { url = "https://files.pythonhosted.org/packages/23/9a/38cb01cb09ce0adceda9fc627c9cf98eb890fc8d50cacbe79b011df20f8a/cryptography-46.0.1-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2dd339ba3345b908fa3141ddba4025568fa6fd398eabce3ef72a29ac2d73ad75", size = 4435828, upload-time = "2025-09-17T00:08:59.606Z" }, - { url = "https://files.pythonhosted.org/packages/0f/53/435b5c36a78d06ae0bef96d666209b0ecd8f8181bfe4dda46536705df59e/cryptography-46.0.1-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7411c910fb2a412053cf33cfad0153ee20d27e256c6c3f14d7d7d1d9fec59fd5", size = 4709553, upload-time = "2025-09-17T00:09:01.832Z" }, - { url = "https://files.pythonhosted.org/packages/f5/c4/0da6e55595d9b9cd3b6eb5dc22f3a07ded7f116a3ea72629cab595abb804/cryptography-46.0.1-cp311-abi3-win32.whl", hash = "sha256:cbb8e769d4cac884bb28e3ff620ef1001b75588a5c83c9c9f1fdc9afbe7f29b0", size = 3058327, upload-time = "2025-09-17T00:09:03.726Z" }, - { url = "https://files.pythonhosted.org/packages/95/0f/cd29a35e0d6e78a0ee61793564c8cff0929c38391cb0de27627bdc7525aa/cryptography-46.0.1-cp311-abi3-win_amd64.whl", hash = "sha256:92e8cfe8bd7dd86eac0a677499894862cd5cc2fd74de917daa881d00871ac8e7", size = 3523893, upload-time = "2025-09-17T00:09:06.272Z" }, - { url = "https://files.pythonhosted.org/packages/f2/dd/eea390f3e78432bc3d2f53952375f8b37cb4d37783e626faa6a51e751719/cryptography-46.0.1-cp311-abi3-win_arm64.whl", hash = "sha256:db5597a4c7353b2e5fb05a8e6cb74b56a4658a2b7bf3cb6b1821ae7e7fd6eaa0", size = 2932145, upload-time = "2025-09-17T00:09:08.568Z" }, - { url = "https://files.pythonhosted.org/packages/98/e5/fbd632385542a3311915976f88e0dfcf09e62a3fc0aff86fb6762162a24d/cryptography-46.0.1-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:d84c40bdb8674c29fa192373498b6cb1e84f882889d21a471b45d1f868d8d44b", size = 7255677, upload-time = "2025-09-17T00:09:42.407Z" }, - { url = "https://files.pythonhosted.org/packages/56/3e/13ce6eab9ad6eba1b15a7bd476f005a4c1b3f299f4c2f32b22408b0edccf/cryptography-46.0.1-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9ed64e5083fa806709e74fc5ea067dfef9090e5b7a2320a49be3c9df3583a2d8", size = 4301110, upload-time = "2025-09-17T00:09:45.614Z" }, - { url = "https://files.pythonhosted.org/packages/a2/67/65dc233c1ddd688073cf7b136b06ff4b84bf517ba5529607c9d79720fc67/cryptography-46.0.1-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:341fb7a26bc9d6093c1b124b9f13acc283d2d51da440b98b55ab3f79f2522ead", size = 4562369, upload-time = "2025-09-17T00:09:47.601Z" }, - { url = "https://files.pythonhosted.org/packages/17/db/d64ae4c6f4e98c3dac5bf35dd4d103f4c7c345703e43560113e5e8e31b2b/cryptography-46.0.1-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6ef1488967e729948d424d09c94753d0167ce59afba8d0f6c07a22b629c557b2", size = 4302126, upload-time = "2025-09-17T00:09:49.335Z" }, - { url = "https://files.pythonhosted.org/packages/3d/19/5f1eea17d4805ebdc2e685b7b02800c4f63f3dd46cfa8d4c18373fea46c8/cryptography-46.0.1-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7823bc7cdf0b747ecfb096d004cc41573c2f5c7e3a29861603a2871b43d3ef32", size = 4009431, upload-time = "2025-09-17T00:09:51.239Z" }, - { url = "https://files.pythonhosted.org/packages/81/b5/229ba6088fe7abccbfe4c5edb96c7a5ad547fac5fdd0d40aa6ea540b2985/cryptography-46.0.1-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:f736ab8036796f5a119ff8211deda416f8c15ce03776db704a7a4e17381cb2ef", size = 4980739, upload-time = "2025-09-17T00:09:54.181Z" }, - { url = "https://files.pythonhosted.org/packages/3a/9c/50aa38907b201e74bc43c572f9603fa82b58e831bd13c245613a23cff736/cryptography-46.0.1-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:e46710a240a41d594953012213ea8ca398cd2448fbc5d0f1be8160b5511104a0", size = 4592289, upload-time = "2025-09-17T00:09:56.731Z" }, - { url = "https://files.pythonhosted.org/packages/5a/33/229858f8a5bb22f82468bb285e9f4c44a31978d5f5830bb4ea1cf8a4e454/cryptography-46.0.1-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:84ef1f145de5aee82ea2447224dc23f065ff4cc5791bb3b506615957a6ba8128", size = 4301815, upload-time = "2025-09-17T00:09:58.548Z" }, - { url = "https://files.pythonhosted.org/packages/52/cb/b76b2c87fbd6ed4a231884bea3ce073406ba8e2dae9defad910d33cbf408/cryptography-46.0.1-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9394c7d5a7565ac5f7d9ba38b2617448eba384d7b107b262d63890079fad77ca", size = 4943251, upload-time = "2025-09-17T00:10:00.475Z" }, - { url = "https://files.pythonhosted.org/packages/94/0f/f66125ecf88e4cb5b8017ff43f3a87ede2d064cb54a1c5893f9da9d65093/cryptography-46.0.1-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:ed957044e368ed295257ae3d212b95456bd9756df490e1ac4538857f67531fcc", size = 4591247, upload-time = "2025-09-17T00:10:02.874Z" }, - { url = "https://files.pythonhosted.org/packages/f6/22/9f3134ae436b63b463cfdf0ff506a0570da6873adb4bf8c19b8a5b4bac64/cryptography-46.0.1-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f7de12fa0eee6234de9a9ce0ffcfa6ce97361db7a50b09b65c63ac58e5f22fc7", size = 4428534, upload-time = "2025-09-17T00:10:04.994Z" }, - { url = "https://files.pythonhosted.org/packages/89/39/e6042bcb2638650b0005c752c38ea830cbfbcbb1830e4d64d530000aa8dc/cryptography-46.0.1-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7fab1187b6c6b2f11a326f33b036f7168f5b996aedd0c059f9738915e4e8f53a", size = 4699541, upload-time = "2025-09-17T00:10:06.925Z" }, - { url = "https://files.pythonhosted.org/packages/68/46/753d457492d15458c7b5a653fc9a84a1c9c7a83af6ebdc94c3fc373ca6e8/cryptography-46.0.1-cp38-abi3-win32.whl", hash = "sha256:45f790934ac1018adeba46a0f7289b2b8fe76ba774a88c7f1922213a56c98bc1", size = 3043779, upload-time = "2025-09-17T00:10:08.951Z" }, - { url = "https://files.pythonhosted.org/packages/2f/50/b6f3b540c2f6ee712feeb5fa780bb11fad76634e71334718568e7695cb55/cryptography-46.0.1-cp38-abi3-win_amd64.whl", hash = "sha256:7176a5ab56fac98d706921f6416a05e5aff7df0e4b91516f450f8627cda22af3", size = 3517226, upload-time = "2025-09-17T00:10:10.769Z" }, - { url = "https://files.pythonhosted.org/packages/ff/e8/77d17d00981cdd27cc493e81e1749a0b8bbfb843780dbd841e30d7f50743/cryptography-46.0.1-cp38-abi3-win_arm64.whl", hash = "sha256:efc9e51c3e595267ff84adf56e9b357db89ab2279d7e375ffcaf8f678606f3d9", size = 2923149, upload-time = "2025-09-17T00:10:13.236Z" }, -] - -[[package]] -name = "dataclasses-json" -version = "0.6.7" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "marshmallow" }, - { name = "typing-inspect" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/64/a4/f71d9cf3a5ac257c993b5ca3f93df5f7fb395c725e7f1e6479d2514173c3/dataclasses_json-0.6.7.tar.gz", hash = "sha256:b6b3e528266ea45b9535223bc53ca645f5208833c29229e847b3f26a1cc55fc0", size = 32227, upload-time = "2024-06-09T16:20:19.103Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/be/d0d44e092656fe7a06b55e6103cbce807cdbdee17884a5367c68c9860853/dataclasses_json-0.6.7-py3-none-any.whl", hash = "sha256:0dbf33f26c8d5305befd61b39d2b3414e8a407bedc2834dea9b8d642666fb40a", size = 28686, upload-time = "2024-06-09T16:20:16.715Z" }, -] - -[[package]] -name = "dataproperty" -version = "1.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mbstrdecoder" }, - { name = "typepy", extra = ["datetime"] }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0b/81/8c8b64ae873cb9014815214c07b63b12e3b18835780fb342223cfe3fe7d8/dataproperty-1.1.0.tar.gz", hash = "sha256:b038437a4097d1a1c497695c3586ea34bea67fdd35372b9a50f30bf044d77d04", size = 42574, upload-time = "2024-12-31T14:37:26.033Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/21/c2/e12e95e289e6081a40454199ab213139ef16a528c7c86432de545b05a23a/DataProperty-1.1.0-py3-none-any.whl", hash = "sha256:c61fcb2e2deca35e6d1eb1f251a7f22f0dcde63e80e61f0cc18c19f42abfd25b", size = 27581, upload-time = "2024-12-31T14:37:22.657Z" }, -] - -[[package]] -name = "debugpy" -version = "1.8.17" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/15/ad/71e708ff4ca377c4230530d6a7aa7992592648c122a2cd2b321cf8b35a76/debugpy-1.8.17.tar.gz", hash = "sha256:fd723b47a8c08892b1a16b2c6239a8b96637c62a59b94bb5dab4bac592a58a8e", size = 1644129, upload-time = "2025-09-17T16:33:20.633Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/08/2b/9d8e65beb2751876c82e1aceb32f328c43ec872711fa80257c7674f45650/debugpy-1.8.17-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:f14467edef672195c6f6b8e27ce5005313cb5d03c9239059bc7182b60c176e2d", size = 2549522, upload-time = "2025-09-17T16:33:38.466Z" }, - { url = "https://files.pythonhosted.org/packages/b4/78/eb0d77f02971c05fca0eb7465b18058ba84bd957062f5eec82f941ac792a/debugpy-1.8.17-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:24693179ef9dfa20dca8605905a42b392be56d410c333af82f1c5dff807a64cc", size = 4309417, upload-time = "2025-09-17T16:33:41.299Z" }, - { url = "https://files.pythonhosted.org/packages/37/42/c40f1d8cc1fed1e75ea54298a382395b8b937d923fcf41ab0797a554f555/debugpy-1.8.17-cp312-cp312-win32.whl", hash = "sha256:6a4e9dacf2cbb60d2514ff7b04b4534b0139facbf2abdffe0639ddb6088e59cf", size = 5277130, upload-time = "2025-09-17T16:33:43.554Z" }, - { url = "https://files.pythonhosted.org/packages/72/22/84263b205baad32b81b36eac076de0cdbe09fe2d0637f5b32243dc7c925b/debugpy-1.8.17-cp312-cp312-win_amd64.whl", hash = "sha256:e8f8f61c518952fb15f74a302e068b48d9c4691768ade433e4adeea961993464", size = 5319053, upload-time = "2025-09-17T16:33:53.033Z" }, - { url = "https://files.pythonhosted.org/packages/b0/d0/89247ec250369fc76db477720a26b2fce7ba079ff1380e4ab4529d2fe233/debugpy-1.8.17-py2.py3-none-any.whl", hash = "sha256:60c7dca6571efe660ccb7a9508d73ca14b8796c4ed484c2002abba714226cfef", size = 5283210, upload-time = "2025-09-17T16:34:25.835Z" }, -] - -[[package]] -name = "decorator" -version = "5.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711, upload-time = "2025-02-24T04:41:34.073Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" }, -] - -[[package]] -name = "defusedxml" -version = "0.7.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520, upload-time = "2021-03-08T10:59:26.269Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" }, -] - -[[package]] -name = "deprecated" -version = "1.2.18" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "wrapt" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/98/97/06afe62762c9a8a86af0cfb7bfdab22a43ad17138b07af5b1a58442690a2/deprecated-1.2.18.tar.gz", hash = "sha256:422b6f6d859da6f2ef57857761bfb392480502a64c3028ca9bbe86085d72115d", size = 2928744, upload-time = "2025-01-27T10:46:25.7Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6e/c6/ac0b6c1e2d138f1002bcf799d330bd6d85084fece321e662a14223794041/Deprecated-1.2.18-py2.py3-none-any.whl", hash = "sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec", size = 9998, upload-time = "2025-01-27T10:46:09.186Z" }, -] - -[[package]] -name = "deprecation" -version = "2.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "packaging" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5a/d3/8ae2869247df154b64c1884d7346d412fed0c49df84db635aab2d1c40e62/deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff", size = 173788, upload-time = "2020-04-20T14:23:38.738Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/02/c3/253a89ee03fc9b9682f1541728eb66db7db22148cd94f89ab22528cd1e1b/deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a", size = 11178, upload-time = "2020-04-20T14:23:36.581Z" }, -] - -[[package]] -name = "dirtyjson" -version = "1.0.8" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/db/04/d24f6e645ad82ba0ef092fa17d9ef7a21953781663648a01c9371d9e8e98/dirtyjson-1.0.8.tar.gz", hash = "sha256:90ca4a18f3ff30ce849d100dcf4a003953c79d3a2348ef056f1d9c22231a25fd", size = 30782, upload-time = "2022-11-28T23:32:33.319Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/68/69/1bcf70f81de1b4a9f21b3a62ec0c83bdff991c88d6cc2267d02408457e88/dirtyjson-1.0.8-py3-none-any.whl", hash = "sha256:125e27248435a58acace26d5c2c4c11a1c0de0a9c5124c5a94ba78e517d74f53", size = 25197, upload-time = "2022-11-28T23:32:31.219Z" }, -] - -[[package]] -name = "distro" -version = "1.9.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" } -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 = "fastuuid" -version = "0.14.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c3/7d/d9daedf0f2ebcacd20d599928f8913e9d2aea1d56d2d355a93bfa2b611d7/fastuuid-0.14.0.tar.gz", hash = "sha256:178947fc2f995b38497a74172adee64fdeb8b7ec18f2a5934d037641ba265d26", size = 18232, upload-time = "2025-10-19T22:19:22.402Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/02/a2/e78fcc5df65467f0d207661b7ef86c5b7ac62eea337c0c0fcedbeee6fb13/fastuuid-0.14.0-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:77e94728324b63660ebf8adb27055e92d2e4611645bf12ed9d88d30486471d0a", size = 510164, upload-time = "2025-10-19T22:31:45.635Z" }, - { url = "https://files.pythonhosted.org/packages/2b/b3/c846f933f22f581f558ee63f81f29fa924acd971ce903dab1a9b6701816e/fastuuid-0.14.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:caa1f14d2102cb8d353096bc6ef6c13b2c81f347e6ab9d6fbd48b9dea41c153d", size = 261837, upload-time = "2025-10-19T22:38:38.53Z" }, - { url = "https://files.pythonhosted.org/packages/54/ea/682551030f8c4fa9a769d9825570ad28c0c71e30cf34020b85c1f7ee7382/fastuuid-0.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d23ef06f9e67163be38cece704170486715b177f6baae338110983f99a72c070", size = 251370, upload-time = "2025-10-19T22:40:26.07Z" }, - { url = "https://files.pythonhosted.org/packages/14/dd/5927f0a523d8e6a76b70968e6004966ee7df30322f5fc9b6cdfb0276646a/fastuuid-0.14.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c9ec605ace243b6dbe3bd27ebdd5d33b00d8d1d3f580b39fdd15cd96fd71796", size = 277766, upload-time = "2025-10-19T22:37:23.779Z" }, - { url = "https://files.pythonhosted.org/packages/16/6e/c0fb547eef61293153348f12e0f75a06abb322664b34a1573a7760501336/fastuuid-0.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:808527f2407f58a76c916d6aa15d58692a4a019fdf8d4c32ac7ff303b7d7af09", size = 278105, upload-time = "2025-10-19T22:26:56.821Z" }, - { url = "https://files.pythonhosted.org/packages/2d/b1/b9c75e03b768f61cf2e84ee193dc18601aeaf89a4684b20f2f0e9f52b62c/fastuuid-0.14.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2fb3c0d7fef6674bbeacdd6dbd386924a7b60b26de849266d1ff6602937675c8", size = 301564, upload-time = "2025-10-19T22:30:31.604Z" }, - { url = "https://files.pythonhosted.org/packages/fc/fa/f7395fdac07c7a54f18f801744573707321ca0cee082e638e36452355a9d/fastuuid-0.14.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab3f5d36e4393e628a4df337c2c039069344db5f4b9d2a3c9cea48284f1dd741", size = 459659, upload-time = "2025-10-19T22:31:32.341Z" }, - { url = "https://files.pythonhosted.org/packages/66/49/c9fd06a4a0b1f0f048aacb6599e7d96e5d6bc6fa680ed0d46bf111929d1b/fastuuid-0.14.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:b9a0ca4f03b7e0b01425281ffd44e99d360e15c895f1907ca105854ed85e2057", size = 478430, upload-time = "2025-10-19T22:26:22.962Z" }, - { url = "https://files.pythonhosted.org/packages/be/9c/909e8c95b494e8e140e8be6165d5fc3f61fdc46198c1554df7b3e1764471/fastuuid-0.14.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3acdf655684cc09e60fb7e4cf524e8f42ea760031945aa8086c7eae2eeeabeb8", size = 450894, upload-time = "2025-10-19T22:27:01.647Z" }, - { url = "https://files.pythonhosted.org/packages/90/eb/d29d17521976e673c55ef7f210d4cdd72091a9ec6755d0fd4710d9b3c871/fastuuid-0.14.0-cp312-cp312-win32.whl", hash = "sha256:9579618be6280700ae36ac42c3efd157049fe4dd40ca49b021280481c78c3176", size = 154374, upload-time = "2025-10-19T22:29:19.879Z" }, - { url = "https://files.pythonhosted.org/packages/cc/fc/f5c799a6ea6d877faec0472d0b27c079b47c86b1cdc577720a5386483b36/fastuuid-0.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:d9e4332dc4ba054434a9594cbfaf7823b57993d7d8e7267831c3e059857cf397", size = 156550, upload-time = "2025-10-19T22:27:49.658Z" }, -] - -[[package]] -name = "filelock" -version = "3.19.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/40/bb/0ab3e58d22305b6f5440629d20683af28959bf793d98d11950e305c1c326/filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58", size = 17687, upload-time = "2025-08-14T16:56:03.016Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/42/14/42b2651a2f46b022ccd948bca9f2d5af0fd8929c4eec235b8d6d844fbe67/filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d", size = 15988, upload-time = "2025-08-14T16:56:01.633Z" }, -] - -[[package]] -name = "filetype" -version = "1.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bb/29/745f7d30d47fe0f251d3ad3dc2978a23141917661998763bebb6da007eb1/filetype-1.2.0.tar.gz", hash = "sha256:66b56cd6474bf41d8c54660347d37afcc3f7d1970648de365c102ef77548aadb", size = 998020, upload-time = "2022-11-02T17:34:04.141Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/18/79/1b8fa1bb3568781e84c9200f951c735f3f157429f44be0495da55894d620/filetype-1.2.0-py2.py3-none-any.whl", hash = "sha256:7ce71b6880181241cf7ac8697a2f1eb6a8bd9b429f7ad6d27b8db9ba5f1c2d25", size = 19970, upload-time = "2022-11-02T17:34:01.425Z" }, -] - -[[package]] -name = "flask" -version = "3.1.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "blinker" }, - { name = "click" }, - { name = "itsdangerous" }, - { name = "jinja2" }, - { name = "markupsafe" }, - { name = "werkzeug" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/dc/6d/cfe3c0fcc5e477df242b98bfe186a4c34357b4847e87ecaef04507332dab/flask-3.1.2.tar.gz", hash = "sha256:bf656c15c80190ed628ad08cdfd3aaa35beb087855e2f494910aa3774cc4fd87", size = 720160, upload-time = "2025-08-19T21:03:21.205Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/f9/7f9263c5695f4bd0023734af91bedb2ff8209e8de6ead162f35d8dc762fd/flask-3.1.2-py3-none-any.whl", hash = "sha256:ca1d8112ec8a6158cc29ea4858963350011b5c846a414cdb7a954aa9e967d03c", size = 103308, upload-time = "2025-08-19T21:03:19.499Z" }, -] - -[[package]] -name = "flask-wtf" -version = "1.2.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "flask" }, - { name = "itsdangerous" }, - { name = "wtforms" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/80/9b/f1cd6e41bbf874f3436368f2c7ee3216c1e82d666ff90d1d800e20eb1317/flask_wtf-1.2.2.tar.gz", hash = "sha256:79d2ee1e436cf570bccb7d916533fa18757a2f18c290accffab1b9a0b684666b", size = 42641, upload-time = "2024-10-24T07:18:58.555Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/19/354449145fbebb65e7c621235b6ad69bebcfaec2142481f044d0ddc5b5c5/flask_wtf-1.2.2-py3-none-any.whl", hash = "sha256:e93160c5c5b6b571cf99300b6e01b72f9a101027cab1579901f8b10c5daf0b70", size = 12779, upload-time = "2024-10-24T07:18:56.976Z" }, -] - -[[package]] -name = "frozenlist" -version = "1.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/79/b1/b64018016eeb087db503b038296fd782586432b9c077fc5c7839e9cb6ef6/frozenlist-1.7.0.tar.gz", hash = "sha256:2e310d81923c2437ea8670467121cc3e9b0f76d3043cc1d2331d56c7fb7a3a8f", size = 45078, upload-time = "2025-06-09T23:02:35.538Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/a2/c8131383f1e66adad5f6ecfcce383d584ca94055a34d683bbb24ac5f2f1c/frozenlist-1.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3dbf9952c4bb0e90e98aec1bd992b3318685005702656bc6f67c1a32b76787f2", size = 81424, upload-time = "2025-06-09T23:00:42.24Z" }, - { url = "https://files.pythonhosted.org/packages/4c/9d/02754159955088cb52567337d1113f945b9e444c4960771ea90eb73de8db/frozenlist-1.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1f5906d3359300b8a9bb194239491122e6cf1444c2efb88865426f170c262cdb", size = 47952, upload-time = "2025-06-09T23:00:43.481Z" }, - { url = "https://files.pythonhosted.org/packages/01/7a/0046ef1bd6699b40acd2067ed6d6670b4db2f425c56980fa21c982c2a9db/frozenlist-1.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3dabd5a8f84573c8d10d8859a50ea2dec01eea372031929871368c09fa103478", size = 46688, upload-time = "2025-06-09T23:00:44.793Z" }, - { url = "https://files.pythonhosted.org/packages/d6/a2/a910bafe29c86997363fb4c02069df4ff0b5bc39d33c5198b4e9dd42d8f8/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa57daa5917f1738064f302bf2626281a1cb01920c32f711fbc7bc36111058a8", size = 243084, upload-time = "2025-06-09T23:00:46.125Z" }, - { url = "https://files.pythonhosted.org/packages/64/3e/5036af9d5031374c64c387469bfcc3af537fc0f5b1187d83a1cf6fab1639/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c193dda2b6d49f4c4398962810fa7d7c78f032bf45572b3e04dd5249dff27e08", size = 233524, upload-time = "2025-06-09T23:00:47.73Z" }, - { url = "https://files.pythonhosted.org/packages/06/39/6a17b7c107a2887e781a48ecf20ad20f1c39d94b2a548c83615b5b879f28/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe2b675cf0aaa6d61bf8fbffd3c274b3c9b7b1623beb3809df8a81399a4a9c4", size = 248493, upload-time = "2025-06-09T23:00:49.742Z" }, - { url = "https://files.pythonhosted.org/packages/be/00/711d1337c7327d88c44d91dd0f556a1c47fb99afc060ae0ef66b4d24793d/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8fc5d5cda37f62b262405cf9652cf0856839c4be8ee41be0afe8858f17f4c94b", size = 244116, upload-time = "2025-06-09T23:00:51.352Z" }, - { url = "https://files.pythonhosted.org/packages/24/fe/74e6ec0639c115df13d5850e75722750adabdc7de24e37e05a40527ca539/frozenlist-1.7.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0d5ce521d1dd7d620198829b87ea002956e4319002ef0bc8d3e6d045cb4646e", size = 224557, upload-time = "2025-06-09T23:00:52.855Z" }, - { url = "https://files.pythonhosted.org/packages/8d/db/48421f62a6f77c553575201e89048e97198046b793f4a089c79a6e3268bd/frozenlist-1.7.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:488d0a7d6a0008ca0db273c542098a0fa9e7dfaa7e57f70acef43f32b3f69dca", size = 241820, upload-time = "2025-06-09T23:00:54.43Z" }, - { url = "https://files.pythonhosted.org/packages/1d/fa/cb4a76bea23047c8462976ea7b7a2bf53997a0ca171302deae9d6dd12096/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:15a7eaba63983d22c54d255b854e8108e7e5f3e89f647fc854bd77a237e767df", size = 236542, upload-time = "2025-06-09T23:00:56.409Z" }, - { url = "https://files.pythonhosted.org/packages/5d/32/476a4b5cfaa0ec94d3f808f193301debff2ea42288a099afe60757ef6282/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1eaa7e9c6d15df825bf255649e05bd8a74b04a4d2baa1ae46d9c2d00b2ca2cb5", size = 249350, upload-time = "2025-06-09T23:00:58.468Z" }, - { url = "https://files.pythonhosted.org/packages/8d/ba/9a28042f84a6bf8ea5dbc81cfff8eaef18d78b2a1ad9d51c7bc5b029ad16/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4389e06714cfa9d47ab87f784a7c5be91d3934cd6e9a7b85beef808297cc025", size = 225093, upload-time = "2025-06-09T23:01:00.015Z" }, - { url = "https://files.pythonhosted.org/packages/bc/29/3a32959e68f9cf000b04e79ba574527c17e8842e38c91d68214a37455786/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:73bd45e1488c40b63fe5a7df892baf9e2a4d4bb6409a2b3b78ac1c6236178e01", size = 245482, upload-time = "2025-06-09T23:01:01.474Z" }, - { url = "https://files.pythonhosted.org/packages/80/e8/edf2f9e00da553f07f5fa165325cfc302dead715cab6ac8336a5f3d0adc2/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99886d98e1643269760e5fe0df31e5ae7050788dd288947f7f007209b8c33f08", size = 249590, upload-time = "2025-06-09T23:01:02.961Z" }, - { url = "https://files.pythonhosted.org/packages/1c/80/9a0eb48b944050f94cc51ee1c413eb14a39543cc4f760ed12657a5a3c45a/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:290a172aae5a4c278c6da8a96222e6337744cd9c77313efe33d5670b9f65fc43", size = 237785, upload-time = "2025-06-09T23:01:05.095Z" }, - { url = "https://files.pythonhosted.org/packages/f3/74/87601e0fb0369b7a2baf404ea921769c53b7ae00dee7dcfe5162c8c6dbf0/frozenlist-1.7.0-cp312-cp312-win32.whl", hash = "sha256:426c7bc70e07cfebc178bc4c2bf2d861d720c4fff172181eeb4a4c41d4ca2ad3", size = 39487, upload-time = "2025-06-09T23:01:06.54Z" }, - { url = "https://files.pythonhosted.org/packages/0b/15/c026e9a9fc17585a9d461f65d8593d281fedf55fbf7eb53f16c6df2392f9/frozenlist-1.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:563b72efe5da92e02eb68c59cb37205457c977aa7a449ed1b37e6939e5c47c6a", size = 43874, upload-time = "2025-06-09T23:01:07.752Z" }, - { url = "https://files.pythonhosted.org/packages/ee/45/b82e3c16be2182bff01179db177fe144d58b5dc787a7d4492c6ed8b9317f/frozenlist-1.7.0-py3-none-any.whl", hash = "sha256:9a5af342e34f7e97caf8c995864c7a396418ae2859cc6fdf1b1073020d516a7e", size = 13106, upload-time = "2025-06-09T23:02:34.204Z" }, -] - -[[package]] -name = "fsspec" -version = "2024.10.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a0/52/f16a068ebadae42526484c31f4398e62962504e5724a8ba5dc3409483df2/fsspec-2024.10.0.tar.gz", hash = "sha256:eda2d8a4116d4f2429db8550f2457da57279247dd930bb12f821b58391359493", size = 286853, upload-time = "2024-10-21T01:21:16.969Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/b2/454d6e7f0158951d8a78c2e1eb4f69ae81beb8dca5fee9809c6c99e9d0d0/fsspec-2024.10.0-py3-none-any.whl", hash = "sha256:03b9a6785766a4de40368b88906366755e2819e758b83705c88cd7cb5fe81871", size = 179641, upload-time = "2024-10-21T01:21:14.793Z" }, -] - -[[package]] -name = "gcsfs" -version = "2024.10.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "aiohttp" }, - { name = "decorator" }, - { name = "fsspec" }, - { name = "google-auth" }, - { name = "google-auth-oauthlib" }, - { name = "google-cloud-storage" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e5/1e/1d8c4593d9e2eb04918fec43253ab152823d67ad51ad9e3ab6b3a78c431a/gcsfs-2024.10.0.tar.gz", hash = "sha256:5df54cfe568e8fdeea5aafa7fed695cdc69a9a674e991ca8c1ce634f5df1d314", size = 79588, upload-time = "2024-10-21T13:43:26.163Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/96/d60e835fb7d10166c77aef0c1fa30e634153c03a0f486786977b95f88fde/gcsfs-2024.10.0-py2.py3-none-any.whl", hash = "sha256:bb2d23547e61203ea2dda5fa6c4b91a0c34b74ebe8bb6ab1926f6c33381bceb2", size = 34953, upload-time = "2024-10-21T13:43:24.951Z" }, -] - -[[package]] -name = "google-api-core" -version = "2.25.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "google-auth" }, - { name = "googleapis-common-protos" }, - { name = "proto-plus" }, - { name = "protobuf" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/dc/21/e9d043e88222317afdbdb567165fdbc3b0aad90064c7e0c9eb0ad9955ad8/google_api_core-2.25.1.tar.gz", hash = "sha256:d2aaa0b13c78c61cb3f4282c464c046e45fbd75755683c9c525e6e8f7ed0a5e8", size = 165443, upload-time = "2025-06-12T20:52:20.439Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/14/4b/ead00905132820b623732b175d66354e9d3e69fcf2a5dcdab780664e7896/google_api_core-2.25.1-py3-none-any.whl", hash = "sha256:8a2a56c1fef82987a524371f99f3bd0143702fecc670c72e600c1cda6bf8dbb7", size = 160807, upload-time = "2025-06-12T20:52:19.334Z" }, -] - -[[package]] -name = "google-auth" -version = "2.41.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cachetools" }, - { name = "pyasn1-modules" }, - { name = "rsa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/07/c5/87742f5b5f055514c67f970f7174a876fccff2289a69d460b0614cc7ccfb/google_auth-2.41.0.tar.gz", hash = "sha256:c9d7b534ea4a5d9813c552846797fafb080312263cd4994d6622dd50992ae101", size = 292282, upload-time = "2025-09-29T21:36:35.791Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/78/ff/a1c426fc9bea7268230bf92340da7d112fae18cf946cafe13ab17d14e6ee/google_auth-2.41.0-py2.py3-none-any.whl", hash = "sha256:d8bed9b53ab63b7b0374656b8e1bef051f95bb14ecc0cf21ba49de7911d62e09", size = 221168, upload-time = "2025-09-29T21:36:33.925Z" }, -] - -[[package]] -name = "google-auth-oauthlib" -version = "1.2.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "google-auth" }, - { name = "requests-oauthlib" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/fb/87/e10bf24f7bcffc1421b84d6f9c3377c30ec305d082cd737ddaa6d8f77f7c/google_auth_oauthlib-1.2.2.tar.gz", hash = "sha256:11046fb8d3348b296302dd939ace8af0a724042e8029c1b872d87fabc9f41684", size = 20955, upload-time = "2025-04-22T16:40:29.172Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ac/84/40ee070be95771acd2f4418981edb834979424565c3eec3cd88b6aa09d24/google_auth_oauthlib-1.2.2-py3-none-any.whl", hash = "sha256:fd619506f4b3908b5df17b65f39ca8d66ea56986e5472eb5978fd8f3786f00a2", size = 19072, upload-time = "2025-04-22T16:40:28.174Z" }, -] - -[[package]] -name = "google-cloud-core" -version = "2.4.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "google-api-core" }, - { name = "google-auth" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d6/b8/2b53838d2acd6ec6168fd284a990c76695e84c65deee79c9f3a4276f6b4f/google_cloud_core-2.4.3.tar.gz", hash = "sha256:1fab62d7102844b278fe6dead3af32408b1df3eb06f5c7e8634cbd40edc4da53", size = 35861, upload-time = "2025-03-10T21:05:38.948Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/40/86/bda7241a8da2d28a754aad2ba0f6776e35b67e37c36ae0c45d49370f1014/google_cloud_core-2.4.3-py2.py3-none-any.whl", hash = "sha256:5130f9f4c14b4fafdff75c79448f9495cfade0d8775facf1b09c3bf67e027f6e", size = 29348, upload-time = "2025-03-10T21:05:37.785Z" }, -] - -[[package]] -name = "google-cloud-storage" -version = "2.19.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "google-api-core" }, - { name = "google-auth" }, - { name = "google-cloud-core" }, - { name = "google-crc32c" }, - { name = "google-resumable-media" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/36/76/4d965702e96bb67976e755bed9828fa50306dca003dbee08b67f41dd265e/google_cloud_storage-2.19.0.tar.gz", hash = "sha256:cd05e9e7191ba6cb68934d8eb76054d9be4562aa89dbc4236feee4d7d51342b2", size = 5535488, upload-time = "2024-12-05T01:35:06.49Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d5/94/6db383d8ee1adf45dc6c73477152b82731fa4c4a46d9c1932cc8757e0fd4/google_cloud_storage-2.19.0-py2.py3-none-any.whl", hash = "sha256:aeb971b5c29cf8ab98445082cbfe7b161a1f48ed275822f59ed3f1524ea54fba", size = 131787, upload-time = "2024-12-05T01:35:04.736Z" }, -] - -[[package]] -name = "google-crc32c" -version = "1.7.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/19/ae/87802e6d9f9d69adfaedfcfd599266bf386a54d0be058b532d04c794f76d/google_crc32c-1.7.1.tar.gz", hash = "sha256:2bff2305f98846f3e825dbeec9ee406f89da7962accdb29356e4eadc251bd472", size = 14495, upload-time = "2025-03-26T14:29:13.32Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dd/b7/787e2453cf8639c94b3d06c9d61f512234a82e1d12d13d18584bd3049904/google_crc32c-1.7.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2d73a68a653c57281401871dd4aeebbb6af3191dcac751a76ce430df4d403194", size = 30470, upload-time = "2025-03-26T14:34:31.655Z" }, - { url = "https://files.pythonhosted.org/packages/ed/b4/6042c2b0cbac3ec3a69bb4c49b28d2f517b7a0f4a0232603c42c58e22b44/google_crc32c-1.7.1-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:22beacf83baaf59f9d3ab2bbb4db0fb018da8e5aebdce07ef9f09fce8220285e", size = 30315, upload-time = "2025-03-26T15:01:54.634Z" }, - { url = "https://files.pythonhosted.org/packages/29/ad/01e7a61a5d059bc57b702d9ff6a18b2585ad97f720bd0a0dbe215df1ab0e/google_crc32c-1.7.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19eafa0e4af11b0a4eb3974483d55d2d77ad1911e6cf6f832e1574f6781fd337", size = 33180, upload-time = "2025-03-26T14:41:32.168Z" }, - { url = "https://files.pythonhosted.org/packages/3b/a5/7279055cf004561894ed3a7bfdf5bf90a53f28fadd01af7cd166e88ddf16/google_crc32c-1.7.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6d86616faaea68101195c6bdc40c494e4d76f41e07a37ffdef270879c15fb65", size = 32794, upload-time = "2025-03-26T14:41:33.264Z" }, - { url = "https://files.pythonhosted.org/packages/0f/d6/77060dbd140c624e42ae3ece3df53b9d811000729a5c821b9fd671ceaac6/google_crc32c-1.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:b7491bdc0c7564fcf48c0179d2048ab2f7c7ba36b84ccd3a3e1c3f7a72d3bba6", size = 33477, upload-time = "2025-03-26T14:29:10.94Z" }, -] - -[[package]] -name = "google-resumable-media" -version = "2.7.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "google-crc32c" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/58/5a/0efdc02665dca14e0837b62c8a1a93132c264bd02054a15abb2218afe0ae/google_resumable_media-2.7.2.tar.gz", hash = "sha256:5280aed4629f2b60b847b0d42f9857fd4935c11af266744df33d8074cae92fe0", size = 2163099, upload-time = "2024-08-07T22:20:38.555Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/82/35/b8d3baf8c46695858cb9d8835a53baa1eeb9906ddaf2f728a5f5b640fd1e/google_resumable_media-2.7.2-py2.py3-none-any.whl", hash = "sha256:3ce7551e9fe6d99e9a126101d2536612bb73486721951e9562fee0f90c6ababa", size = 81251, upload-time = "2024-08-07T22:20:36.409Z" }, -] - -[[package]] -name = "googleapis-common-protos" -version = "1.70.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "protobuf" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/39/24/33db22342cf4a2ea27c9955e6713140fedd51e8b141b5ce5260897020f1a/googleapis_common_protos-1.70.0.tar.gz", hash = "sha256:0e1b44e0ea153e6594f9f394fef15193a68aaaea2d843f83e2742717ca753257", size = 145903, upload-time = "2025-04-14T10:17:02.924Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/86/f1/62a193f0227cf15a920390abe675f386dec35f7ae3ffe6da582d3ade42c7/googleapis_common_protos-1.70.0-py3-none-any.whl", hash = "sha256:b8bfcca8c25a2bb253e0e0b0adaf8c00773e5e6af6fd92397576680b807e0fd8", size = 294530, upload-time = "2025-04-14T10:17:01.271Z" }, -] - -[[package]] -name = "greenlet" -version = "3.2.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/03/b8/704d753a5a45507a7aab61f18db9509302ed3d0a27ac7e0359ec2905b1a6/greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d", size = 188260, upload-time = "2025-08-07T13:24:33.51Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/44/69/9b804adb5fd0671f367781560eb5eb586c4d495277c93bde4307b9e28068/greenlet-3.2.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd", size = 274079, upload-time = "2025-08-07T13:15:45.033Z" }, - { url = "https://files.pythonhosted.org/packages/46/e9/d2a80c99f19a153eff70bc451ab78615583b8dac0754cfb942223d2c1a0d/greenlet-3.2.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddf9164e7a5b08e9d22511526865780a576f19ddd00d62f8a665949327fde8bb", size = 640997, upload-time = "2025-08-07T13:42:56.234Z" }, - { url = "https://files.pythonhosted.org/packages/3b/16/035dcfcc48715ccd345f3a93183267167cdd162ad123cd93067d86f27ce4/greenlet-3.2.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f28588772bb5fb869a8eb331374ec06f24a83a9c25bfa1f38b6993afe9c1e968", size = 655185, upload-time = "2025-08-07T13:45:27.624Z" }, - { url = "https://files.pythonhosted.org/packages/31/da/0386695eef69ffae1ad726881571dfe28b41970173947e7c558d9998de0f/greenlet-3.2.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5c9320971821a7cb77cfab8d956fa8e39cd07ca44b6070db358ceb7f8797c8c9", size = 649926, upload-time = "2025-08-07T13:53:15.251Z" }, - { url = "https://files.pythonhosted.org/packages/68/88/69bf19fd4dc19981928ceacbc5fd4bb6bc2215d53199e367832e98d1d8fe/greenlet-3.2.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c60a6d84229b271d44b70fb6e5fa23781abb5d742af7b808ae3f6efd7c9c60f6", size = 651839, upload-time = "2025-08-07T13:18:30.281Z" }, - { url = "https://files.pythonhosted.org/packages/19/0d/6660d55f7373b2ff8152401a83e02084956da23ae58cddbfb0b330978fe9/greenlet-3.2.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b3812d8d0c9579967815af437d96623f45c0f2ae5f04e366de62a12d83a8fb0", size = 607586, upload-time = "2025-08-07T13:18:28.544Z" }, - { url = "https://files.pythonhosted.org/packages/8e/1a/c953fdedd22d81ee4629afbb38d2f9d71e37d23caace44775a3a969147d4/greenlet-3.2.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:abbf57b5a870d30c4675928c37278493044d7c14378350b3aa5d484fa65575f0", size = 1123281, upload-time = "2025-08-07T13:42:39.858Z" }, - { url = "https://files.pythonhosted.org/packages/3f/c7/12381b18e21aef2c6bd3a636da1088b888b97b7a0362fac2e4de92405f97/greenlet-3.2.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:20fb936b4652b6e307b8f347665e2c615540d4b42b3b4c8a321d8286da7e520f", size = 1151142, upload-time = "2025-08-07T13:18:22.981Z" }, - { url = "https://files.pythonhosted.org/packages/27/45/80935968b53cfd3f33cf99ea5f08227f2646e044568c9b1555b58ffd61c2/greenlet-3.2.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ee7a6ec486883397d70eec05059353b8e83eca9168b9f3f9a361971e77e0bcd0", size = 1564846, upload-time = "2025-11-04T12:42:15.191Z" }, - { url = "https://files.pythonhosted.org/packages/69/02/b7c30e5e04752cb4db6202a3858b149c0710e5453b71a3b2aec5d78a1aab/greenlet-3.2.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:326d234cbf337c9c3def0676412eb7040a35a768efc92504b947b3e9cfc7543d", size = 1633814, upload-time = "2025-11-04T12:42:17.175Z" }, - { url = "https://files.pythonhosted.org/packages/e9/08/b0814846b79399e585f974bbeebf5580fbe59e258ea7be64d9dfb253c84f/greenlet-3.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:a7d4e128405eea3814a12cc2605e0e6aedb4035bf32697f72deca74de4105e02", size = 299899, upload-time = "2025-08-07T13:38:53.448Z" }, -] - -[[package]] -name = "griffe" -version = "1.14.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ec/d7/6c09dd7ce4c7837e4cdb11dce980cb45ae3cd87677298dc3b781b6bce7d3/griffe-1.14.0.tar.gz", hash = "sha256:9d2a15c1eca966d68e00517de5d69dd1bc5c9f2335ef6c1775362ba5b8651a13", size = 424684, upload-time = "2025-09-05T15:02:29.167Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/b1/9ff6578d789a89812ff21e4e0f80ffae20a65d5dd84e7a17873fe3b365be/griffe-1.14.0-py3-none-any.whl", hash = "sha256:0e9d52832cccf0f7188cfe585ba962d2674b241c01916d780925df34873bceb0", size = 144439, upload-time = "2025-09-05T15:02:27.511Z" }, -] - -[[package]] -name = "grpcio" -version = "1.76.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b6/e0/318c1ce3ae5a17894d5791e87aea147587c9e702f24122cc7a5c8bbaeeb1/grpcio-1.76.0.tar.gz", hash = "sha256:7be78388d6da1a25c0d5ec506523db58b18be22d9c37d8d3a32c08be4987bd73", size = 12785182, upload-time = "2025-10-21T16:23:12.106Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/05/8e29121994b8d959ffa0afd28996d452f291b48cfc0875619de0bde2c50c/grpcio-1.76.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:81fd9652b37b36f16138611c7e884eb82e0cec137c40d3ef7c3f9b3ed00f6ed8", size = 5799718, upload-time = "2025-10-21T16:21:17.939Z" }, - { url = "https://files.pythonhosted.org/packages/d9/75/11d0e66b3cdf998c996489581bdad8900db79ebd83513e45c19548f1cba4/grpcio-1.76.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:04bbe1bfe3a68bbfd4e52402ab7d4eb59d72d02647ae2042204326cf4bbad280", size = 11825627, upload-time = "2025-10-21T16:21:20.466Z" }, - { url = "https://files.pythonhosted.org/packages/28/50/2f0aa0498bc188048f5d9504dcc5c2c24f2eb1a9337cd0fa09a61a2e75f0/grpcio-1.76.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d388087771c837cdb6515539f43b9d4bf0b0f23593a24054ac16f7a960be16f4", size = 6359167, upload-time = "2025-10-21T16:21:23.122Z" }, - { url = "https://files.pythonhosted.org/packages/66/e5/bbf0bb97d29ede1d59d6588af40018cfc345b17ce979b7b45424628dc8bb/grpcio-1.76.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:9f8f757bebaaea112c00dba718fc0d3260052ce714e25804a03f93f5d1c6cc11", size = 7044267, upload-time = "2025-10-21T16:21:25.995Z" }, - { url = "https://files.pythonhosted.org/packages/f5/86/f6ec2164f743d9609691115ae8ece098c76b894ebe4f7c94a655c6b03e98/grpcio-1.76.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:980a846182ce88c4f2f7e2c22c56aefd515daeb36149d1c897f83cf57999e0b6", size = 6573963, upload-time = "2025-10-21T16:21:28.631Z" }, - { url = "https://files.pythonhosted.org/packages/60/bc/8d9d0d8505feccfdf38a766d262c71e73639c165b311c9457208b56d92ae/grpcio-1.76.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f92f88e6c033db65a5ae3d97905c8fea9c725b63e28d5a75cb73b49bda5024d8", size = 7164484, upload-time = "2025-10-21T16:21:30.837Z" }, - { url = "https://files.pythonhosted.org/packages/67/e6/5d6c2fc10b95edf6df9b8f19cf10a34263b7fd48493936fffd5085521292/grpcio-1.76.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4baf3cbe2f0be3289eb68ac8ae771156971848bb8aaff60bad42005539431980", size = 8127777, upload-time = "2025-10-21T16:21:33.577Z" }, - { url = "https://files.pythonhosted.org/packages/3f/c8/dce8ff21c86abe025efe304d9e31fdb0deaaa3b502b6a78141080f206da0/grpcio-1.76.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:615ba64c208aaceb5ec83bfdce7728b80bfeb8be97562944836a7a0a9647d882", size = 7594014, upload-time = "2025-10-21T16:21:41.882Z" }, - { url = "https://files.pythonhosted.org/packages/e0/42/ad28191ebf983a5d0ecef90bab66baa5a6b18f2bfdef9d0a63b1973d9f75/grpcio-1.76.0-cp312-cp312-win32.whl", hash = "sha256:45d59a649a82df5718fd9527ce775fd66d1af35e6d31abdcdc906a49c6822958", size = 3984750, upload-time = "2025-10-21T16:21:44.006Z" }, - { url = "https://files.pythonhosted.org/packages/9e/00/7bd478cbb851c04a48baccaa49b75abaa8e4122f7d86da797500cccdd771/grpcio-1.76.0-cp312-cp312-win_amd64.whl", hash = "sha256:c088e7a90b6017307f423efbb9d1ba97a22aa2170876223f9709e9d1de0b5347", size = 4704003, upload-time = "2025-10-21T16:21:46.244Z" }, -] - -[[package]] -name = "grpcio-tools" -version = "1.62.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "grpcio" }, - { name = "protobuf" }, - { name = "setuptools" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/54/fa/b69bd8040eafc09b88bb0ec0fea59e8aacd1a801e688af087cead213b0d0/grpcio-tools-1.62.3.tar.gz", hash = "sha256:7c7136015c3d62c3eef493efabaf9e3380e3e66d24ee8e94c01cb71377f57833", size = 4538520, upload-time = "2024-08-06T00:37:11.035Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/a5/d6887eba415ce318ae5005e8dfac3fa74892400b54b6d37b79e8b4f14f5e/grpcio_tools-1.62.3-cp312-cp312-macosx_10_10_universal2.whl", hash = "sha256:d102b9b21c4e1e40af9a2ab3c6d41afba6bd29c0aa50ca013bf85c99cdc44ac5", size = 5147690, upload-time = "2024-08-06T00:31:16.436Z" }, - { url = "https://files.pythonhosted.org/packages/8a/7c/3cde447a045e83ceb4b570af8afe67ffc86896a2fe7f59594dc8e5d0a645/grpcio_tools-1.62.3-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:0a52cc9444df978438b8d2332c0ca99000521895229934a59f94f37ed896b133", size = 2720538, upload-time = "2024-08-06T00:31:18.905Z" }, - { url = "https://files.pythonhosted.org/packages/88/07/f83f2750d44ac4f06c07c37395b9c1383ef5c994745f73c6bfaf767f0944/grpcio_tools-1.62.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:141d028bf5762d4a97f981c501da873589df3f7e02f4c1260e1921e565b376fa", size = 3071571, upload-time = "2024-08-06T00:31:21.684Z" }, - { url = "https://files.pythonhosted.org/packages/37/74/40175897deb61e54aca716bc2e8919155b48f33aafec8043dda9592d8768/grpcio_tools-1.62.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47a5c093ab256dec5714a7a345f8cc89315cb57c298b276fa244f37a0ba507f0", size = 2806207, upload-time = "2024-08-06T00:31:24.208Z" }, - { url = "https://files.pythonhosted.org/packages/ec/ee/d8de915105a217cbcb9084d684abdc032030dcd887277f2ef167372287fe/grpcio_tools-1.62.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:f6831fdec2b853c9daa3358535c55eed3694325889aa714070528cf8f92d7d6d", size = 3685815, upload-time = "2024-08-06T00:31:26.917Z" }, - { url = "https://files.pythonhosted.org/packages/fd/d9/4360a6c12be3d7521b0b8c39e5d3801d622fbb81cc2721dbd3eee31e28c8/grpcio_tools-1.62.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e02d7c1a02e3814c94ba0cfe43d93e872c758bd8fd5c2797f894d0c49b4a1dfc", size = 3298378, upload-time = "2024-08-06T00:31:30.401Z" }, - { url = "https://files.pythonhosted.org/packages/29/3b/7cdf4a9e5a3e0a35a528b48b111355cd14da601413a4f887aa99b6da468f/grpcio_tools-1.62.3-cp312-cp312-win32.whl", hash = "sha256:b881fd9505a84457e9f7e99362eeedd86497b659030cf57c6f0070df6d9c2b9b", size = 910416, upload-time = "2024-08-06T00:31:33.118Z" }, - { url = "https://files.pythonhosted.org/packages/6c/66/dd3ec249e44c1cc15e902e783747819ed41ead1336fcba72bf841f72c6e9/grpcio_tools-1.62.3-cp312-cp312-win_amd64.whl", hash = "sha256:11c625eebefd1fd40a228fc8bae385e448c7e32a6ae134e43cf13bbc23f902b7", size = 1052856, upload-time = "2024-08-06T00:31:36.519Z" }, -] - -[[package]] -name = "gunicorn" -version = "23.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "packaging" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/34/72/9614c465dc206155d93eff0ca20d42e1e35afc533971379482de953521a4/gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec", size = 375031, upload-time = "2024-08-10T20:25:27.378Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/7d/6dac2a6e1eba33ee43f318edbed4ff29151a49b5d37f080aad1e6469bca4/gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", size = 85029, upload-time = "2024-08-10T20:25:24.996Z" }, -] - -[[package]] -name = "h11" -version = "0.16.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } -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 = "h2" -version = "4.3.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "hpack" }, - { name = "hyperframe" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/1d/17/afa56379f94ad0fe8defd37d6eb3f89a25404ffc71d4d848893d270325fc/h2-4.3.0.tar.gz", hash = "sha256:6c59efe4323fa18b47a632221a1888bd7fde6249819beda254aeca909f221bf1", size = 2152026, upload-time = "2025-08-23T18:12:19.778Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/69/b2/119f6e6dcbd96f9069ce9a2665e0146588dc9f88f29549711853645e736a/h2-4.3.0-py3-none-any.whl", hash = "sha256:c438f029a25f7945c69e0ccf0fb951dc3f73a5f6412981daee861431b70e2bdd", size = 61779, upload-time = "2025-08-23T18:12:17.779Z" }, -] - -[[package]] -name = "hf-xet" -version = "1.1.10" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/74/31/feeddfce1748c4a233ec1aa5b7396161c07ae1aa9b7bdbc9a72c3c7dd768/hf_xet-1.1.10.tar.gz", hash = "sha256:408aef343800a2102374a883f283ff29068055c111f003ff840733d3b715bb97", size = 487910, upload-time = "2025-09-12T20:10:27.12Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/a2/343e6d05de96908366bdc0081f2d8607d61200be2ac802769c4284cc65bd/hf_xet-1.1.10-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:686083aca1a6669bc85c21c0563551cbcdaa5cf7876a91f3d074a030b577231d", size = 2761466, upload-time = "2025-09-12T20:10:22.836Z" }, - { url = "https://files.pythonhosted.org/packages/31/f9/6215f948ac8f17566ee27af6430ea72045e0418ce757260248b483f4183b/hf_xet-1.1.10-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:71081925383b66b24eedff3013f8e6bbd41215c3338be4b94ba75fd75b21513b", size = 2623807, upload-time = "2025-09-12T20:10:21.118Z" }, - { url = "https://files.pythonhosted.org/packages/15/07/86397573efefff941e100367bbda0b21496ffcdb34db7ab51912994c32a2/hf_xet-1.1.10-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b6bceb6361c80c1cc42b5a7b4e3efd90e64630bcf11224dcac50ef30a47e435", size = 3186960, upload-time = "2025-09-12T20:10:19.336Z" }, - { url = "https://files.pythonhosted.org/packages/01/a7/0b2e242b918cc30e1f91980f3c4b026ff2eedaf1e2ad96933bca164b2869/hf_xet-1.1.10-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:eae7c1fc8a664e54753ffc235e11427ca61f4b0477d757cc4eb9ae374b69f09c", size = 3087167, upload-time = "2025-09-12T20:10:17.255Z" }, - { url = "https://files.pythonhosted.org/packages/4a/25/3e32ab61cc7145b11eee9d745988e2f0f4fafda81b25980eebf97d8cff15/hf_xet-1.1.10-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0a0005fd08f002180f7a12d4e13b22be277725bc23ed0529f8add5c7a6309c06", size = 3248612, upload-time = "2025-09-12T20:10:24.093Z" }, - { url = "https://files.pythonhosted.org/packages/2c/3d/ab7109e607ed321afaa690f557a9ada6d6d164ec852fd6bf9979665dc3d6/hf_xet-1.1.10-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f900481cf6e362a6c549c61ff77468bd59d6dd082f3170a36acfef2eb6a6793f", size = 3353360, upload-time = "2025-09-12T20:10:25.563Z" }, - { url = "https://files.pythonhosted.org/packages/ee/0e/471f0a21db36e71a2f1752767ad77e92d8cde24e974e03d662931b1305ec/hf_xet-1.1.10-cp37-abi3-win_amd64.whl", hash = "sha256:5f54b19cc347c13235ae7ee98b330c26dd65ef1df47e5316ffb1e87713ca7045", size = 2804691, upload-time = "2025-09-12T20:10:28.433Z" }, -] - -[[package]] -name = "hpack" -version = "4.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2c/48/71de9ed269fdae9c8057e5a4c0aa7402e8bb16f2c6e90b3aa53327b113f8/hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca", size = 51276, upload-time = "2025-01-22T21:44:58.347Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/07/c6/80c95b1b2b94682a72cbdbfb85b81ae2daffa4291fbfa1b1464502ede10d/hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496", size = 34357, upload-time = "2025-01-22T21:44:56.92Z" }, -] - -[[package]] -name = "httpcore" -version = "1.0.9" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "h11" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } -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 = "httpx" -version = "0.28.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "certifi" }, - { name = "httpcore" }, - { name = "idna" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, -] - -[package.optional-dependencies] -http2 = [ - { name = "h2" }, -] - -[[package]] -name = "huggingface-hub" -version = "0.35.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "filelock" }, - { name = "fsspec" }, - { name = "hf-xet", marker = "platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, - { name = "packaging" }, - { name = "pyyaml" }, - { name = "requests" }, - { name = "tqdm" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/10/7e/a0a97de7c73671863ca6b3f61fa12518caf35db37825e43d63a70956738c/huggingface_hub-0.35.3.tar.gz", hash = "sha256:350932eaa5cc6a4747efae85126ee220e4ef1b54e29d31c3b45c5612ddf0b32a", size = 461798, upload-time = "2025-09-29T14:29:58.625Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/31/a0/651f93d154cb72323358bf2bbae3e642bdb5d2f1bfc874d096f7cb159fa0/huggingface_hub-0.35.3-py3-none-any.whl", hash = "sha256:0e3a01829c19d86d03793e4577816fe3bdfc1602ac62c7fb220d593d351224ba", size = 564262, upload-time = "2025-09-29T14:29:55.813Z" }, -] - -[[package]] -name = "hyperframe" -version = "6.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/02/e7/94f8232d4a74cc99514c13a9f995811485a6903d48e5d952771ef6322e30/hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08", size = 26566, upload-time = "2025-01-22T21:41:49.302Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/48/30/47d0bf6072f7252e6521f3447ccfa40b421b6824517f82854703d0f5a98b/hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5", size = 13007, upload-time = "2025-01-22T21:41:47.295Z" }, -] - -[[package]] -name = "idna" -version = "3.10" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, -] - -[[package]] -name = "importlib-metadata" -version = "8.5.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "zipp" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cd/12/33e59336dca5be0c398a7482335911a33aa0e20776128f038019f1a95f1b/importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7", size = 55304, upload-time = "2024-09-11T14:56:08.937Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/d9/a1e041c5e7caa9a05c925f4bdbdfb7f006d1f74996af53467bc394c97be7/importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b", size = 26514, upload-time = "2024-09-11T14:56:07.019Z" }, -] - -[[package]] -name = "iniconfig" -version = "2.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, -] - -[[package]] -name = "isodate" -version = "0.7.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/54/4d/e940025e2ce31a8ce1202635910747e5a87cc3a6a6bb2d00973375014749/isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6", size = 29705, upload-time = "2024-10-08T23:04:11.5Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/15/aa/0aca39a37d3c7eb941ba736ede56d689e7be91cab5d9ca846bde3999eba6/isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15", size = 22320, upload-time = "2024-10-08T23:04:09.501Z" }, -] - -[[package]] -name = "itsdangerous" -version = "2.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" }, -] - -[[package]] -name = "jinja2" -version = "3.1.6" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markupsafe" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, -] - -[[package]] -name = "jiter" -version = "0.11.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9d/c0/a3bb4cc13aced219dd18191ea66e874266bd8aa7b96744e495e1c733aa2d/jiter-0.11.0.tar.gz", hash = "sha256:1d9637eaf8c1d6a63d6562f2a6e5ab3af946c66037eb1b894e8fad75422266e4", size = 167094, upload-time = "2025-09-15T09:20:38.212Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ba/b5/3009b112b8f673e568ef79af9863d8309a15f0a8cdcc06ed6092051f377e/jiter-0.11.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:2fb7b377688cc3850bbe5c192a6bd493562a0bc50cbc8b047316428fbae00ada", size = 305510, upload-time = "2025-09-15T09:19:25.893Z" }, - { url = "https://files.pythonhosted.org/packages/fe/82/15514244e03b9e71e086bbe2a6de3e4616b48f07d5f834200c873956fb8c/jiter-0.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a1b7cbe3f25bd0d8abb468ba4302a5d45617ee61b2a7a638f63fee1dc086be99", size = 316521, upload-time = "2025-09-15T09:19:27.525Z" }, - { url = "https://files.pythonhosted.org/packages/92/94/7a2e905f40ad2d6d660e00b68d818f9e29fb87ffe82774f06191e93cbe4a/jiter-0.11.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0a7f0ec81d5b7588c5cade1eb1925b91436ae6726dc2df2348524aeabad5de6", size = 338214, upload-time = "2025-09-15T09:19:28.727Z" }, - { url = "https://files.pythonhosted.org/packages/a8/9c/5791ed5bdc76f12110158d3316a7a3ec0b1413d018b41c5ed399549d3ad5/jiter-0.11.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07630bb46ea2a6b9c6ed986c6e17e35b26148cce2c535454b26ee3f0e8dcaba1", size = 361280, upload-time = "2025-09-15T09:19:30.013Z" }, - { url = "https://files.pythonhosted.org/packages/d4/7f/b7d82d77ff0d2cb06424141000176b53a9e6b16a1125525bb51ea4990c2e/jiter-0.11.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7764f27d28cd4a9cbc61704dfcd80c903ce3aad106a37902d3270cd6673d17f4", size = 487895, upload-time = "2025-09-15T09:19:31.424Z" }, - { url = "https://files.pythonhosted.org/packages/42/44/10a1475d46f1fc1fd5cc2e82c58e7bca0ce5852208e0fa5df2f949353321/jiter-0.11.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1d4a6c4a737d486f77f842aeb22807edecb4a9417e6700c7b981e16d34ba7c72", size = 378421, upload-time = "2025-09-15T09:19:32.746Z" }, - { url = "https://files.pythonhosted.org/packages/9a/5f/0dc34563d8164d31d07bc09d141d3da08157a68dcd1f9b886fa4e917805b/jiter-0.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf408d2a0abd919b60de8c2e7bc5eeab72d4dafd18784152acc7c9adc3291591", size = 347932, upload-time = "2025-09-15T09:19:34.612Z" }, - { url = "https://files.pythonhosted.org/packages/f7/de/b68f32a4fcb7b4a682b37c73a0e5dae32180140cd1caf11aef6ad40ddbf2/jiter-0.11.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cdef53eda7d18e799625023e1e250dbc18fbc275153039b873ec74d7e8883e09", size = 386959, upload-time = "2025-09-15T09:19:35.994Z" }, - { url = "https://files.pythonhosted.org/packages/76/0a/c08c92e713b6e28972a846a81ce374883dac2f78ec6f39a0dad9f2339c3a/jiter-0.11.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:53933a38ef7b551dd9c7f1064f9d7bb235bb3168d0fa5f14f0798d1b7ea0d9c5", size = 517187, upload-time = "2025-09-15T09:19:37.426Z" }, - { url = "https://files.pythonhosted.org/packages/89/b5/4a283bec43b15aad54fcae18d951f06a2ec3f78db5708d3b59a48e9c3fbd/jiter-0.11.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:11840d2324c9ab5162fc1abba23bc922124fedcff0d7b7f85fffa291e2f69206", size = 509461, upload-time = "2025-09-15T09:19:38.761Z" }, - { url = "https://files.pythonhosted.org/packages/34/a5/f8bad793010534ea73c985caaeef8cc22dfb1fedb15220ecdf15c623c07a/jiter-0.11.0-cp312-cp312-win32.whl", hash = "sha256:4f01a744d24a5f2bb4a11657a1b27b61dc038ae2e674621a74020406e08f749b", size = 206664, upload-time = "2025-09-15T09:19:40.096Z" }, - { url = "https://files.pythonhosted.org/packages/ed/42/5823ec2b1469395a160b4bf5f14326b4a098f3b6898fbd327366789fa5d3/jiter-0.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:29fff31190ab3a26de026da2f187814f4b9c6695361e20a9ac2123e4d4378a4c", size = 203520, upload-time = "2025-09-15T09:19:41.798Z" }, -] - -[[package]] -name = "jmespath" -version = "1.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/00/2a/e867e8531cf3e36b41201936b7fa7ba7b5702dbef42922193f05c8976cd6/jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe", size = 25843, upload-time = "2022-06-17T18:00:12.224Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", size = 20256, upload-time = "2022-06-17T18:00:10.251Z" }, -] - -[[package]] -name = "joblib" -version = "1.5.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e8/5d/447af5ea094b9e4c4054f82e223ada074c552335b9b4b2d14bd9b35a67c4/joblib-1.5.2.tar.gz", hash = "sha256:3faa5c39054b2f03ca547da9b2f52fde67c06240c31853f306aea97f13647b55", size = 331077, upload-time = "2025-08-27T12:15:46.575Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/e8/685f47e0d754320684db4425a0967f7d3fa70126bffd76110b7009a0090f/joblib-1.5.2-py3-none-any.whl", hash = "sha256:4e1f0bdbb987e6d843c70cf43714cb276623def372df3c22fe5266b2670bc241", size = 308396, upload-time = "2025-08-27T12:15:45.188Z" }, -] - -[[package]] -name = "json-repair" -version = "0.42.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f4/1e/aea70e484e271e3d841773d5d185bedf488cf8f881088d47faae31f19116/json_repair-0.42.0.tar.gz", hash = "sha256:1a901f706c5b6b4325f0f79b53b0d998c5b327070e98b530da71cc5a3eda8616", size = 31349, upload-time = "2025-04-22T11:12:17.692Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/c7/dd23f764de95771300a8a7ae17293c47ba0e48f826154229d18ecfe147cd/json_repair-0.42.0-py3-none-any.whl", hash = "sha256:7b6805162053dfe65722e961bc51b5eecec0582ec8a8e0fd218d33e8de757daf", size = 21612, upload-time = "2025-04-22T11:12:16.302Z" }, -] - -[[package]] -name = "jsonschema" -version = "4.23.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "attrs" }, - { name = "jsonschema-specifications" }, - { name = "referencing" }, - { name = "rpds-py" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/38/2e/03362ee4034a4c917f697890ccd4aec0800ccf9ded7f511971c75451deec/jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4", size = 325778, upload-time = "2024-07-08T18:40:05.546Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/69/4a/4f9dbeb84e8850557c02365a0eee0649abe5eb1d84af92a25731c6c0f922/jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566", size = 88462, upload-time = "2024-07-08T18:40:00.165Z" }, -] - -[[package]] -name = "jsonschema-specifications" -version = "2025.9.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "referencing" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, -] - -[[package]] -name = "kombu" -version = "5.5.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "amqp" }, - { name = "packaging" }, - { name = "tzdata" }, - { name = "vine" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0f/d3/5ff936d8319ac86b9c409f1501b07c426e6ad41966fedace9ef1b966e23f/kombu-5.5.4.tar.gz", hash = "sha256:886600168275ebeada93b888e831352fe578168342f0d1d5833d88ba0d847363", size = 461992, upload-time = "2025-06-01T10:19:22.281Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/70/a07dcf4f62598c8ad579df241af55ced65bed76e42e45d3c368a6d82dbc1/kombu-5.5.4-py3-none-any.whl", hash = "sha256:a12ed0557c238897d8e518f1d1fdf84bd1516c5e305af2dacd85c2015115feb8", size = 210034, upload-time = "2025-06-01T10:19:20.436Z" }, -] - -[[package]] -name = "litellm" -version = "1.83.10" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "aiohttp" }, - { name = "click" }, - { name = "fastuuid" }, - { name = "httpx" }, - { name = "importlib-metadata" }, - { name = "jinja2" }, - { name = "jsonschema" }, - { name = "openai" }, - { name = "pydantic" }, - { name = "python-dotenv" }, - { name = "tiktoken" }, - { name = "tokenizers" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e2/16/fc51935e406887079f0d7c20427b9a15b9fc1d558e68b4e7072331b70db4/litellm-1.83.10.tar.gz", hash = "sha256:d54eaa98f93a1eb02decf593dbb525fa1ddd4cf03686c1d5c7bb69c2a9ba2a41", size = 14726546, upload-time = "2026-04-19T02:36:28.586Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6f/58/8dd69d5b1ab11f206a9c9f21b6fd191bcdd4fb3ed90b8efbf3a1291fd47c/litellm-1.83.10-py3-none-any.whl", hash = "sha256:55203a7b5551efec8f2fccde29ee045ba057e768591e0b6b9fe1d12f00685ff8", size = 16334780, upload-time = "2026-04-19T02:36:25.274Z" }, -] - -[[package]] -name = "llama-cloud" -version = "0.1.35" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "httpx" }, - { name = "pydantic" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9b/72/816e6e900448e1b4a8137d90e65876b296c5264a23db6ae888bd3e6660ba/llama_cloud-0.1.35.tar.gz", hash = "sha256:200349d5d57424d7461f304cdb1355a58eea3e6ca1e6b0d75c66b2e937216983", size = 106403, upload-time = "2025-07-28T17:22:06.41Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/d2/8d18a021ab757cea231428404f21fe3186bf1ebaac3f57a73c379483fd3f/llama_cloud-0.1.35-py3-none-any.whl", hash = "sha256:b7abab4423118e6f638d2f326749e7a07c6426543bea6da99b623c715b22af71", size = 303280, upload-time = "2025-07-28T17:22:04.946Z" }, -] - -[[package]] -name = "llama-cloud-services" -version = "0.6.54" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "llama-cloud" }, - { name = "llama-index-core" }, - { name = "platformdirs" }, - { name = "pydantic" }, - { name = "python-dotenv" }, - { name = "tenacity" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8a/0c/8ca87d33bea0340a8ed791f36390112aeb29fd3eebfd64b6aef6204a03f0/llama_cloud_services-0.6.54.tar.gz", hash = "sha256:baf65d9bffb68f9dca98ac6e22908b6675b2038b021e657ead1ffc0e43cbd45d", size = 53468, upload-time = "2025-08-01T20:09:20.988Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/48/4e295e3f791b279885a2e584f71e75cbe4ac84e93bba3c36e2668f60a8ac/llama_cloud_services-0.6.54-py3-none-any.whl", hash = "sha256:07f595f7a0ba40c6a1a20543d63024ca7600fe65c4811d1951039977908997be", size = 63874, upload-time = "2025-08-01T20:09:20.076Z" }, -] - -[[package]] -name = "llama-index" -version = "0.14.13" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "llama-index-cli" }, - { name = "llama-index-core" }, - { name = "llama-index-embeddings-openai" }, - { name = "llama-index-indices-managed-llama-cloud" }, - { name = "llama-index-llms-openai" }, - { name = "llama-index-readers-file" }, - { name = "llama-index-readers-llama-parse" }, - { name = "nltk" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/63/26/e46c43ca97f849169d54b1c11e599ea51e650f4c27355c28e384cb0c7e94/llama_index-0.14.13.tar.gz", hash = "sha256:6392822f8ad0e747da2f0adbfcd170bc91fafdff4341cd50da809feae5ca828a", size = 8461, upload-time = "2026-01-21T20:44:49.651Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/10/32/5c13c0b0e7fdedaf4f98a137a64558ee87bbdbc14e16b394c103f6a5418e/llama_index-0.14.13-py3-none-any.whl", hash = "sha256:54b3c15d9327a05c981f332643e15a7adc52168d173302987aeb0f9dc7f0267c", size = 7459, upload-time = "2026-01-21T20:44:47.96Z" }, -] - -[[package]] -name = "llama-index-cli" -version = "0.5.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "llama-index-core" }, - { name = "llama-index-embeddings-openai" }, - { name = "llama-index-llms-openai" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/67/84/41e820efffbe327c38228d3b37fe42512a37e0c3ee4ff6bf97a394e9577a/llama_index_cli-0.5.3.tar.gz", hash = "sha256:ebaf39e785efbfa8d50d837f60cb0f95125c04bf73ed1f92092a2a5f506172f8", size = 24821, upload-time = "2025-09-29T18:03:10.798Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/54/81/b7b3778aa8662913760fbbee77578daf4407aeaa677ccbf0125c4cfa2e67/llama_index_cli-0.5.3-py3-none-any.whl", hash = "sha256:7deb1e953e582bd885443881ce8bd6ab2817b594fef00079dce9993c47d990f7", size = 28173, upload-time = "2025-09-29T18:03:10.024Z" }, -] - -[[package]] -name = "llama-index-core" -version = "0.14.13" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "aiohttp" }, - { name = "aiosqlite" }, - { name = "banks" }, - { name = "dataclasses-json" }, - { name = "deprecated" }, - { name = "dirtyjson" }, - { name = "filetype" }, - { name = "fsspec" }, - { name = "httpx" }, - { name = "llama-index-workflows" }, - { name = "nest-asyncio" }, - { name = "networkx" }, - { name = "nltk" }, - { name = "numpy" }, - { name = "pillow" }, - { name = "platformdirs" }, - { name = "pydantic" }, - { name = "pyyaml" }, - { name = "requests" }, - { name = "setuptools" }, - { name = "sqlalchemy", extra = ["asyncio"] }, - { name = "tenacity" }, - { name = "tiktoken" }, - { name = "tqdm" }, - { name = "typing-extensions" }, - { name = "typing-inspect" }, - { name = "wrapt" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/74/54/d6043a088e5e9c1d62300db7ad0ef417c6b9a92f7b4a5cade066aeafdaca/llama_index_core-0.14.13.tar.gz", hash = "sha256:c3b30d20ae0407e5d0a1d35bb3376a98e242661ebfc22da754b5a3da1f8108c0", size = 11589074, upload-time = "2026-01-21T20:44:16.287Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/76/59/9769f03f1cccadcc014b3b65c166de18999b51459a0f0a579d80f6c91d80/llama_index_core-0.14.13-py3-none-any.whl", hash = "sha256:392f0a5a09433e9dea786964ef5fe5ca2a2b10aee9f979a9507c19a14da2a20a", size = 11934761, upload-time = "2026-01-21T20:44:18.892Z" }, -] - -[[package]] -name = "llama-index-embeddings-openai" -version = "0.5.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "llama-index-core" }, - { name = "openai" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/10/36/90336d054a5061a3f5bc17ac2c18ef63d9d84c55c14d557de484e811ea4d/llama_index_embeddings_openai-0.5.1.tar.gz", hash = "sha256:1c89867a48b0d0daa3d2d44f5e76b394b2b2ef9935932daf921b9e77939ccda8", size = 7020, upload-time = "2025-09-08T20:17:44.681Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/23/4a/8ab11026cf8deff8f555aa73919be0bac48332683111e5fc4290f352dc50/llama_index_embeddings_openai-0.5.1-py3-none-any.whl", hash = "sha256:a2fcda3398bbd987b5ce3f02367caee8e84a56b930fdf43cc1d059aa9fd20ca5", size = 7011, upload-time = "2025-09-08T20:17:44.015Z" }, -] - -[[package]] -name = "llama-index-indices-managed-llama-cloud" -version = "0.9.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "deprecated" }, - { name = "llama-cloud" }, - { name = "llama-index-core" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/61/4a/79044fcb3209583d1ffe0c2a7c19dddfb657a03faeb9fe0cf5a74027e646/llama_index_indices_managed_llama_cloud-0.9.4.tar.gz", hash = "sha256:b5e00752ab30564abf19c57595a2107f5697c3b03b085817b4fca84a38ebbd59", size = 15146, upload-time = "2025-09-08T20:29:58.673Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a6/6a/0e33245df06afc9766c46a1fe92687be8a09da5d0d0128bc08d84a9f5efa/llama_index_indices_managed_llama_cloud-0.9.4-py3-none-any.whl", hash = "sha256:535a08811046803ca6ab7f8e9d510e926aa5306608b02201ad3d9d21701383bc", size = 17005, upload-time = "2025-09-08T20:29:57.876Z" }, -] - -[[package]] -name = "llama-index-instrumentation" -version = "0.4.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "deprecated" }, - { name = "pydantic" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/70/e5/a3628da5d716d6bbc2c0a8d39b629dff81b33d5625c5b934e1456370064f/llama_index_instrumentation-0.4.1.tar.gz", hash = "sha256:a79d0dd2baba34f05ff4354d63a99b212322635b8afa6cc96ed00a7e11ebfdc3", size = 45788, upload-time = "2025-09-15T03:53:00.219Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/7a/c414f4dc9a7dd90d050c387489436bab2d678a566b704ede2f5b62f82ad7/llama_index_instrumentation-0.4.1-py3-none-any.whl", hash = "sha256:0d3ac926d0db3d39c0ec34ee72da5322d61e06b87fe956407e4a1e7a2708b936", size = 15063, upload-time = "2025-09-15T03:52:59.098Z" }, -] - -[[package]] -name = "llama-index-llms-openai" -version = "0.6.17" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "llama-index-core" }, - { name = "openai" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c8/94/55e9855f565b30604fe9e154a7c3df1f4437ce62ed8926a8d6b5112cf73b/llama_index_llms_openai-0.6.17.tar.gz", hash = "sha256:63294c1d8d221d2009023b2c294c919f6141f4cdce9cd48bffe700d8be2a37e0", size = 25941, upload-time = "2026-02-03T11:03:26.355Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/85/1dee39a45b31528611cc6139777fc5631b9946fe085b37cd981062aa6d50/llama_index_llms_openai-0.6.17-py3-none-any.whl", hash = "sha256:d275f7e218611afc6bb157fe722f6e8590e8e20ac3ea1392cf560ac674c175af", size = 26940, upload-time = "2026-02-03T11:03:25.059Z" }, -] - -[[package]] -name = "llama-index-readers-file" -version = "0.5.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "beautifulsoup4" }, - { name = "defusedxml" }, - { name = "llama-index-core" }, - { name = "pandas" }, - { name = "pypdf" }, - { name = "striprtf" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/01/d9/c67ad2b9cba8dacf1d4a55fe5432357b6eceaecfb096a0de5c1cbd959b98/llama_index_readers_file-0.5.4.tar.gz", hash = "sha256:5e766f32597622e66529464101914548ad683770a0a5d2bdc9ee84eb3a110332", size = 32565, upload-time = "2025-09-08T20:39:40.287Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ea/e3/76d72a7281b9c88d488908731c9034e1ee1a2cad5aa1dead76b051eca989/llama_index_readers_file-0.5.4-py3-none-any.whl", hash = "sha256:135be5ddda66c5b35883911918b2d99f67a2ab010d180af5630c872ea9509d45", size = 51827, upload-time = "2025-09-08T20:39:39.408Z" }, -] - -[[package]] -name = "llama-index-readers-llama-parse" -version = "0.5.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "llama-index-core" }, - { name = "llama-parse" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b3/77/5bfaab20e6ec8428dbf2352e18be550c957602723d69383908176b5686cd/llama_index_readers_llama_parse-0.5.1.tar.gz", hash = "sha256:2b78b73faa933e30e6c69df351e4e9f36dfe2ae142e2ab3969ddd2ac48930e37", size = 3858, upload-time = "2025-09-08T20:41:29.201Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/68/81/52410c7245dcbf1a54756a9ce3892cdd167ec0b884d696de1304ca3f452e/llama_index_readers_llama_parse-0.5.1-py3-none-any.whl", hash = "sha256:0d41450ed29b0c49c024e206ef6c8e662b1854e77a1c5faefed3b958be54f880", size = 3203, upload-time = "2025-09-08T20:41:28.438Z" }, -] - -[[package]] -name = "llama-index-vector-stores-milvus" -version = "0.9.6" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "llama-index-core" }, - { name = "pymilvus" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ef/79/72bf70fac3b77770a5c2b1b7d441aa1998bb522d50fe88b0f9c4071854e5/llama_index_vector_stores_milvus-0.9.6.tar.gz", hash = "sha256:6d38ac5939a570e0240687f54fbee4e1ff6c5faa2d28d25377a3f38d2ca07e2b", size = 15584, upload-time = "2026-01-13T11:46:41.394Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/51/04/98359849c095b5a3eb06f0649865a30b5a733b9fdbfeac3c6951253f804c/llama_index_vector_stores_milvus-0.9.6-py3-none-any.whl", hash = "sha256:916cbd9b07035ec137905970ef6a49dd77d3ece6e0a79271db35705cca5f5f84", size = 15792, upload-time = "2026-01-13T11:46:39.708Z" }, -] - -[[package]] -name = "llama-index-vector-stores-pinecone" -version = "0.7.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "llama-index-core" }, - { name = "pinecone" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5c/a0/2e2e969a133894f10b3a55b5148feef0c546ca8047b461f51f79d115c5b9/llama_index_vector_stores_pinecone-0.7.1.tar.gz", hash = "sha256:0ab3cc44f309bca1d74e58f221dade672169da01561114b067f4734293bd0280", size = 7852, upload-time = "2025-09-08T20:28:54.11Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/93/22/ae8c3073e4866a41eb53030db7cfecca1d84192c16a67d40a76f8d593e6d/llama_index_vector_stores_pinecone-0.7.1-py3-none-any.whl", hash = "sha256:861c4d01b3766cdca318f1285c03cd5e52dabf3d2f136cb38db421b16103129a", size = 8041, upload-time = "2025-09-08T20:28:53.406Z" }, -] - -[[package]] -name = "llama-index-vector-stores-postgres" -version = "0.7.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "asyncpg" }, - { name = "llama-index-core" }, - { name = "pgvector" }, - { name = "psycopg2-binary" }, - { name = "sqlalchemy", extra = ["asyncio"] }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ef/78/04ff0cb9e14b8c1c3cb8716fab35c95ec2a4b551d769c65031c5c8624337/llama_index_vector_stores_postgres-0.7.3.tar.gz", hash = "sha256:7b5c62e462d681d7b8d8668b93e5b0023bfd3aaafcf76e2b4bfcf885dc3b49c6", size = 11950, upload-time = "2026-01-22T15:14:13.007Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/de/140f678d930ea869fc989adaa0140b9267c5ee0c7b971d061112d0d5b75a/llama_index_vector_stores_postgres-0.7.3-py3-none-any.whl", hash = "sha256:65b70266cc6041ab5011d64d1183d8783112ba5b38eb32ca21e00ea5b96aa058", size = 11635, upload-time = "2026-01-22T15:14:13.722Z" }, -] - -[[package]] -name = "llama-index-vector-stores-qdrant" -version = "0.9.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "grpcio" }, - { name = "llama-index-core" }, - { name = "qdrant-client" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/99/63/646ec9d7035429d4fb5488146ab7a9d55e955fb855f0a7e7fc29f6eb136f/llama_index_vector_stores_qdrant-0.9.1.tar.gz", hash = "sha256:215e24278bde44c6746d60c7df3f8811f943c20524a496e0c954eeb6449e8319", size = 14688, upload-time = "2026-01-13T11:13:07.123Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/d2/e258a2b5526fe42d6ca60a054b3830948d6411e3f075de542ba492c5f5d1/llama_index_vector_stores_qdrant-0.9.1-py3-none-any.whl", hash = "sha256:de71d0e867c04c87aa81ab35b092c32d684961c26a1cda601856faf29b21a598", size = 14944, upload-time = "2026-01-13T11:13:08.08Z" }, -] - -[[package]] -name = "llama-index-vector-stores-weaviate" -version = "1.4.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "llama-index-core" }, - { name = "weaviate-client" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/25/75/bc95030d2c4a7c1c225a0035117442f4cd9fc1d975bde7abbfc4e94601a9/llama_index_vector_stores_weaviate-1.4.1.tar.gz", hash = "sha256:9fa3aa71732066f15349ddc796fbe230e74d345726db21d340334b0c4ebfdd3c", size = 8537, upload-time = "2025-09-08T20:45:46.877Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ea/cd/c70c0006a1d82f472700e6fcad9bad953f48405bb524c87888b8de215f7e/llama_index_vector_stores_weaviate-1.4.1-py3-none-any.whl", hash = "sha256:a7f32be4e1963b9718f9cbc12ac7f38db1a0a1041ba6fe45d809260a8d43f123", size = 9325, upload-time = "2025-09-08T20:45:45.845Z" }, -] - -[[package]] -name = "llama-index-workflows" -version = "2.13.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "llama-index-instrumentation" }, - { name = "pydantic" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/49/7b/00c35d14dc4a7dc64c63dad8b1532f55cd5b5f8856c34f5bf587693ac270/llama_index_workflows-2.13.1.tar.gz", hash = "sha256:55cd3cff9c92a37272ab8651ad750288abc339165b009066f130cb0c5c65994b", size = 84279, upload-time = "2026-01-25T14:51:50.493Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/8d/ba97cda62829b60658a86d4c012d59e3b13c99d9b336fcf0e507d03812df/llama_index_workflows-2.13.1-py3-none-any.whl", hash = "sha256:e779078817d413b29a5297521fb71694a80e502f18dfd41e8b342f83e45f2c19", size = 107344, upload-time = "2026-01-25T14:51:49.297Z" }, -] - -[[package]] -name = "llama-parse" -version = "0.6.54" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "llama-cloud-services" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/08/f6/93b5d123c480bc8c93e6dc3ea930f4f8df8da27f829bb011100ba3ce23dc/llama_parse-0.6.54.tar.gz", hash = "sha256:c707b31152155c9bae84e316fab790bbc8c85f4d8825ce5ee386ebeb7db258f1", size = 3577, upload-time = "2025-08-01T20:09:23.762Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/05/50/c5ccd2a50daa0a10c7f3f7d4e6992392454198cd8a7d99fcb96cb60d0686/llama_parse-0.6.54-py3-none-any.whl", hash = "sha256:c66c8d51cf6f29a44eaa8595a595de5d2598afc86e5a33a4cebe5fe228036920", size = 4879, upload-time = "2025-08-01T20:09:22.651Z" }, -] - -[[package]] -name = "llmwhisperer-client" -version = "2.6.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "requests" }, - { name = "tenacity" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/38/44/18d4158618ebbd76ceb8e43b8deb77f4983e6f1ccff2dffd73d6f3fb1628/llmwhisperer_client-2.6.2.tar.gz", hash = "sha256:ce846af62e7e7337dfcfe2960ec72de2989457b717ab7b9dd4110ee82c002ed0", size = 3268197, upload-time = "2026-02-23T10:52:17.634Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/27/eb/0f9edd21302eddc6020a1b43a78e27f361e8b6b8af7611134a58487f7d8a/llmwhisperer_client-2.6.2-py3-none-any.whl", hash = "sha256:7226344506bc85a663e4d4f8feb763f853ec9bcb6cea9bd9cf170ba135c50cdd", size = 10857, upload-time = "2026-02-23T10:52:16.325Z" }, -] - -[[package]] -name = "markupsafe" -version = "3.0.3" -source = { registry = "https://pypi.org/simple" } -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/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]] -name = "marshmallow" -version = "3.26.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "packaging" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ab/5e/5e53d26b42ab75491cda89b871dab9e97c840bf12c63ec58a1919710cd06/marshmallow-3.26.1.tar.gz", hash = "sha256:e6d8affb6cb61d39d26402096dc0aee12d5a26d490a121f118d2e81dc0719dc6", size = 221825, upload-time = "2025-02-03T15:32:25.093Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/34/75/51952c7b2d3873b44a0028b1bd26a25078c18f92f256608e8d1dc61b39fd/marshmallow-3.26.1-py3-none-any.whl", hash = "sha256:3350409f20a70a7e4e11a27661187b77cdcaeb20abca41c1454fe33636bea09c", size = 50878, upload-time = "2025-02-03T15:32:22.295Z" }, -] - -[[package]] -name = "mbstrdecoder" -version = "1.1.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "chardet" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/31/ab/05ae008357c8bdb6245ebf8a101d99f26c096e0ea20800b318153da23796/mbstrdecoder-1.1.4.tar.gz", hash = "sha256:8105ef9cf6b7d7d69fe7fd6b68a2d8f281ca9b365d7a9b670be376b2e6c81b21", size = 14527, upload-time = "2025-01-18T10:07:31.089Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/30/ac/5ce64a1d4cce00390beab88622a290420401f1cabf05caf2fc0995157c21/mbstrdecoder-1.1.4-py3-none-any.whl", hash = "sha256:03dae4ec50ec0d2ff4743e63fdbd5e0022815857494d35224b60775d3d934a8c", size = 7933, upload-time = "2025-01-18T10:07:29.562Z" }, -] - -[[package]] -name = "milvus-lite" -version = "2.5.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "tqdm" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/b2/acc5024c8e8b6a0b034670b8e8af306ebd633ede777dcbf557eac4785937/milvus_lite-2.5.1-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:6b014453200ba977be37ba660cb2d021030375fa6a35bc53c2e1d92980a0c512", size = 27934713, upload-time = "2025-06-30T04:23:37.028Z" }, - { url = "https://files.pythonhosted.org/packages/9b/2e/746f5bb1d6facd1e73eb4af6dd5efda11125b0f29d7908a097485ca6cad9/milvus_lite-2.5.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a2e031088bf308afe5f8567850412d618cfb05a65238ed1a6117f60decccc95a", size = 24421451, upload-time = "2025-06-30T04:23:51.747Z" }, - { url = "https://files.pythonhosted.org/packages/2e/cf/3d1fee5c16c7661cf53977067a34820f7269ed8ba99fe9cf35efc1700866/milvus_lite-2.5.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:a13277e9bacc6933dea172e42231f7e6135bd3bdb073dd2688ee180418abd8d9", size = 45337093, upload-time = "2025-06-30T04:24:06.706Z" }, - { url = "https://files.pythonhosted.org/packages/d3/82/41d9b80f09b82e066894d9b508af07b7b0fa325ce0322980674de49106a0/milvus_lite-2.5.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:25ce13f4b8d46876dd2b7ac8563d7d8306da7ff3999bb0d14b116b30f71d706c", size = 55263911, upload-time = "2025-06-30T04:24:19.434Z" }, -] - -[[package]] -name = "msal" -version = "1.34.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cryptography" }, - { name = "pyjwt", extra = ["crypto"] }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cf/0e/c857c46d653e104019a84f22d4494f2119b4fe9f896c92b4b864b3b045cc/msal-1.34.0.tar.gz", hash = "sha256:76ba83b716ea5a6d75b0279c0ac353a0e05b820ca1f6682c0eb7f45190c43c2f", size = 153961, upload-time = "2025-09-22T23:05:48.989Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/dc/18d48843499e278538890dc709e9ee3dea8375f8be8e82682851df1b48b5/msal-1.34.0-py3-none-any.whl", hash = "sha256:f669b1644e4950115da7a176441b0e13ec2975c29528d8b9e81316023676d6e1", size = 116987, upload-time = "2025-09-22T23:05:47.294Z" }, -] - -[[package]] -name = "msal-extensions" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "msal" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/01/99/5d239b6156eddf761a636bded1118414d161bd6b7b37a9335549ed159396/msal_extensions-1.3.1.tar.gz", hash = "sha256:c5b0fd10f65ef62b5f1d62f4251d51cbcaf003fcedae8c91b040a488614be1a4", size = 23315, upload-time = "2025-03-14T23:51:03.902Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/75/bd9b7bb966668920f06b200e84454c8f3566b102183bc55c5473d96cb2b9/msal_extensions-1.3.1-py3-none-any.whl", hash = "sha256:96d3de4d034504e969ac5e85bae8106c8373b5c6568e4c8fa7af2eca9dbe6bca", size = 20583, upload-time = "2025-03-14T23:51:03.016Z" }, -] - -[[package]] -name = "multidict" -version = "6.6.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/69/7f/0652e6ed47ab288e3756ea9c0df8b14950781184d4bd7883f4d87dd41245/multidict-6.6.4.tar.gz", hash = "sha256:d2d4e4787672911b48350df02ed3fa3fffdc2f2e8ca06dd6afdf34189b76a9dd", size = 101843, upload-time = "2025-08-11T12:08:48.217Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/05/f6/512ffd8fd8b37fb2680e5ac35d788f1d71bbaf37789d21a820bdc441e565/multidict-6.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0ffb87be160942d56d7b87b0fdf098e81ed565add09eaa1294268c7f3caac4c8", size = 76516, upload-time = "2025-08-11T12:06:53.393Z" }, - { url = "https://files.pythonhosted.org/packages/99/58/45c3e75deb8855c36bd66cc1658007589662ba584dbf423d01df478dd1c5/multidict-6.6.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d191de6cbab2aff5de6c5723101705fd044b3e4c7cfd587a1929b5028b9714b3", size = 45394, upload-time = "2025-08-11T12:06:54.555Z" }, - { url = "https://files.pythonhosted.org/packages/fd/ca/e8c4472a93a26e4507c0b8e1f0762c0d8a32de1328ef72fd704ef9cc5447/multidict-6.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:38a0956dd92d918ad5feff3db8fcb4a5eb7dba114da917e1a88475619781b57b", size = 43591, upload-time = "2025-08-11T12:06:55.672Z" }, - { url = "https://files.pythonhosted.org/packages/05/51/edf414f4df058574a7265034d04c935aa84a89e79ce90fcf4df211f47b16/multidict-6.6.4-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:6865f6d3b7900ae020b495d599fcf3765653bc927951c1abb959017f81ae8287", size = 237215, upload-time = "2025-08-11T12:06:57.213Z" }, - { url = "https://files.pythonhosted.org/packages/c8/45/8b3d6dbad8cf3252553cc41abea09ad527b33ce47a5e199072620b296902/multidict-6.6.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a2088c126b6f72db6c9212ad827d0ba088c01d951cee25e758c450da732c138", size = 258299, upload-time = "2025-08-11T12:06:58.946Z" }, - { url = "https://files.pythonhosted.org/packages/3c/e8/8ca2e9a9f5a435fc6db40438a55730a4bf4956b554e487fa1b9ae920f825/multidict-6.6.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0f37bed7319b848097085d7d48116f545985db988e2256b2e6f00563a3416ee6", size = 242357, upload-time = "2025-08-11T12:07:00.301Z" }, - { url = "https://files.pythonhosted.org/packages/0f/84/80c77c99df05a75c28490b2af8f7cba2a12621186e0a8b0865d8e745c104/multidict-6.6.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:01368e3c94032ba6ca0b78e7ccb099643466cf24f8dc8eefcfdc0571d56e58f9", size = 268369, upload-time = "2025-08-11T12:07:01.638Z" }, - { url = "https://files.pythonhosted.org/packages/0d/e9/920bfa46c27b05fb3e1ad85121fd49f441492dca2449c5bcfe42e4565d8a/multidict-6.6.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fe323540c255db0bffee79ad7f048c909f2ab0edb87a597e1c17da6a54e493c", size = 269341, upload-time = "2025-08-11T12:07:02.943Z" }, - { url = "https://files.pythonhosted.org/packages/af/65/753a2d8b05daf496f4a9c367fe844e90a1b2cac78e2be2c844200d10cc4c/multidict-6.6.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8eb3025f17b0a4c3cd08cda49acf312a19ad6e8a4edd9dbd591e6506d999402", size = 256100, upload-time = "2025-08-11T12:07:04.564Z" }, - { url = "https://files.pythonhosted.org/packages/09/54/655be13ae324212bf0bc15d665a4e34844f34c206f78801be42f7a0a8aaa/multidict-6.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bbc14f0365534d35a06970d6a83478b249752e922d662dc24d489af1aa0d1be7", size = 253584, upload-time = "2025-08-11T12:07:05.914Z" }, - { url = "https://files.pythonhosted.org/packages/5c/74/ab2039ecc05264b5cec73eb018ce417af3ebb384ae9c0e9ed42cb33f8151/multidict-6.6.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:75aa52fba2d96bf972e85451b99d8e19cc37ce26fd016f6d4aa60da9ab2b005f", size = 251018, upload-time = "2025-08-11T12:07:08.301Z" }, - { url = "https://files.pythonhosted.org/packages/af/0a/ccbb244ac848e56c6427f2392741c06302bbfba49c0042f1eb3c5b606497/multidict-6.6.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4fefd4a815e362d4f011919d97d7b4a1e566f1dde83dc4ad8cfb5b41de1df68d", size = 251477, upload-time = "2025-08-11T12:07:10.248Z" }, - { url = "https://files.pythonhosted.org/packages/0e/b0/0ed49bba775b135937f52fe13922bc64a7eaf0a3ead84a36e8e4e446e096/multidict-6.6.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:db9801fe021f59a5b375ab778973127ca0ac52429a26e2fd86aa9508f4d26eb7", size = 263575, upload-time = "2025-08-11T12:07:11.928Z" }, - { url = "https://files.pythonhosted.org/packages/3e/d9/7fb85a85e14de2e44dfb6a24f03c41e2af8697a6df83daddb0e9b7569f73/multidict-6.6.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a650629970fa21ac1fb06ba25dabfc5b8a2054fcbf6ae97c758aa956b8dba802", size = 259649, upload-time = "2025-08-11T12:07:13.244Z" }, - { url = "https://files.pythonhosted.org/packages/03/9e/b3a459bcf9b6e74fa461a5222a10ff9b544cb1cd52fd482fb1b75ecda2a2/multidict-6.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:452ff5da78d4720d7516a3a2abd804957532dd69296cb77319c193e3ffb87e24", size = 251505, upload-time = "2025-08-11T12:07:14.57Z" }, - { url = "https://files.pythonhosted.org/packages/86/a2/8022f78f041dfe6d71e364001a5cf987c30edfc83c8a5fb7a3f0974cff39/multidict-6.6.4-cp312-cp312-win32.whl", hash = "sha256:8c2fcb12136530ed19572bbba61b407f655e3953ba669b96a35036a11a485793", size = 41888, upload-time = "2025-08-11T12:07:15.904Z" }, - { url = "https://files.pythonhosted.org/packages/c7/eb/d88b1780d43a56db2cba24289fa744a9d216c1a8546a0dc3956563fd53ea/multidict-6.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:047d9425860a8c9544fed1b9584f0c8bcd31bcde9568b047c5e567a1025ecd6e", size = 46072, upload-time = "2025-08-11T12:07:17.045Z" }, - { url = "https://files.pythonhosted.org/packages/9f/16/b929320bf5750e2d9d4931835a4c638a19d2494a5b519caaaa7492ebe105/multidict-6.6.4-cp312-cp312-win_arm64.whl", hash = "sha256:14754eb72feaa1e8ae528468f24250dd997b8e2188c3d2f593f9eba259e4b364", size = 43222, upload-time = "2025-08-11T12:07:18.328Z" }, - { url = "https://files.pythonhosted.org/packages/fd/69/b547032297c7e63ba2af494edba695d781af8a0c6e89e4d06cf848b21d80/multidict-6.6.4-py3-none-any.whl", hash = "sha256:27d8f8e125c07cb954e54d75d04905a9bba8a439c1d84aca94949d4d03d8601c", size = 12313, upload-time = "2025-08-11T12:08:46.891Z" }, -] - -[[package]] -name = "mypy-extensions" -version = "1.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, -] - -[[package]] -name = "nest-asyncio" -version = "1.6.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418, upload-time = "2024-01-21T14:25:19.227Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" }, -] - -[[package]] -name = "networkx" -version = "3.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6c/4f/ccdb8ad3a38e583f214547fd2f7ff1fc160c43a75af88e6aec213404b96a/networkx-3.5.tar.gz", hash = "sha256:d4c6f9cf81f52d69230866796b82afbccdec3db7ae4fbd1b65ea750feed50037", size = 2471065, upload-time = "2025-05-29T11:35:07.804Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/8d/776adee7bbf76365fdd7f2552710282c79a4ead5d2a46408c9043a2b70ba/networkx-3.5-py3-none-any.whl", hash = "sha256:0030d386a9a06dee3565298b4a734b68589749a544acbb6c412dc9e2489ec6ec", size = 2034406, upload-time = "2025-05-29T11:35:04.961Z" }, -] - -[[package]] -name = "nltk" -version = "3.9.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "joblib" }, - { name = "regex" }, - { name = "tqdm" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3c/87/db8be88ad32c2d042420b6fd9ffd4a149f9a0d7f0e86b3f543be2eeeedd2/nltk-3.9.1.tar.gz", hash = "sha256:87d127bd3de4bd89a4f81265e5fa59cb1b199b27440175370f7417d2bc7ae868", size = 2904691, upload-time = "2024-08-18T19:48:37.769Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/66/7d9e26593edda06e8cb531874633f7c2372279c3b0f46235539fe546df8b/nltk-3.9.1-py3-none-any.whl", hash = "sha256:4fa26829c5b00715afe3061398a8989dc643b92ce7dd93fb4585a70930d168a1", size = 1505442, upload-time = "2024-08-18T19:48:21.909Z" }, -] - -[[package]] -name = "numpy" -version = "2.3.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d0/19/95b3d357407220ed24c139018d2518fab0a61a948e68286a25f1a4d049ff/numpy-2.3.3.tar.gz", hash = "sha256:ddc7c39727ba62b80dfdbedf400d1c10ddfa8eefbd7ec8dcb118be8b56d31029", size = 20576648, upload-time = "2025-09-09T16:54:12.543Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/51/5d/bb7fc075b762c96329147799e1bcc9176ab07ca6375ea976c475482ad5b3/numpy-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cfdd09f9c84a1a934cde1eec2267f0a43a7cd44b2cca4ff95b7c0d14d144b0bf", size = 20957014, upload-time = "2025-09-09T15:56:29.966Z" }, - { url = "https://files.pythonhosted.org/packages/6b/0e/c6211bb92af26517acd52125a237a92afe9c3124c6a68d3b9f81b62a0568/numpy-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb32e3cf0f762aee47ad1ddc6672988f7f27045b0783c887190545baba73aa25", size = 14185220, upload-time = "2025-09-09T15:56:32.175Z" }, - { url = "https://files.pythonhosted.org/packages/22/f2/07bb754eb2ede9073f4054f7c0286b0d9d2e23982e090a80d478b26d35ca/numpy-2.3.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:396b254daeb0a57b1fe0ecb5e3cff6fa79a380fa97c8f7781a6d08cd429418fe", size = 5113918, upload-time = "2025-09-09T15:56:34.175Z" }, - { url = "https://files.pythonhosted.org/packages/81/0a/afa51697e9fb74642f231ea36aca80fa17c8fb89f7a82abd5174023c3960/numpy-2.3.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:067e3d7159a5d8f8a0b46ee11148fc35ca9b21f61e3c49fbd0a027450e65a33b", size = 6647922, upload-time = "2025-09-09T15:56:36.149Z" }, - { url = "https://files.pythonhosted.org/packages/5d/f5/122d9cdb3f51c520d150fef6e87df9279e33d19a9611a87c0d2cf78a89f4/numpy-2.3.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c02d0629d25d426585fb2e45a66154081b9fa677bc92a881ff1d216bc9919a8", size = 14281991, upload-time = "2025-09-09T15:56:40.548Z" }, - { url = "https://files.pythonhosted.org/packages/51/64/7de3c91e821a2debf77c92962ea3fe6ac2bc45d0778c1cbe15d4fce2fd94/numpy-2.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9192da52b9745f7f0766531dcfa978b7763916f158bb63bdb8a1eca0068ab20", size = 16641643, upload-time = "2025-09-09T15:56:43.343Z" }, - { url = "https://files.pythonhosted.org/packages/30/e4/961a5fa681502cd0d68907818b69f67542695b74e3ceaa513918103b7e80/numpy-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cd7de500a5b66319db419dc3c345244404a164beae0d0937283b907d8152e6ea", size = 16056787, upload-time = "2025-09-09T15:56:46.141Z" }, - { url = "https://files.pythonhosted.org/packages/99/26/92c912b966e47fbbdf2ad556cb17e3a3088e2e1292b9833be1dfa5361a1a/numpy-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:93d4962d8f82af58f0b2eb85daaf1b3ca23fe0a85d0be8f1f2b7bb46034e56d7", size = 18579598, upload-time = "2025-09-09T15:56:49.844Z" }, - { url = "https://files.pythonhosted.org/packages/17/b6/fc8f82cb3520768718834f310c37d96380d9dc61bfdaf05fe5c0b7653e01/numpy-2.3.3-cp312-cp312-win32.whl", hash = "sha256:5534ed6b92f9b7dca6c0a19d6df12d41c68b991cef051d108f6dbff3babc4ebf", size = 6320800, upload-time = "2025-09-09T15:56:52.499Z" }, - { url = "https://files.pythonhosted.org/packages/32/ee/de999f2625b80d043d6d2d628c07d0d5555a677a3cf78fdf868d409b8766/numpy-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:497d7cad08e7092dba36e3d296fe4c97708c93daf26643a1ae4b03f6294d30eb", size = 12786615, upload-time = "2025-09-09T15:56:54.422Z" }, - { url = "https://files.pythonhosted.org/packages/49/6e/b479032f8a43559c383acb20816644f5f91c88f633d9271ee84f3b3a996c/numpy-2.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:ca0309a18d4dfea6fc6262a66d06c26cfe4640c3926ceec90e57791a82b6eee5", size = 10195936, upload-time = "2025-09-09T15:56:56.541Z" }, -] - -[[package]] -name = "oauthlib" -version = "3.3.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0b/5f/19930f824ffeb0ad4372da4812c50edbd1434f678c90c2733e1188edfc63/oauthlib-3.3.1.tar.gz", hash = "sha256:0f0f8aa759826a193cf66c12ea1af1637f87b9b4622d46e866952bb022e538c9", size = 185918, upload-time = "2025-06-19T22:48:08.269Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/be/9c/92789c596b8df838baa98fa71844d84283302f7604ed565dafe5a6b5041a/oauthlib-3.3.1-py3-none-any.whl", hash = "sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1", size = 160065, upload-time = "2025-06-19T22:48:06.508Z" }, -] - -[[package]] -name = "openai" -version = "2.24.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/55/13/17e87641b89b74552ed408a92b231283786523edddc95f3545809fab673c/openai-2.24.0.tar.gz", hash = "sha256:1e5769f540dbd01cb33bc4716a23e67b9d695161a734aff9c5f925e2bf99a673", size = 658717, upload-time = "2026-02-24T20:02:07.958Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c9/30/844dc675ee6902579b8eef01ed23917cc9319a1c9c0c14ec6e39340c96d0/openai-2.24.0-py3-none-any.whl", hash = "sha256:fed30480d7d6c884303287bde864980a4b137b60553ffbcf9ab4a233b7a73d94", size = 1120122, upload-time = "2026-02-24T20:02:05.669Z" }, -] - -[[package]] -name = "opentelemetry-api" -version = "1.37.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "importlib-metadata" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/63/04/05040d7ce33a907a2a02257e601992f0cdf11c73b33f13c4492bf6c3d6d5/opentelemetry_api-1.37.0.tar.gz", hash = "sha256:540735b120355bd5112738ea53621f8d5edb35ebcd6fe21ada3ab1c61d1cd9a7", size = 64923, upload-time = "2025-09-11T10:29:01.662Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/91/48/28ed9e55dcf2f453128df738210a980e09f4e468a456fa3c763dbc8be70a/opentelemetry_api-1.37.0-py3-none-any.whl", hash = "sha256:accf2024d3e89faec14302213bc39550ec0f4095d1cf5ca688e1bfb1c8612f47", size = 65732, upload-time = "2025-09-11T10:28:41.826Z" }, -] - -[[package]] -name = "opentelemetry-distro" -version = "0.58b0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "opentelemetry-api" }, - { name = "opentelemetry-instrumentation" }, - { name = "opentelemetry-sdk" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c3/20/597f387b42c649bac39af9ff8ad5bfdc163ce1a30cdecb16474ab8e57905/opentelemetry_distro-0.58b0.tar.gz", hash = "sha256:ef993c845c11fd156046a96e5ffe1ecfe33f7282fa6149cf9decb26ff8716666", size = 2583, upload-time = "2025-09-11T11:42:12.034Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/84/34/53016553489592262408b72e94466403da3c84ebe044b073bbcc1a6b228b/opentelemetry_distro-0.58b0-py3-none-any.whl", hash = "sha256:d90dddc3ae93d60d917a267a0099bd72f87fa3454b49ca8799f97cb58c777ef4", size = 3346, upload-time = "2025-09-11T11:40:56.853Z" }, -] - -[[package]] -name = "opentelemetry-exporter-otlp" -version = "1.15.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "opentelemetry-exporter-otlp-proto-grpc" }, - { name = "opentelemetry-exporter-otlp-proto-http" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d9/bd/abafe13a0d77145270a39de7442d12d71b51a9f9d103d15d636110ae8a21/opentelemetry_exporter_otlp-1.15.0.tar.gz", hash = "sha256:4f7c49751d9720e2e726e13b0bb958ccade4e29122c305d92c033da432c8d2c5", size = 6126, upload-time = "2022-12-09T22:28:43.353Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/64/a2/4956610bd5348977fea8818d488793a46d1359337c0226164f093a17c61c/opentelemetry_exporter_otlp-1.15.0-py3-none-any.whl", hash = "sha256:79f22748b6a54808a0448093dfa189c8490e729f67c134d4c992533d9393b33e", size = 6976, upload-time = "2022-12-09T22:28:12.944Z" }, -] - -[[package]] -name = "opentelemetry-exporter-otlp-proto-grpc" -version = "1.15.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "backoff" }, - { name = "googleapis-common-protos" }, - { name = "grpcio" }, - { name = "opentelemetry-api" }, - { name = "opentelemetry-proto" }, - { name = "opentelemetry-sdk" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e4/ab/1be294b194af410f350f867a54621b4f33b7551adce2ae795e907148fc1e/opentelemetry_exporter_otlp_proto_grpc-1.15.0.tar.gz", hash = "sha256:844f2a4bb9bcda34e4eb6fe36765e5031aacb36dc60ed88c90fc246942ea26e7", size = 27262, upload-time = "2022-12-09T22:28:44.359Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dd/8f/73ad108bcfd61b4169be5ad8b76acaf9158f224740da10ab9ea3469d551a/opentelemetry_exporter_otlp_proto_grpc-1.15.0-py3-none-any.whl", hash = "sha256:c2a5492ba7d140109968135d641d06ce3c5bd73c50665f787526065d57d7fd1d", size = 20378, upload-time = "2022-12-09T22:28:14.623Z" }, -] - -[[package]] -name = "opentelemetry-exporter-otlp-proto-http" -version = "1.15.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "backoff" }, - { name = "googleapis-common-protos" }, - { name = "opentelemetry-api" }, - { name = "opentelemetry-proto" }, - { name = "opentelemetry-sdk" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ad/ee/14baa8edbf6b0c8e23a93ee0807fb637d4689959a0b166e2821032fade34/opentelemetry_exporter_otlp_proto_http-1.15.0.tar.gz", hash = "sha256:11b2c814249a49b22f6cca7a06b05701f561d577b747f3660dfd67b6eb9daf9c", size = 18930, upload-time = "2022-12-09T22:28:45.366Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/12/77af459682a4f41eb9f13801af6a12420a86f5673dc568585ee49112e969/opentelemetry_exporter_otlp_proto_http-1.15.0-py3-none-any.whl", hash = "sha256:3ec2a02196c8a54bf5cbf7fe623a5238625638e83b6047a983bdf96e2bbb74c0", size = 21588, upload-time = "2022-12-09T22:28:15.776Z" }, -] - -[[package]] -name = "opentelemetry-instrumentation" -version = "0.58b0" -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/f6/36/7c307d9be8ce4ee7beb86d7f1d31027f2a6a89228240405a858d6e4d64f9/opentelemetry_instrumentation-0.58b0.tar.gz", hash = "sha256:df640f3ac715a3e05af145c18f527f4422c6ab6c467e40bd24d2ad75a00cb705", size = 31549, upload-time = "2025-09-11T11:42:14.084Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/db/5ff1cd6c5ca1d12ecf1b73be16fbb2a8af2114ee46d4b0e6d4b23f4f4db7/opentelemetry_instrumentation-0.58b0-py3-none-any.whl", hash = "sha256:50f97ac03100676c9f7fc28197f8240c7290ca1baa12da8bfbb9a1de4f34cc45", size = 33019, upload-time = "2025-09-11T11:41:00.624Z" }, -] - -[[package]] -name = "opentelemetry-proto" -version = "1.15.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "protobuf" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/1e/80/b3b2a98039574e57b6b15982219ae025d55f8c46d50dde258865ce5601b4/opentelemetry_proto-1.15.0.tar.gz", hash = "sha256:9c4008e40ac8cab359daac283fbe7002c5c29c77ea2674ad5626a249e64e0101", size = 35713, upload-time = "2022-12-09T22:28:55.409Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/56/8343d94af8f32594f6b0bd273f72a40e430fb5970a353237af53af5d3031/opentelemetry_proto-1.15.0-py3-none-any.whl", hash = "sha256:044b6d044b4d10530f250856f933442b8753a17f94ae37c207607f733fb9a844", size = 52616, upload-time = "2022-12-09T22:28:30.03Z" }, -] - -[[package]] -name = "opentelemetry-sdk" -version = "1.37.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/f4/62/2e0ca80d7fe94f0b193135375da92c640d15fe81f636658d2acf373086bc/opentelemetry_sdk-1.37.0.tar.gz", hash = "sha256:cc8e089c10953ded765b5ab5669b198bbe0af1b3f89f1007d19acd32dc46dda5", size = 170404, upload-time = "2025-09-11T10:29:11.779Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9f/62/9f4ad6a54126fb00f7ed4bb5034964c6e4f00fcd5a905e115bd22707e20d/opentelemetry_sdk-1.37.0-py3-none-any.whl", hash = "sha256:8f3c3c22063e52475c5dbced7209495c2c16723d016d39287dfc215d1771257c", size = 131941, upload-time = "2025-09-11T10:28:57.83Z" }, -] - -[[package]] -name = "opentelemetry-semantic-conventions" -version = "0.58b0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "opentelemetry-api" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/aa/1b/90701d91e6300d9f2fb352153fb1721ed99ed1f6ea14fa992c756016e63a/opentelemetry_semantic_conventions-0.58b0.tar.gz", hash = "sha256:6bd46f51264279c433755767bb44ad00f1c9e2367e1b42af563372c5a6fa0c25", size = 129867, upload-time = "2025-09-11T10:29:12.597Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/07/90/68152b7465f50285d3ce2481b3aec2f82822e3f52e5152eeeaf516bab841/opentelemetry_semantic_conventions-0.58b0-py3-none-any.whl", hash = "sha256:5564905ab1458b96684db1340232729fce3b5375a06e140e8904c78e4f815b28", size = 207954, upload-time = "2025-09-11T10:28:59.218Z" }, -] - -[[package]] -name = "packaging" -version = "25.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" } -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 = "pandas" -version = "2.2.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, - { name = "python-dateutil" }, - { name = "pytz" }, - { name = "tzdata" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9c/d6/9f8431bacc2e19dca897724cd097b1bb224a6ad5433784a44b587c7c13af/pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667", size = 4399213, upload-time = "2024-09-20T13:10:04.827Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/17/a3/fb2734118db0af37ea7433f57f722c0a56687e14b14690edff0cdb4b7e58/pandas-2.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b1d432e8d08679a40e2a6d8b2f9770a5c21793a6f9f47fdd52c5ce1948a5a8a9", size = 12529893, upload-time = "2024-09-20T13:09:09.655Z" }, - { url = "https://files.pythonhosted.org/packages/e1/0c/ad295fd74bfac85358fd579e271cded3ac969de81f62dd0142c426b9da91/pandas-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5a1595fe639f5988ba6a8e5bc9649af3baf26df3998a0abe56c02609392e0a4", size = 11363475, upload-time = "2024-09-20T13:09:14.718Z" }, - { url = "https://files.pythonhosted.org/packages/c6/2a/4bba3f03f7d07207481fed47f5b35f556c7441acddc368ec43d6643c5777/pandas-2.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5de54125a92bb4d1c051c0659e6fcb75256bf799a732a87184e5ea503965bce3", size = 15188645, upload-time = "2024-09-20T19:02:03.88Z" }, - { url = "https://files.pythonhosted.org/packages/38/f8/d8fddee9ed0d0c0f4a2132c1dfcf0e3e53265055da8df952a53e7eaf178c/pandas-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb8ae78d8af97f849404f21411c95062db1496aeb3e56f146f0355c9989319", size = 12739445, upload-time = "2024-09-20T13:09:17.621Z" }, - { url = "https://files.pythonhosted.org/packages/20/e8/45a05d9c39d2cea61ab175dbe6a2de1d05b679e8de2011da4ee190d7e748/pandas-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfcb5ee8d4d50c06a51c2fffa6cff6272098ad6540aed1a76d15fb9318194d8", size = 16359235, upload-time = "2024-09-20T19:02:07.094Z" }, - { url = "https://files.pythonhosted.org/packages/1d/99/617d07a6a5e429ff90c90da64d428516605a1ec7d7bea494235e1c3882de/pandas-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:062309c1b9ea12a50e8ce661145c6aab431b1e99530d3cd60640e255778bd43a", size = 14056756, upload-time = "2024-09-20T13:09:20.474Z" }, - { url = "https://files.pythonhosted.org/packages/29/d4/1244ab8edf173a10fd601f7e13b9566c1b525c4f365d6bee918e68381889/pandas-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:59ef3764d0fe818125a5097d2ae867ca3fa64df032331b7e0917cf5d7bf66b13", size = 11504248, upload-time = "2024-09-20T13:09:23.137Z" }, -] - -[[package]] -name = "pastel" -version = "0.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/76/f1/4594f5e0fcddb6953e5b8fe00da8c317b8b41b547e2b3ae2da7512943c62/pastel-0.2.1.tar.gz", hash = "sha256:e6581ac04e973cac858828c6202c1e1e81fee1dc7de7683f3e1ffe0bfd8a573d", size = 7555, upload-time = "2020-09-16T19:21:12.43Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/aa/18/a8444036c6dd65ba3624c63b734d3ba95ba63ace513078e1580590075d21/pastel-0.2.1-py2.py3-none-any.whl", hash = "sha256:4349225fcdf6c2bb34d483e523475de5bb04a5c10ef711263452cb37d7dd4364", size = 5955, upload-time = "2020-09-16T19:21:11.409Z" }, -] - -[[package]] -name = "pathvalidate" -version = "3.3.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fa/2a/52a8da6fe965dea6192eb716b357558e103aea0a1e9a8352ad575a8406ca/pathvalidate-3.3.1.tar.gz", hash = "sha256:b18c07212bfead624345bb8e1d6141cdcf15a39736994ea0b94035ad2b1ba177", size = 63262, upload-time = "2025-06-15T09:07:20.736Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/70/875f4a23bfc4731703a5835487d0d2fb999031bd415e7d17c0ae615c18b7/pathvalidate-3.3.1-py3-none-any.whl", hash = "sha256:5263baab691f8e1af96092fa5137ee17df5bdfbd6cff1fcac4d6ef4bc2e1735f", size = 24305, upload-time = "2025-06-15T09:07:19.117Z" }, -] - -[[package]] -name = "pdfminer-six" -version = "20250506" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "charset-normalizer" }, - { name = "cryptography" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/78/46/5223d613ac4963e1f7c07b2660fe0e9e770102ec6bda8c038400113fb215/pdfminer_six-20250506.tar.gz", hash = "sha256:b03cc8df09cf3c7aba8246deae52e0bca7ebb112a38895b5e1d4f5dd2b8ca2e7", size = 7387678, upload-time = "2025-05-06T16:17:00.787Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/73/16/7a432c0101fa87457e75cb12c879e1749c5870a786525e2e0f42871d6462/pdfminer_six-20250506-py3-none-any.whl", hash = "sha256:d81ad173f62e5f841b53a8ba63af1a4a355933cfc0ffabd608e568b9193909e3", size = 5620187, upload-time = "2025-05-06T16:16:58.669Z" }, -] - -[[package]] -name = "pdfplumber" -version = "0.11.7" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pdfminer-six" }, - { name = "pillow" }, - { name = "pypdfium2" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6d/0d/4135821aa7b1a0b77a29fac881ef0890b46b0b002290d04915ed7acc0043/pdfplumber-0.11.7.tar.gz", hash = "sha256:fa67773e5e599de1624255e9b75d1409297c5e1d7493b386ce63648637c67368", size = 115518, upload-time = "2025-06-12T11:30:49.864Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/db/e0/52b67d4f00e09e497aec4f71bc44d395605e8ebcea52543242ed34c25ef9/pdfplumber-0.11.7-py3-none-any.whl", hash = "sha256:edd2195cca68bd770da479cf528a737e362968ec2351e62a6c0b71ff612ac25e", size = 60029, upload-time = "2025-06-12T11:30:48.89Z" }, -] - -[[package]] -name = "peewee" -version = "3.18.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/04/89/76f6f1b744c8608e0d416b588b9d63c2a500ff800065ae610f7c80f532d6/peewee-3.18.2.tar.gz", hash = "sha256:77a54263eb61aff2ea72f63d2eeb91b140c25c1884148e28e4c0f7c4f64996a0", size = 949220, upload-time = "2025-07-08T12:52:03.941Z" } - -[[package]] -name = "pgvector" -version = "0.4.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/44/43/9a0fb552ab4fd980680c2037962e331820f67585df740bedc4a2b50faf20/pgvector-0.4.1.tar.gz", hash = "sha256:83d3a1c044ff0c2f1e95d13dfb625beb0b65506cfec0941bfe81fd0ad44f4003", size = 30646, upload-time = "2025-04-26T18:56:37.151Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/21/b5735d5982892c878ff3d01bb06e018c43fc204428361ee9fc25a1b2125c/pgvector-0.4.1-py3-none-any.whl", hash = "sha256:34bb4e99e1b13d08a2fe82dda9f860f15ddcd0166fbb25bffe15821cbfeb7362", size = 27086, upload-time = "2025-04-26T18:56:35.956Z" }, -] - -[[package]] -name = "pillow" -version = "11.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/d0d6dea55cd152ce3d6767bb38a8fc10e33796ba4ba210cbab9354b6d238/pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523", size = 47113069, upload-time = "2025-07-01T09:16:30.666Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/40/fe/1bc9b3ee13f68487a99ac9529968035cca2f0a51ec36892060edcc51d06a/pillow-11.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4", size = 5278800, upload-time = "2025-07-01T09:14:17.648Z" }, - { url = "https://files.pythonhosted.org/packages/2c/32/7e2ac19b5713657384cec55f89065fb306b06af008cfd87e572035b27119/pillow-11.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:921bd305b10e82b4d1f5e802b6850677f965d8394203d182f078873851dada69", size = 4686296, upload-time = "2025-07-01T09:14:19.828Z" }, - { url = "https://files.pythonhosted.org/packages/8e/1e/b9e12bbe6e4c2220effebc09ea0923a07a6da1e1f1bfbc8d7d29a01ce32b/pillow-11.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb76541cba2f958032d79d143b98a3a6b3ea87f0959bbe256c0b5e416599fd5d", size = 5871726, upload-time = "2025-07-03T13:10:04.448Z" }, - { url = "https://files.pythonhosted.org/packages/8d/33/e9200d2bd7ba00dc3ddb78df1198a6e80d7669cce6c2bdbeb2530a74ec58/pillow-11.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67172f2944ebba3d4a7b54f2e95c786a3a50c21b88456329314caaa28cda70f6", size = 7644652, upload-time = "2025-07-03T13:10:10.391Z" }, - { url = "https://files.pythonhosted.org/packages/41/f1/6f2427a26fc683e00d985bc391bdd76d8dd4e92fac33d841127eb8fb2313/pillow-11.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f07ed9f56a3b9b5f49d3661dc9607484e85c67e27f3e8be2c7d28ca032fec7", size = 5977787, upload-time = "2025-07-01T09:14:21.63Z" }, - { url = "https://files.pythonhosted.org/packages/e4/c9/06dd4a38974e24f932ff5f98ea3c546ce3f8c995d3f0985f8e5ba48bba19/pillow-11.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024", size = 6645236, upload-time = "2025-07-01T09:14:23.321Z" }, - { url = "https://files.pythonhosted.org/packages/40/e7/848f69fb79843b3d91241bad658e9c14f39a32f71a301bcd1d139416d1be/pillow-11.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e184b2f26ff146363dd07bde8b711833d7b0202e27d13540bfe2e35a323a809", size = 6086950, upload-time = "2025-07-01T09:14:25.237Z" }, - { url = "https://files.pythonhosted.org/packages/0b/1a/7cff92e695a2a29ac1958c2a0fe4c0b2393b60aac13b04a4fe2735cad52d/pillow-11.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6be31e3fc9a621e071bc17bb7de63b85cbe0bfae91bb0363c893cbe67247780d", size = 6723358, upload-time = "2025-07-01T09:14:27.053Z" }, - { url = "https://files.pythonhosted.org/packages/26/7d/73699ad77895f69edff76b0f332acc3d497f22f5d75e5360f78cbcaff248/pillow-11.3.0-cp312-cp312-win32.whl", hash = "sha256:7b161756381f0918e05e7cb8a371fff367e807770f8fe92ecb20d905d0e1c149", size = 6275079, upload-time = "2025-07-01T09:14:30.104Z" }, - { url = "https://files.pythonhosted.org/packages/8c/ce/e7dfc873bdd9828f3b6e5c2bbb74e47a98ec23cc5c74fc4e54462f0d9204/pillow-11.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a6444696fce635783440b7f7a9fc24b3ad10a9ea3f0ab66c5905be1c19ccf17d", size = 6986324, upload-time = "2025-07-01T09:14:31.899Z" }, - { url = "https://files.pythonhosted.org/packages/16/8f/b13447d1bf0b1f7467ce7d86f6e6edf66c0ad7cf44cf5c87a37f9bed9936/pillow-11.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:2aceea54f957dd4448264f9bf40875da0415c83eb85f55069d89c0ed436e3542", size = 2423067, upload-time = "2025-07-01T09:14:33.709Z" }, -] - -[[package]] -name = "pinecone" -version = "7.0.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "pinecone-plugin-interface" }, - { name = "python-dateutil" }, - { name = "typing-extensions" }, - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/bc/9d/07a7f2136ce04cabd21d69c057dc2915867082b0047e6873e424388d4475/pinecone-7.0.1.tar.gz", hash = "sha256:49ff7b0f5be4a2ddec5aaa709758a9f2df56baa58ad46507d081409e246a81ec", size = 207930, upload-time = "2025-05-21T19:39:01.218Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/81/88/896221e991077d353e61991b759f46d75f3b4298eb5a4aa6534c1371f4b0/pinecone-7.0.1-py3-none-any.whl", hash = "sha256:ce7b0dab3c9f7d81e75b24c13fcbca4a51371e08021faaecaf0cd9a45ca1be6c", size = 516590, upload-time = "2025-05-21T19:38:59.117Z" }, -] - -[[package]] -name = "pinecone-plugin-interface" -version = "0.0.7" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f4/fb/e8a4063264953ead9e2b24d9b390152c60f042c951c47f4592e9996e57ff/pinecone_plugin_interface-0.0.7.tar.gz", hash = "sha256:b8e6675e41847333aa13923cc44daa3f85676d7157324682dc1640588a982846", size = 3370, upload-time = "2024-06-05T01:57:52.093Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/1d/a21fdfcd6d022cb64cef5c2a29ee6691c6c103c4566b41646b080b7536a5/pinecone_plugin_interface-0.0.7-py3-none-any.whl", hash = "sha256:875857ad9c9fc8bbc074dbe780d187a2afd21f5bfe0f3b08601924a61ef1bba8", size = 6249, upload-time = "2024-06-05T01:57:50.583Z" }, -] - -[[package]] -name = "platformdirs" -version = "4.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/23/e8/21db9c9987b0e728855bd57bff6984f67952bea55d6f75e055c46b5383e8/platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf", size = 21634, upload-time = "2025-08-26T14:32:04.268Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/40/4b/2028861e724d3bd36227adfa20d3fd24c3fc6d52032f4a93c133be5d17ce/platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85", size = 18654, upload-time = "2025-08-26T14:32:02.735Z" }, -] - -[[package]] -name = "pluggy" -version = "1.6.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } -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 = "poethepoet" -version = "0.37.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pastel" }, - { name = "pyyaml" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a5/f2/273fe54a78dc5c6c8dd63db71f5a6ceb95e4648516b5aeaeff4bde804e44/poethepoet-0.37.0.tar.gz", hash = "sha256:73edf458707c674a079baa46802e21455bda3a7f82a408e58c31b9f4fe8e933d", size = 68570, upload-time = "2025-08-11T18:00:29.103Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/92/1b/5337af1a6a478d25a3e3c56b9b4b42b0a160314e02f4a0498d5322c8dac4/poethepoet-0.37.0-py3-none-any.whl", hash = "sha256:861790276315abcc8df1b4bd60e28c3d48a06db273edd3092f3c94e1a46e5e22", size = 90062, upload-time = "2025-08-11T18:00:27.595Z" }, -] - -[[package]] -name = "portalocker" -version = "3.2.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pywin32", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5e/77/65b857a69ed876e1951e88aaba60f5ce6120c33703f7cb61a3c894b8c1b6/portalocker-3.2.0.tar.gz", hash = "sha256:1f3002956a54a8c3730586c5c77bf18fae4149e07eaf1c29fc3faf4d5a3f89ac", size = 95644, upload-time = "2025-06-14T13:20:40.03Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/a6/38c8e2f318bf67d338f4d629e93b0b4b9af331f455f0390ea8ce4a099b26/portalocker-3.2.0-py3-none-any.whl", hash = "sha256:3cdc5f565312224bc570c49337bd21428bba0ef363bbcf58b9ef4a9f11779968", size = 22424, upload-time = "2025-06-14T13:20:38.083Z" }, -] - -[[package]] -name = "propcache" -version = "0.3.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a6/16/43264e4a779dd8588c21a70f0709665ee8f611211bdd2c87d952cfa7c776/propcache-0.3.2.tar.gz", hash = "sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168", size = 44139, upload-time = "2025-06-09T22:56:06.081Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/42/9ca01b0a6f48e81615dca4765a8f1dd2c057e0540f6116a27dc5ee01dfb6/propcache-0.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8de106b6c84506b31c27168582cd3cb3000a6412c16df14a8628e5871ff83c10", size = 73674, upload-time = "2025-06-09T22:54:30.551Z" }, - { url = "https://files.pythonhosted.org/packages/af/6e/21293133beb550f9c901bbece755d582bfaf2176bee4774000bd4dd41884/propcache-0.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:28710b0d3975117239c76600ea351934ac7b5ff56e60953474342608dbbb6154", size = 43570, upload-time = "2025-06-09T22:54:32.296Z" }, - { url = "https://files.pythonhosted.org/packages/0c/c8/0393a0a3a2b8760eb3bde3c147f62b20044f0ddac81e9d6ed7318ec0d852/propcache-0.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce26862344bdf836650ed2487c3d724b00fbfec4233a1013f597b78c1cb73615", size = 43094, upload-time = "2025-06-09T22:54:33.929Z" }, - { url = "https://files.pythonhosted.org/packages/37/2c/489afe311a690399d04a3e03b069225670c1d489eb7b044a566511c1c498/propcache-0.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bca54bd347a253af2cf4544bbec232ab982f4868de0dd684246b67a51bc6b1db", size = 226958, upload-time = "2025-06-09T22:54:35.186Z" }, - { url = "https://files.pythonhosted.org/packages/9d/ca/63b520d2f3d418c968bf596839ae26cf7f87bead026b6192d4da6a08c467/propcache-0.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55780d5e9a2ddc59711d727226bb1ba83a22dd32f64ee15594b9392b1f544eb1", size = 234894, upload-time = "2025-06-09T22:54:36.708Z" }, - { url = "https://files.pythonhosted.org/packages/11/60/1d0ed6fff455a028d678df30cc28dcee7af77fa2b0e6962ce1df95c9a2a9/propcache-0.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:035e631be25d6975ed87ab23153db6a73426a48db688070d925aa27e996fe93c", size = 233672, upload-time = "2025-06-09T22:54:38.062Z" }, - { url = "https://files.pythonhosted.org/packages/37/7c/54fd5301ef38505ab235d98827207176a5c9b2aa61939b10a460ca53e123/propcache-0.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee6f22b6eaa39297c751d0e80c0d3a454f112f5c6481214fcf4c092074cecd67", size = 224395, upload-time = "2025-06-09T22:54:39.634Z" }, - { url = "https://files.pythonhosted.org/packages/ee/1a/89a40e0846f5de05fdc6779883bf46ba980e6df4d2ff8fb02643de126592/propcache-0.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ca3aee1aa955438c4dba34fc20a9f390e4c79967257d830f137bd5a8a32ed3b", size = 212510, upload-time = "2025-06-09T22:54:41.565Z" }, - { url = "https://files.pythonhosted.org/packages/5e/33/ca98368586c9566a6b8d5ef66e30484f8da84c0aac3f2d9aec6d31a11bd5/propcache-0.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4f30862869fa2b68380d677cc1c5fcf1e0f2b9ea0cf665812895c75d0ca3b8", size = 222949, upload-time = "2025-06-09T22:54:43.038Z" }, - { url = "https://files.pythonhosted.org/packages/ba/11/ace870d0aafe443b33b2f0b7efdb872b7c3abd505bfb4890716ad7865e9d/propcache-0.3.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b77ec3c257d7816d9f3700013639db7491a434644c906a2578a11daf13176251", size = 217258, upload-time = "2025-06-09T22:54:44.376Z" }, - { url = "https://files.pythonhosted.org/packages/5b/d2/86fd6f7adffcfc74b42c10a6b7db721d1d9ca1055c45d39a1a8f2a740a21/propcache-0.3.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cab90ac9d3f14b2d5050928483d3d3b8fb6b4018893fc75710e6aa361ecb2474", size = 213036, upload-time = "2025-06-09T22:54:46.243Z" }, - { url = "https://files.pythonhosted.org/packages/07/94/2d7d1e328f45ff34a0a284cf5a2847013701e24c2a53117e7c280a4316b3/propcache-0.3.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0b504d29f3c47cf6b9e936c1852246c83d450e8e063d50562115a6be6d3a2535", size = 227684, upload-time = "2025-06-09T22:54:47.63Z" }, - { url = "https://files.pythonhosted.org/packages/b7/05/37ae63a0087677e90b1d14710e532ff104d44bc1efa3b3970fff99b891dc/propcache-0.3.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:ce2ac2675a6aa41ddb2a0c9cbff53780a617ac3d43e620f8fd77ba1c84dcfc06", size = 234562, upload-time = "2025-06-09T22:54:48.982Z" }, - { url = "https://files.pythonhosted.org/packages/a4/7c/3f539fcae630408d0bd8bf3208b9a647ccad10976eda62402a80adf8fc34/propcache-0.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b4239611205294cc433845b914131b2a1f03500ff3c1ed093ed216b82621e1", size = 222142, upload-time = "2025-06-09T22:54:50.424Z" }, - { url = "https://files.pythonhosted.org/packages/7c/d2/34b9eac8c35f79f8a962546b3e97e9d4b990c420ee66ac8255d5d9611648/propcache-0.3.2-cp312-cp312-win32.whl", hash = "sha256:df4a81b9b53449ebc90cc4deefb052c1dd934ba85012aa912c7ea7b7e38b60c1", size = 37711, upload-time = "2025-06-09T22:54:52.072Z" }, - { url = "https://files.pythonhosted.org/packages/19/61/d582be5d226cf79071681d1b46b848d6cb03d7b70af7063e33a2787eaa03/propcache-0.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7046e79b989d7fe457bb755844019e10f693752d169076138abf17f31380800c", size = 41479, upload-time = "2025-06-09T22:54:53.234Z" }, - { url = "https://files.pythonhosted.org/packages/cc/35/cc0aaecf278bb4575b8555f2b137de5ab821595ddae9da9d3cd1da4072c7/propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f", size = 12663, upload-time = "2025-06-09T22:56:04.484Z" }, -] - -[[package]] -name = "proto-plus" -version = "1.26.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "protobuf" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f4/ac/87285f15f7cce6d4a008f33f1757fb5a13611ea8914eb58c3d0d26243468/proto_plus-1.26.1.tar.gz", hash = "sha256:21a515a4c4c0088a773899e23c7bbade3d18f9c66c73edd4c7ee3816bc96a012", size = 56142, upload-time = "2025-03-10T15:54:38.843Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/6d/280c4c2ce28b1593a19ad5239c8b826871fc6ec275c21afc8e1820108039/proto_plus-1.26.1-py3-none-any.whl", hash = "sha256:13285478c2dcf2abb829db158e1047e2f1e8d63a077d94263c2b88b043c75a66", size = 50163, upload-time = "2025-03-10T15:54:37.335Z" }, -] - -[[package]] -name = "protobuf" -version = "4.25.8" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/df/01/34c8d2b6354906d728703cb9d546a0e534de479e25f1b581e4094c4a85cc/protobuf-4.25.8.tar.gz", hash = "sha256:6135cf8affe1fc6f76cced2641e4ea8d3e59518d1f24ae41ba97bcad82d397cd", size = 380920, upload-time = "2025-05-28T14:22:25.153Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/45/ff/05f34305fe6b85bbfbecbc559d423a5985605cad5eda4f47eae9e9c9c5c5/protobuf-4.25.8-cp310-abi3-win32.whl", hash = "sha256:504435d831565f7cfac9f0714440028907f1975e4bed228e58e72ecfff58a1e0", size = 392745, upload-time = "2025-05-28T14:22:10.524Z" }, - { url = "https://files.pythonhosted.org/packages/08/35/8b8a8405c564caf4ba835b1fdf554da869954712b26d8f2a98c0e434469b/protobuf-4.25.8-cp310-abi3-win_amd64.whl", hash = "sha256:bd551eb1fe1d7e92c1af1d75bdfa572eff1ab0e5bf1736716814cdccdb2360f9", size = 413736, upload-time = "2025-05-28T14:22:13.156Z" }, - { url = "https://files.pythonhosted.org/packages/28/d7/ab27049a035b258dab43445eb6ec84a26277b16105b277cbe0a7698bdc6c/protobuf-4.25.8-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:ca809b42f4444f144f2115c4c1a747b9a404d590f18f37e9402422033e464e0f", size = 394537, upload-time = "2025-05-28T14:22:14.768Z" }, - { url = "https://files.pythonhosted.org/packages/bd/6d/a4a198b61808dd3d1ee187082ccc21499bc949d639feb948961b48be9a7e/protobuf-4.25.8-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:9ad7ef62d92baf5a8654fbb88dac7fa5594cfa70fd3440488a5ca3bfc6d795a7", size = 294005, upload-time = "2025-05-28T14:22:16.052Z" }, - { url = "https://files.pythonhosted.org/packages/d6/c6/c9deaa6e789b6fc41b88ccbdfe7a42d2b82663248b715f55aa77fbc00724/protobuf-4.25.8-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:83e6e54e93d2b696a92cad6e6efc924f3850f82b52e1563778dfab8b355101b0", size = 294924, upload-time = "2025-05-28T14:22:17.105Z" }, - { url = "https://files.pythonhosted.org/packages/0c/c1/6aece0ab5209981a70cd186f164c133fdba2f51e124ff92b73de7fd24d78/protobuf-4.25.8-py3-none-any.whl", hash = "sha256:15a0af558aa3b13efef102ae6e4f3efac06f1eea11afb3a57db2901447d9fb59", size = 156757, upload-time = "2025-05-28T14:22:24.135Z" }, -] - -[[package]] -name = "psycopg2-binary" -version = "2.9.10" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cb/0e/bdc8274dc0585090b4e3432267d7be4dfbfd8971c0fa59167c711105a6bf/psycopg2-binary-2.9.10.tar.gz", hash = "sha256:4b3df0e6990aa98acda57d983942eff13d824135fe2250e6522edaa782a06de2", size = 385764, upload-time = "2024-10-16T11:24:58.126Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/49/7d/465cc9795cf76f6d329efdafca74693714556ea3891813701ac1fee87545/psycopg2_binary-2.9.10-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:880845dfe1f85d9d5f7c412efea7a08946a46894537e4e5d091732eb1d34d9a0", size = 3044771, upload-time = "2024-10-16T11:20:35.234Z" }, - { url = "https://files.pythonhosted.org/packages/8b/31/6d225b7b641a1a2148e3ed65e1aa74fc86ba3fee850545e27be9e1de893d/psycopg2_binary-2.9.10-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9440fa522a79356aaa482aa4ba500b65f28e5d0e63b801abf6aa152a29bd842a", size = 3275336, upload-time = "2024-10-16T11:20:38.742Z" }, - { url = "https://files.pythonhosted.org/packages/30/b7/a68c2b4bff1cbb1728e3ec864b2d92327c77ad52edcd27922535a8366f68/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3923c1d9870c49a2d44f795df0c889a22380d36ef92440ff618ec315757e539", size = 2851637, upload-time = "2024-10-16T11:20:42.145Z" }, - { url = "https://files.pythonhosted.org/packages/0b/b1/cfedc0e0e6f9ad61f8657fd173b2f831ce261c02a08c0b09c652b127d813/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b2c956c028ea5de47ff3a8d6b3cc3330ab45cf0b7c3da35a2d6ff8420896526", size = 3082097, upload-time = "2024-10-16T11:20:46.185Z" }, - { url = "https://files.pythonhosted.org/packages/18/ed/0a8e4153c9b769f59c02fb5e7914f20f0b2483a19dae7bf2db54b743d0d0/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f758ed67cab30b9a8d2833609513ce4d3bd027641673d4ebc9c067e4d208eec1", size = 3264776, upload-time = "2024-10-16T11:20:50.879Z" }, - { url = "https://files.pythonhosted.org/packages/10/db/d09da68c6a0cdab41566b74e0a6068a425f077169bed0946559b7348ebe9/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cd9b4f2cfab88ed4a9106192de509464b75a906462fb846b936eabe45c2063e", size = 3020968, upload-time = "2024-10-16T11:20:56.819Z" }, - { url = "https://files.pythonhosted.org/packages/94/28/4d6f8c255f0dfffb410db2b3f9ac5218d959a66c715c34cac31081e19b95/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dc08420625b5a20b53551c50deae6e231e6371194fa0651dbe0fb206452ae1f", size = 2872334, upload-time = "2024-10-16T11:21:02.411Z" }, - { url = "https://files.pythonhosted.org/packages/05/f7/20d7bf796593c4fea95e12119d6cc384ff1f6141a24fbb7df5a668d29d29/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d7cd730dfa7c36dbe8724426bf5612798734bff2d3c3857f36f2733f5bfc7c00", size = 2822722, upload-time = "2024-10-16T11:21:09.01Z" }, - { url = "https://files.pythonhosted.org/packages/4d/e4/0c407ae919ef626dbdb32835a03b6737013c3cc7240169843965cada2bdf/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:155e69561d54d02b3c3209545fb08938e27889ff5a10c19de8d23eb5a41be8a5", size = 2920132, upload-time = "2024-10-16T11:21:16.339Z" }, - { url = "https://files.pythonhosted.org/packages/2d/70/aa69c9f69cf09a01da224909ff6ce8b68faeef476f00f7ec377e8f03be70/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3cc28a6fd5a4a26224007712e79b81dbaee2ffb90ff406256158ec4d7b52b47", size = 2959312, upload-time = "2024-10-16T11:21:25.584Z" }, - { url = "https://files.pythonhosted.org/packages/d3/bd/213e59854fafe87ba47814bf413ace0dcee33a89c8c8c814faca6bc7cf3c/psycopg2_binary-2.9.10-cp312-cp312-win32.whl", hash = "sha256:ec8a77f521a17506a24a5f626cb2aee7850f9b69a0afe704586f63a464f3cd64", size = 1025191, upload-time = "2024-10-16T11:21:29.912Z" }, - { url = "https://files.pythonhosted.org/packages/92/29/06261ea000e2dc1e22907dbbc483a1093665509ea586b29b8986a0e56733/psycopg2_binary-2.9.10-cp312-cp312-win_amd64.whl", hash = "sha256:18c5ee682b9c6dd3696dad6e54cc7ff3a1a9020df6a5c0f861ef8bfd338c3ca0", size = 1164031, upload-time = "2024-10-16T11:21:34.211Z" }, -] - -[[package]] -name = "pyasn1" -version = "0.6.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ba/e9/01f1a64245b89f039897cb0130016d79f77d52669aae6ee7b159a6c4c018/pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034", size = 145322, upload-time = "2024-09-10T22:41:42.55Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/f1/d6a797abb14f6283c0ddff96bbdd46937f64122b8c925cab503dd37f8214/pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", size = 83135, upload-time = "2024-09-11T16:00:36.122Z" }, -] - -[[package]] -name = "pyasn1-modules" -version = "0.4.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyasn1" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892, upload-time = "2025-03-28T02:41:22.17Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" }, -] - -[[package]] -name = "pycparser" -version = "2.23" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, -] - -[[package]] -name = "pydantic" -version = "2.12.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "annotated-types" }, - { name = "pydantic-core" }, - { name = "typing-extensions" }, - { name = "typing-inspection" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } -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]] -name = "pydantic-core" -version = "2.41.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" }, - { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" }, - { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" }, - { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" }, - { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" }, - { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" }, - { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" }, - { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" }, - { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" }, - { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" }, - { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" }, - { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" }, - { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" }, - { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" }, - { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" }, - { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" }, - { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" }, - { 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 = "pyjwt" -version = "2.10.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785, upload-time = "2024-11-28T03:43:29.933Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload-time = "2024-11-28T03:43:27.893Z" }, -] - -[package.optional-dependencies] -crypto = [ - { name = "cryptography" }, -] - -[[package]] -name = "pymilvus" -version = "2.5.16" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "grpcio" }, - { name = "milvus-lite", marker = "sys_platform != 'win32'" }, - { name = "pandas" }, - { name = "protobuf" }, - { name = "python-dotenv" }, - { name = "setuptools" }, - { name = "ujson" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/24/e2/5613bc7b2af0ccd760177ca4255243c284cfc0f2cba3f10ff63325c4ca34/pymilvus-2.5.16.tar.gz", hash = "sha256:65f56b81806bc217cca3cf29b70a27d053dea4b1ffada910cf63a38f96381618", size = 1280614, upload-time = "2025-09-19T07:02:14.747Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/09/b67a55abee0a53ea50ba0de0cba6e1c0f7ca7ce2c15ffd6f40c059c25e88/pymilvus-2.5.16-py3-none-any.whl", hash = "sha256:76258a324f19c60fee247467e11cd7d6f35a64d2a9c753f5d7b1a5fa15dd6c8a", size = 243272, upload-time = "2025-09-19T07:02:12.443Z" }, -] - -[[package]] -name = "pypdf" -version = "6.1.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a6/85/4c0f12616db83c2e3ef580c3cfa98bd082e88fc8d02e136bad3bede1e3fa/pypdf-6.1.1.tar.gz", hash = "sha256:10f44d49bf2a82e54c3c5ba3cdcbb118f2a44fc57df8ce51d6fb9b1ed9bfbe8b", size = 5074507, upload-time = "2025-09-28T13:29:16.165Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/07/ed/adae13756d9dabdddee483fc7712905bb5585fbf6e922b1a19aca3a29cd1/pypdf-6.1.1-py3-none-any.whl", hash = "sha256:7781f99493208a37a7d4275601d883e19af24e62a525c25844d22157c2e4cde7", size = 323455, upload-time = "2025-09-28T13:29:14.392Z" }, -] - -[[package]] -name = "pypdfium2" -version = "4.30.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/14/838b3ba247a0ba92e4df5d23f2bea9478edcfd72b78a39d6ca36ccd84ad2/pypdfium2-4.30.0.tar.gz", hash = "sha256:48b5b7e5566665bc1015b9d69c1ebabe21f6aee468b509531c3c8318eeee2e16", size = 140239, upload-time = "2024-05-09T18:33:17.552Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/9a/c8ff5cc352c1b60b0b97642ae734f51edbab6e28b45b4fcdfe5306ee3c83/pypdfium2-4.30.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:b33ceded0b6ff5b2b93bc1fe0ad4b71aa6b7e7bd5875f1ca0cdfb6ba6ac01aab", size = 2837254, upload-time = "2024-05-09T18:32:48.653Z" }, - { url = "https://files.pythonhosted.org/packages/21/8b/27d4d5409f3c76b985f4ee4afe147b606594411e15ac4dc1c3363c9a9810/pypdfium2-4.30.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:4e55689f4b06e2d2406203e771f78789bd4f190731b5d57383d05cf611d829de", size = 2707624, upload-time = "2024-05-09T18:32:51.458Z" }, - { url = "https://files.pythonhosted.org/packages/11/63/28a73ca17c24b41a205d658e177d68e198d7dde65a8c99c821d231b6ee3d/pypdfium2-4.30.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e6e50f5ce7f65a40a33d7c9edc39f23140c57e37144c2d6d9e9262a2a854854", size = 2793126, upload-time = "2024-05-09T18:32:53.581Z" }, - { url = "https://files.pythonhosted.org/packages/d1/96/53b3ebf0955edbd02ac6da16a818ecc65c939e98fdeb4e0958362bd385c8/pypdfium2-4.30.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3d0dd3ecaffd0b6dbda3da663220e705cb563918249bda26058c6036752ba3a2", size = 2591077, upload-time = "2024-05-09T18:32:55.99Z" }, - { url = "https://files.pythonhosted.org/packages/ec/ee/0394e56e7cab8b5b21f744d988400948ef71a9a892cbeb0b200d324ab2c7/pypdfium2-4.30.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc3bf29b0db8c76cdfaac1ec1cde8edf211a7de7390fbf8934ad2aa9b4d6dfad", size = 2864431, upload-time = "2024-05-09T18:32:57.911Z" }, - { url = "https://files.pythonhosted.org/packages/65/cd/3f1edf20a0ef4a212a5e20a5900e64942c5a374473671ac0780eaa08ea80/pypdfium2-4.30.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1f78d2189e0ddf9ac2b7a9b9bd4f0c66f54d1389ff6c17e9fd9dc034d06eb3f", size = 2812008, upload-time = "2024-05-09T18:32:59.886Z" }, - { url = "https://files.pythonhosted.org/packages/c8/91/2d517db61845698f41a2a974de90762e50faeb529201c6b3574935969045/pypdfium2-4.30.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:5eda3641a2da7a7a0b2f4dbd71d706401a656fea521b6b6faa0675b15d31a163", size = 6181543, upload-time = "2024-05-09T18:33:02.597Z" }, - { url = "https://files.pythonhosted.org/packages/ba/c4/ed1315143a7a84b2c7616569dfb472473968d628f17c231c39e29ae9d780/pypdfium2-4.30.0-py3-none-musllinux_1_1_i686.whl", hash = "sha256:0dfa61421b5eb68e1188b0b2231e7ba35735aef2d867d86e48ee6cab6975195e", size = 6175911, upload-time = "2024-05-09T18:33:05.376Z" }, - { url = "https://files.pythonhosted.org/packages/7a/c4/9e62d03f414e0e3051c56d5943c3bf42aa9608ede4e19dc96438364e9e03/pypdfium2-4.30.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:f33bd79e7a09d5f7acca3b0b69ff6c8a488869a7fab48fdf400fec6e20b9c8be", size = 6267430, upload-time = "2024-05-09T18:33:08.067Z" }, - { url = "https://files.pythonhosted.org/packages/90/47/eda4904f715fb98561e34012826e883816945934a851745570521ec89520/pypdfium2-4.30.0-py3-none-win32.whl", hash = "sha256:ee2410f15d576d976c2ab2558c93d392a25fb9f6635e8dd0a8a3a5241b275e0e", size = 2775951, upload-time = "2024-05-09T18:33:10.567Z" }, - { url = "https://files.pythonhosted.org/packages/25/bd/56d9ec6b9f0fc4e0d95288759f3179f0fcd34b1a1526b75673d2f6d5196f/pypdfium2-4.30.0-py3-none-win_amd64.whl", hash = "sha256:90dbb2ac07be53219f56be09961eb95cf2473f834d01a42d901d13ccfad64b4c", size = 2892098, upload-time = "2024-05-09T18:33:13.107Z" }, - { url = "https://files.pythonhosted.org/packages/be/7a/097801205b991bc3115e8af1edb850d30aeaf0118520b016354cf5ccd3f6/pypdfium2-4.30.0-py3-none-win_arm64.whl", hash = "sha256:119b2969a6d6b1e8d55e99caaf05290294f2d0fe49c12a3f17102d01c441bd29", size = 2752118, upload-time = "2024-05-09T18:33:15.489Z" }, -] - -[[package]] -name = "pytablewriter" -version = "1.2.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "dataproperty" }, - { name = "mbstrdecoder" }, - { name = "pathvalidate" }, - { name = "setuptools" }, - { name = "tabledata" }, - { name = "tcolorpy" }, - { name = "typepy", extra = ["datetime"] }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f6/a1/617730f290f04d347103ab40bf67d317df6691b14746f6e1ea039fb57062/pytablewriter-1.2.1.tar.gz", hash = "sha256:7bd0f4f397e070e3b8a34edcf1b9257ccbb18305493d8350a5dbc9957fced959", size = 619241, upload-time = "2025-01-01T15:37:00.04Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/21/4c/c199512f01c845dfe5a7840ab3aae6c60463b5dc2a775be72502dfd9170a/pytablewriter-1.2.1-py3-none-any.whl", hash = "sha256:e906ff7ff5151d70a5f66e0f7b75642a7f2dce8d893c265b79cc9cf6bc04ddb4", size = 91083, upload-time = "2025-01-01T15:36:55.63Z" }, -] - -[[package]] -name = "pytest" -version = "8.0.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "iniconfig" }, - { name = "packaging" }, - { name = "pluggy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3f/c0/238f25cb27495fdbaa5c48cef9886162e9df1f3d0e957fc8326d9c24fa2f/pytest-8.0.2.tar.gz", hash = "sha256:d4051d623a2e0b7e51960ba963193b09ce6daeb9759a451844a21e4ddedfc1bd", size = 1396924, upload-time = "2024-02-24T22:21:30.762Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/ea/d0ab9595a0d4b2320483e634123171deaf50885e29d442180efcbf2ed0b2/pytest-8.0.2-py3-none-any.whl", hash = "sha256:edfaaef32ce5172d5466b5127b42e0d6d35ebbe4453f0e3505d96afd93f6b096", size = 333984, upload-time = "2024-02-24T22:21:27.561Z" }, -] - -[[package]] -name = "pytest-asyncio" -version = "0.23.8" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pytest" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/de/b4/0b378b7bf26a8ae161c3890c0b48a91a04106c5713ce81b4b080ea2f4f18/pytest_asyncio-0.23.8.tar.gz", hash = "sha256:759b10b33a6dc61cce40a8bd5205e302978bbbcc00e279a8b61d9a6a3c82e4d3", size = 46920, upload-time = "2024-07-17T17:39:34.617Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/82/62e2d63639ecb0fbe8a7ee59ef0bc69a4669ec50f6d3459f74ad4e4189a2/pytest_asyncio-0.23.8-py3-none-any.whl", hash = "sha256:50265d892689a5faefb84df80819d1ecef566eb3549cf915dfb33569359d1ce2", size = 17663, upload-time = "2024-07-17T17:39:32.478Z" }, -] - -[[package]] -name = "pytest-dotenv" -version = "0.5.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pytest" }, - { name = "python-dotenv" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cd/b0/cafee9c627c1bae228eb07c9977f679b3a7cb111b488307ab9594ba9e4da/pytest-dotenv-0.5.2.tar.gz", hash = "sha256:2dc6c3ac6d8764c71c6d2804e902d0ff810fa19692e95fe138aefc9b1aa73732", size = 3782, upload-time = "2020-06-16T12:38:03.4Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/da/9da67c67b3d0963160e3d2cbc7c38b6fae342670cc8e6d5936644b2cf944/pytest_dotenv-0.5.2-py3-none-any.whl", hash = "sha256:40a2cece120a213898afaa5407673f6bd924b1fa7eafce6bda0e8abffe2f710f", size = 3993, upload-time = "2020-06-16T12:38:01.139Z" }, -] - -[[package]] -name = "pytest-md-report" -version = "0.7.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pytablewriter" }, - { name = "pytest" }, - { name = "tcolorpy" }, - { name = "typepy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f6/63/80d92406f952eee7856114c18ad269192ce179d576343fbcf0679c9a2bdd/pytest_md_report-0.7.0.tar.gz", hash = "sha256:3b832eaf660b470b5742e58d9c9a5e312b0712a7012d251cc04e908a81ce3c96", size = 284275, upload-time = "2025-05-02T03:07:09.835Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/de/96/6125a1a963b3864b4a3981c9fce27df60370a889c2e79071e33d325f14d9/pytest_md_report-0.7.0-py3-none-any.whl", hash = "sha256:90ccb3b5b9587d064ec83974db34619addd11d3de6ec7056fab4feb3af69ae94", size = 14250, upload-time = "2025-05-02T03:07:06.539Z" }, -] - -[[package]] -name = "pytest-mock" -version = "3.14.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pytest" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/71/28/67172c96ba684058a4d24ffe144d64783d2a270d0af0d9e792737bddc75c/pytest_mock-3.14.1.tar.gz", hash = "sha256:159e9edac4c451ce77a5cdb9fc5d1100708d2dd4ba3c3df572f14097351af80e", size = 33241, upload-time = "2025-05-26T13:58:45.167Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/05/77b60e520511c53d1c1ca75f1930c7dd8e971d0c4379b7f4b3f9644685ba/pytest_mock-3.14.1-py3-none-any.whl", hash = "sha256:178aefcd11307d874b4cd3100344e7e2d888d9791a6a1d9bfe90fbc1b74fd1d0", size = 9923, upload-time = "2025-05-26T13:58:43.487Z" }, -] - -[[package]] -name = "python-dateutil" -version = "2.9.0.post0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "six" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, -] - -[[package]] -name = "python-dotenv" -version = "1.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115, upload-time = "2024-01-23T06:33:00.505Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863, upload-time = "2024-01-23T06:32:58.246Z" }, -] - -[[package]] -name = "python-magic" -version = "0.4.27" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/da/db/0b3e28ac047452d079d375ec6798bf76a036a08182dbb39ed38116a49130/python-magic-0.4.27.tar.gz", hash = "sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b", size = 14677, upload-time = "2022-06-07T20:16:59.508Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/73/9f872cb81fc5c3bb48f7227872c28975f998f3e7c2b1c16e95e6432bbb90/python_magic-0.4.27-py2.py3-none-any.whl", hash = "sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3", size = 13840, upload-time = "2022-06-07T20:16:57.763Z" }, -] - -[[package]] -name = "pytz" -version = "2025.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, -] - -[[package]] -name = "pywin32" -version = "311" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" }, - { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" }, - { 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 = "pyyaml" -version = "6.0.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, - { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, - { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, - { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, - { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, - { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, - { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, - { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, - { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, - { 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 = "qdrant-client" -version = "1.16.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "grpcio" }, - { name = "httpx", extra = ["http2"] }, - { name = "numpy" }, - { name = "portalocker" }, - { name = "protobuf" }, - { name = "pydantic" }, - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ca/7d/3cd10e26ae97b35cf856ca1dc67576e42414ae39502c51165bb36bb1dff8/qdrant_client-1.16.2.tar.gz", hash = "sha256:ca4ef5f9be7b5eadeec89a085d96d5c723585a391eb8b2be8192919ab63185f0", size = 331112, upload-time = "2025-12-12T10:58:30.866Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/08/13/8ce16f808297e16968269de44a14f4fef19b64d9766be1d6ba5ba78b579d/qdrant_client-1.16.2-py3-none-any.whl", hash = "sha256:442c7ef32ae0f005e88b5d3c0783c63d4912b97ae756eb5e052523be682f17d3", size = 377186, upload-time = "2025-12-12T10:58:29.282Z" }, -] - -[[package]] -name = "redis" -version = "5.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/47/da/d283a37303a995cd36f8b92db85135153dc4f7a8e4441aa827721b442cfb/redis-5.2.1.tar.gz", hash = "sha256:16f2e22dff21d5125e8481515e386711a34cbec50f0e44413dd7d9c060a54e0f", size = 4608355, upload-time = "2024-12-06T09:50:41.956Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/5f/fa26b9b2672cbe30e07d9a5bdf39cf16e3b80b42916757c5f92bca88e4ba/redis-5.2.1-py3-none-any.whl", hash = "sha256:ee7e1056b9aea0f04c6c2ed59452947f34c4940ee025f5dd83e6a6418b6989e4", size = 261502, upload-time = "2024-12-06T09:50:39.656Z" }, -] - -[[package]] -name = "referencing" -version = "0.36.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "attrs" }, - { name = "rpds-py" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744, upload-time = "2025-01-25T08:48:16.138Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775, upload-time = "2025-01-25T08:48:14.241Z" }, -] - -[[package]] -name = "regex" -version = "2025.9.18" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/49/d3/eaa0d28aba6ad1827ad1e716d9a93e1ba963ada61887498297d3da715133/regex-2025.9.18.tar.gz", hash = "sha256:c5ba23274c61c6fef447ba6a39333297d0c247f53059dba0bca415cac511edc4", size = 400917, upload-time = "2025-09-19T00:38:35.79Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/99/05859d87a66ae7098222d65748f11ef7f2dff51bfd7482a4e2256c90d72b/regex-2025.9.18-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:436e1b31d7efd4dcd52091d076482031c611dde58bf9c46ca6d0a26e33053a7e", size = 486335, upload-time = "2025-09-19T00:36:03.661Z" }, - { url = "https://files.pythonhosted.org/packages/97/7e/d43d4e8b978890932cf7b0957fce58c5b08c66f32698f695b0c2c24a48bf/regex-2025.9.18-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c190af81e5576b9c5fdc708f781a52ff20f8b96386c6e2e0557a78402b029f4a", size = 289720, upload-time = "2025-09-19T00:36:05.471Z" }, - { url = "https://files.pythonhosted.org/packages/bb/3b/ff80886089eb5dcf7e0d2040d9aaed539e25a94300403814bb24cc775058/regex-2025.9.18-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e4121f1ce2b2b5eec4b397cc1b277686e577e658d8f5870b7eb2d726bd2300ab", size = 287257, upload-time = "2025-09-19T00:36:07.072Z" }, - { url = "https://files.pythonhosted.org/packages/ee/66/243edf49dd8720cba8d5245dd4d6adcb03a1defab7238598c0c97cf549b8/regex-2025.9.18-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:300e25dbbf8299d87205e821a201057f2ef9aa3deb29caa01cd2cac669e508d5", size = 797463, upload-time = "2025-09-19T00:36:08.399Z" }, - { url = "https://files.pythonhosted.org/packages/df/71/c9d25a1142c70432e68bb03211d4a82299cd1c1fbc41db9409a394374ef5/regex-2025.9.18-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7b47fcf9f5316c0bdaf449e879407e1b9937a23c3b369135ca94ebc8d74b1742", size = 862670, upload-time = "2025-09-19T00:36:10.101Z" }, - { url = "https://files.pythonhosted.org/packages/f8/8f/329b1efc3a64375a294e3a92d43372bf1a351aa418e83c21f2f01cf6ec41/regex-2025.9.18-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:57a161bd3acaa4b513220b49949b07e252165e6b6dc910ee7617a37ff4f5b425", size = 910881, upload-time = "2025-09-19T00:36:12.223Z" }, - { url = "https://files.pythonhosted.org/packages/35/9e/a91b50332a9750519320ed30ec378b74c996f6befe282cfa6bb6cea7e9fd/regex-2025.9.18-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f130c3a7845ba42de42f380fff3c8aebe89a810747d91bcf56d40a069f15352", size = 802011, upload-time = "2025-09-19T00:36:13.901Z" }, - { url = "https://files.pythonhosted.org/packages/a4/1d/6be3b8d7856b6e0d7ee7f942f437d0a76e0d5622983abbb6d21e21ab9a17/regex-2025.9.18-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5f96fa342b6f54dcba928dd452e8d8cb9f0d63e711d1721cd765bb9f73bb048d", size = 786668, upload-time = "2025-09-19T00:36:15.391Z" }, - { url = "https://files.pythonhosted.org/packages/cb/ce/4a60e53df58bd157c5156a1736d3636f9910bdcc271d067b32b7fcd0c3a8/regex-2025.9.18-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0f0d676522d68c207828dcd01fb6f214f63f238c283d9f01d85fc664c7c85b56", size = 856578, upload-time = "2025-09-19T00:36:16.845Z" }, - { url = "https://files.pythonhosted.org/packages/86/e8/162c91bfe7217253afccde112868afb239f94703de6580fb235058d506a6/regex-2025.9.18-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:40532bff8a1a0621e7903ae57fce88feb2e8a9a9116d341701302c9302aef06e", size = 849017, upload-time = "2025-09-19T00:36:18.597Z" }, - { url = "https://files.pythonhosted.org/packages/35/34/42b165bc45289646ea0959a1bc7531733e90b47c56a72067adfe6b3251f6/regex-2025.9.18-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:039f11b618ce8d71a1c364fdee37da1012f5a3e79b1b2819a9f389cd82fd6282", size = 788150, upload-time = "2025-09-19T00:36:20.464Z" }, - { url = "https://files.pythonhosted.org/packages/79/5d/cdd13b1f3c53afa7191593a7ad2ee24092a5a46417725ffff7f64be8342d/regex-2025.9.18-cp312-cp312-win32.whl", hash = "sha256:e1dd06f981eb226edf87c55d523131ade7285137fbde837c34dc9d1bf309f459", size = 264536, upload-time = "2025-09-19T00:36:21.922Z" }, - { url = "https://files.pythonhosted.org/packages/e0/f5/4a7770c9a522e7d2dc1fa3ffc83ab2ab33b0b22b447e62cffef186805302/regex-2025.9.18-cp312-cp312-win_amd64.whl", hash = "sha256:3d86b5247bf25fa3715e385aa9ff272c307e0636ce0c9595f64568b41f0a9c77", size = 275501, upload-time = "2025-09-19T00:36:23.4Z" }, - { url = "https://files.pythonhosted.org/packages/df/05/9ce3e110e70d225ecbed455b966003a3afda5e58e8aec2964042363a18f4/regex-2025.9.18-cp312-cp312-win_arm64.whl", hash = "sha256:032720248cbeeae6444c269b78cb15664458b7bb9ed02401d3da59fe4d68c3a5", size = 268601, upload-time = "2025-09-19T00:36:25.092Z" }, -] - -[[package]] -name = "requests" -version = "2.31.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "charset-normalizer" }, - { name = "idna" }, - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9d/be/10918a2eac4ae9f02f6cfe6414b7a155ccd8f7f9d4380d62fd5b955065c3/requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1", size = 110794, upload-time = "2023-05-22T15:12:44.175Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/70/8e/0e2d847013cb52cd35b38c009bb167a1a26b2ce6cd6965bf26b47bc0bf44/requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", size = 62574, upload-time = "2023-05-22T15:12:42.313Z" }, -] - -[[package]] -name = "requests-oauthlib" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "oauthlib" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/42/f2/05f29bc3913aea15eb670be136045bf5c5bbf4b99ecb839da9b422bb2c85/requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9", size = 55650, upload-time = "2024-03-22T20:32:29.939Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/5d/63d4ae3b9daea098d5d6f5da83984853c1bbacd5dc826764b249fe119d24/requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36", size = 24179, upload-time = "2024-03-22T20:32:28.055Z" }, -] - -[[package]] -name = "rpds-py" -version = "0.27.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e9/dd/2c0cbe774744272b0ae725f44032c77bdcab6e8bcf544bffa3b6e70c8dba/rpds_py-0.27.1.tar.gz", hash = "sha256:26a1c73171d10b7acccbded82bf6a586ab8203601e565badc74bbbf8bc5a10f8", size = 27479, upload-time = "2025-08-27T12:16:36.024Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/fe/38de28dee5df58b8198c743fe2bea0c785c6d40941b9950bac4cdb71a014/rpds_py-0.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ae2775c1973e3c30316892737b91f9283f9908e3cc7625b9331271eaaed7dc90", size = 361887, upload-time = "2025-08-27T12:13:10.233Z" }, - { url = "https://files.pythonhosted.org/packages/7c/9a/4b6c7eedc7dd90986bf0fab6ea2a091ec11c01b15f8ba0a14d3f80450468/rpds_py-0.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2643400120f55c8a96f7c9d858f7be0c88d383cd4653ae2cf0d0c88f668073e5", size = 345795, upload-time = "2025-08-27T12:13:11.65Z" }, - { url = "https://files.pythonhosted.org/packages/6f/0e/e650e1b81922847a09cca820237b0edee69416a01268b7754d506ade11ad/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16323f674c089b0360674a4abd28d5042947d54ba620f72514d69be4ff64845e", size = 385121, upload-time = "2025-08-27T12:13:13.008Z" }, - { url = "https://files.pythonhosted.org/packages/1b/ea/b306067a712988e2bff00dcc7c8f31d26c29b6d5931b461aa4b60a013e33/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a1f4814b65eacac94a00fc9a526e3fdafd78e439469644032032d0d63de4881", size = 398976, upload-time = "2025-08-27T12:13:14.368Z" }, - { url = "https://files.pythonhosted.org/packages/2c/0a/26dc43c8840cb8fe239fe12dbc8d8de40f2365e838f3d395835dde72f0e5/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ba32c16b064267b22f1850a34051121d423b6f7338a12b9459550eb2096e7ec", size = 525953, upload-time = "2025-08-27T12:13:15.774Z" }, - { url = "https://files.pythonhosted.org/packages/22/14/c85e8127b573aaf3a0cbd7fbb8c9c99e735a4a02180c84da2a463b766e9e/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5c20f33fd10485b80f65e800bbe5f6785af510b9f4056c5a3c612ebc83ba6cb", size = 407915, upload-time = "2025-08-27T12:13:17.379Z" }, - { url = "https://files.pythonhosted.org/packages/ed/7b/8f4fee9ba1fb5ec856eb22d725a4efa3deb47f769597c809e03578b0f9d9/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:466bfe65bd932da36ff279ddd92de56b042f2266d752719beb97b08526268ec5", size = 386883, upload-time = "2025-08-27T12:13:18.704Z" }, - { url = "https://files.pythonhosted.org/packages/86/47/28fa6d60f8b74fcdceba81b272f8d9836ac0340570f68f5df6b41838547b/rpds_py-0.27.1-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:41e532bbdcb57c92ba3be62c42e9f096431b4cf478da9bc3bc6ce5c38ab7ba7a", size = 405699, upload-time = "2025-08-27T12:13:20.089Z" }, - { url = "https://files.pythonhosted.org/packages/d0/fd/c5987b5e054548df56953a21fe2ebed51fc1ec7c8f24fd41c067b68c4a0a/rpds_py-0.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f149826d742b406579466283769a8ea448eed82a789af0ed17b0cd5770433444", size = 423713, upload-time = "2025-08-27T12:13:21.436Z" }, - { url = "https://files.pythonhosted.org/packages/ac/ba/3c4978b54a73ed19a7d74531be37a8bcc542d917c770e14d372b8daea186/rpds_py-0.27.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:80c60cfb5310677bd67cb1e85a1e8eb52e12529545441b43e6f14d90b878775a", size = 562324, upload-time = "2025-08-27T12:13:22.789Z" }, - { url = "https://files.pythonhosted.org/packages/b5/6c/6943a91768fec16db09a42b08644b960cff540c66aab89b74be6d4a144ba/rpds_py-0.27.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7ee6521b9baf06085f62ba9c7a3e5becffbc32480d2f1b351559c001c38ce4c1", size = 593646, upload-time = "2025-08-27T12:13:24.122Z" }, - { url = "https://files.pythonhosted.org/packages/11/73/9d7a8f4be5f4396f011a6bb7a19fe26303a0dac9064462f5651ced2f572f/rpds_py-0.27.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a512c8263249a9d68cac08b05dd59d2b3f2061d99b322813cbcc14c3c7421998", size = 558137, upload-time = "2025-08-27T12:13:25.557Z" }, - { url = "https://files.pythonhosted.org/packages/6e/96/6772cbfa0e2485bcceef8071de7821f81aeac8bb45fbfd5542a3e8108165/rpds_py-0.27.1-cp312-cp312-win32.whl", hash = "sha256:819064fa048ba01b6dadc5116f3ac48610435ac9a0058bbde98e569f9e785c39", size = 221343, upload-time = "2025-08-27T12:13:26.967Z" }, - { url = "https://files.pythonhosted.org/packages/67/b6/c82f0faa9af1c6a64669f73a17ee0eeef25aff30bb9a1c318509efe45d84/rpds_py-0.27.1-cp312-cp312-win_amd64.whl", hash = "sha256:d9199717881f13c32c4046a15f024971a3b78ad4ea029e8da6b86e5aa9cf4594", size = 232497, upload-time = "2025-08-27T12:13:28.326Z" }, - { url = "https://files.pythonhosted.org/packages/e1/96/2817b44bd2ed11aebacc9251da03689d56109b9aba5e311297b6902136e2/rpds_py-0.27.1-cp312-cp312-win_arm64.whl", hash = "sha256:33aa65b97826a0e885ef6e278fbd934e98cdcfed80b63946025f01e2f5b29502", size = 222790, upload-time = "2025-08-27T12:13:29.71Z" }, -] - -[[package]] -name = "rsa" -version = "4.9.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyasn1" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034, upload-time = "2025-04-16T09:51:18.218Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" }, -] - -[[package]] -name = "s3fs" -version = "2024.10.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "aiobotocore" }, - { name = "aiohttp" }, - { name = "fsspec" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/75/65/4b4c868cff76c036d11dc75dd91e5696dbf16ce626514166f35d5f4a930f/s3fs-2024.10.0.tar.gz", hash = "sha256:58b8c3650f8b99dbedf361543da3533aac8707035a104db5d80b094617ad4a3f", size = 75916, upload-time = "2024-10-21T01:45:49.967Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/99/44/bb9ff095ae7b1b6908480f683b6ca6b71c2105d343a5e5cb25334b01f5fa/s3fs-2024.10.0-py3-none-any.whl", hash = "sha256:7a2025d60d5b1a6025726b3a5e292a8e5aa713abc3b16fd1f81735181f7bb282", size = 29855, upload-time = "2024-10-21T01:45:47.905Z" }, -] - -[package.optional-dependencies] -boto3 = [ - { name = "aiobotocore", extra = ["boto3"] }, -] - -[[package]] -name = "s3transfer" -version = "0.10.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "botocore" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c0/0a/1cdbabf9edd0ea7747efdf6c9ab4e7061b085aa7f9bfc36bb1601563b069/s3transfer-0.10.4.tar.gz", hash = "sha256:29edc09801743c21eb5ecbc617a152df41d3c287f67b615f73e5f750583666a7", size = 145287, upload-time = "2024-11-20T21:06:05.981Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/66/05/7957af15543b8c9799209506df4660cba7afc4cf94bfb60513827e96bed6/s3transfer-0.10.4-py3-none-any.whl", hash = "sha256:244a76a24355363a68164241438de1b72f8781664920260c48465896b712a41e", size = 83175, upload-time = "2024-11-20T21:06:03.961Z" }, -] - -[[package]] -name = "setuptools" -version = "80.9.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, -] - -[[package]] -name = "singleton-decorator" -version = "1.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/33/98/a8b5c919bee1152a9a1afd82014431f8db5882699754de50d1b3aba4d136/singleton-decorator-1.0.0.tar.gz", hash = "sha256:1a90ad8a8a738be591c9c167fdd677c5d4a43d1bc6b1c128227be1c5e03bee07", size = 2791, upload-time = "2017-08-10T19:52:45.903Z" } - -[[package]] -name = "six" -version = "1.17.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, -] - -[[package]] -name = "sniffio" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } -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 = "soupsieve" -version = "2.8" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6d/e6/21ccce3262dd4889aa3332e5a119a3491a95e8f60939870a3a035aabac0d/soupsieve-2.8.tar.gz", hash = "sha256:e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f", size = 103472, upload-time = "2025-08-27T15:39:51.78Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl", hash = "sha256:0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c", size = 36679, upload-time = "2025-08-27T15:39:50.179Z" }, -] - -[[package]] -name = "sqlalchemy" -version = "2.0.43" -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/d7/bc/d59b5d97d27229b0e009bd9098cd81af71c2fa5549c580a0a67b9bed0496/sqlalchemy-2.0.43.tar.gz", hash = "sha256:788bfcef6787a7764169cfe9859fe425bf44559619e1d9f56f5bddf2ebf6f417", size = 9762949, upload-time = "2025-08-11T14:24:58.438Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/61/db/20c78f1081446095450bdc6ee6cc10045fce67a8e003a5876b6eaafc5cc4/sqlalchemy-2.0.43-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:20d81fc2736509d7a2bd33292e489b056cbae543661bb7de7ce9f1c0cd6e7f24", size = 2134891, upload-time = "2025-08-11T15:51:13.019Z" }, - { url = "https://files.pythonhosted.org/packages/45/0a/3d89034ae62b200b4396f0f95319f7d86e9945ee64d2343dcad857150fa2/sqlalchemy-2.0.43-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25b9fc27650ff5a2c9d490c13c14906b918b0de1f8fcbb4c992712d8caf40e83", size = 2123061, upload-time = "2025-08-11T15:51:14.319Z" }, - { url = "https://files.pythonhosted.org/packages/cb/10/2711f7ff1805919221ad5bee205971254845c069ee2e7036847103ca1e4c/sqlalchemy-2.0.43-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6772e3ca8a43a65a37c88e2f3e2adfd511b0b1da37ef11ed78dea16aeae85bd9", size = 3320384, upload-time = "2025-08-11T15:52:35.088Z" }, - { url = "https://files.pythonhosted.org/packages/6e/0e/3d155e264d2ed2778484006ef04647bc63f55b3e2d12e6a4f787747b5900/sqlalchemy-2.0.43-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a113da919c25f7f641ffbd07fbc9077abd4b3b75097c888ab818f962707eb48", size = 3329648, upload-time = "2025-08-11T15:56:34.153Z" }, - { url = "https://files.pythonhosted.org/packages/5b/81/635100fb19725c931622c673900da5efb1595c96ff5b441e07e3dd61f2be/sqlalchemy-2.0.43-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4286a1139f14b7d70141c67a8ae1582fc2b69105f1b09d9573494eb4bb4b2687", size = 3258030, upload-time = "2025-08-11T15:52:36.933Z" }, - { url = "https://files.pythonhosted.org/packages/0c/ed/a99302716d62b4965fded12520c1cbb189f99b17a6d8cf77611d21442e47/sqlalchemy-2.0.43-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:529064085be2f4d8a6e5fab12d36ad44f1909a18848fcfbdb59cc6d4bbe48efe", size = 3294469, upload-time = "2025-08-11T15:56:35.553Z" }, - { url = "https://files.pythonhosted.org/packages/5d/a2/3a11b06715149bf3310b55a98b5c1e84a42cfb949a7b800bc75cb4e33abc/sqlalchemy-2.0.43-cp312-cp312-win32.whl", hash = "sha256:b535d35dea8bbb8195e7e2b40059e2253acb2b7579b73c1b432a35363694641d", size = 2098906, upload-time = "2025-08-11T15:55:00.645Z" }, - { url = "https://files.pythonhosted.org/packages/bc/09/405c915a974814b90aa591280623adc6ad6b322f61fd5cff80aeaef216c9/sqlalchemy-2.0.43-cp312-cp312-win_amd64.whl", hash = "sha256:1c6d85327ca688dbae7e2b06d7d84cfe4f3fffa5b5f9e21bb6ce9d0e1a0e0e0a", size = 2126260, upload-time = "2025-08-11T15:55:02.965Z" }, - { url = "https://files.pythonhosted.org/packages/b8/d9/13bdde6521f322861fab67473cec4b1cc8999f3871953531cf61945fad92/sqlalchemy-2.0.43-py3-none-any.whl", hash = "sha256:1681c21dd2ccee222c2fe0bef671d1aef7c504087c9c4e800371cfcc8ac966fc", size = 1924759, upload-time = "2025-08-11T15:39:53.024Z" }, -] - -[package.optional-dependencies] -asyncio = [ - { name = "greenlet" }, -] - -[[package]] -name = "striprtf" -version = "0.0.26" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/25/20/3d419008265346452d09e5dadfd5d045b64b40d8fc31af40588e6c76997a/striprtf-0.0.26.tar.gz", hash = "sha256:fdb2bba7ac440072d1c41eab50d8d74ae88f60a8b6575c6e2c7805dc462093aa", size = 6258, upload-time = "2023-07-20T14:30:36.29Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/cf/0fea4f4ba3fc2772ac2419278aa9f6964124d4302117d61bc055758e000c/striprtf-0.0.26-py3-none-any.whl", hash = "sha256:8c8f9d32083cdc2e8bfb149455aa1cc5a4e0a035893bedc75db8b73becb3a1bb", size = 6914, upload-time = "2023-07-20T14:30:35.338Z" }, -] - -[[package]] -name = "tabledata" -version = "1.3.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "dataproperty" }, - { name = "typepy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b2/35/171c8977162f1163368406deddde4c59673b62bd0cb2f34948a02effb075/tabledata-1.3.4.tar.gz", hash = "sha256:e9649cab129d718f3bff4150083b77f8a78c30f6634a30caf692b10fdc60cb97", size = 25074, upload-time = "2024-12-31T14:12:31.198Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/08/64/fa4160151976ee4b2cf0c1217a99443ffaeb991956feddfeac9eee9952f8/tabledata-1.3.4-py3-none-any.whl", hash = "sha256:1f56e433bfdeb89f4487abfa48c4603a3b07c5d3a3c7e05ff73dd018c24bd0d4", size = 11820, upload-time = "2024-12-31T14:12:28.584Z" }, -] - -[[package]] -name = "tcolorpy" -version = "0.1.7" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/80/cc/44f2d81d8f9093aad81c3467a5bf5718d2b5f786e887b6e4adcfc17ec6b9/tcolorpy-0.1.7.tar.gz", hash = "sha256:0fbf6bf238890bbc2e32662aa25736769a29bf6d880328f310c910a327632614", size = 299437, upload-time = "2024-12-29T15:24:23.847Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/05/a2/ed023f2edd1e011b4d99b6727bce8253842d66c3fbf9ed0a26fc09a92571/tcolorpy-0.1.7-py3-none-any.whl", hash = "sha256:26a59d52027e175a37e0aba72efc99dda43f074db71f55b316d3de37d3251378", size = 8096, upload-time = "2024-12-29T15:24:21.33Z" }, -] - -[[package]] -name = "tenacity" -version = "9.1.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0a/d4/2b0cd0fe285e14b36db076e78c93766ff1d529d70408bd1d2a5a84f1d929/tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb", size = 48036, upload-time = "2025-04-02T08:25:09.966Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/30/643397144bfbfec6f6ef821f36f33e57d35946c44a2352d3c9f0ae847619/tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138", size = 28248, upload-time = "2025-04-02T08:25:07.678Z" }, -] - -[[package]] -name = "tiktoken" -version = "0.12.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" } -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.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, -] - -[[package]] -name = "typepy" -version = "1.3.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mbstrdecoder" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/79/59/4c39942077d7de285f762a91024dbda731be693591732977358f77d120fb/typepy-1.3.4.tar.gz", hash = "sha256:89c1f66de6c6133209c43a94d23431d320ba03ef5db18f241091ea594035d9de", size = 39558, upload-time = "2024-12-29T09:18:15.774Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/31/e393c3830bdedd01735bd195c85ac3034b6bcaf6c18142bab60a4047ca36/typepy-1.3.4-py3-none-any.whl", hash = "sha256:d5ed3e0c7f49521bff0603dd08cf8d453371cf68d65a29d3d0038552ccc46e2e", size = 31449, upload-time = "2024-12-29T09:18:13.135Z" }, -] - -[package.optional-dependencies] -datetime = [ - { name = "packaging" }, - { name = "python-dateutil" }, - { name = "pytz" }, -] - -[[package]] -name = "typing-extensions" -version = "4.15.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, -] - -[[package]] -name = "typing-inspect" -version = "0.9.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mypy-extensions" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/dc/74/1789779d91f1961fa9438e9a8710cdae6bd138c80d7303996933d117264a/typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78", size = 13825, upload-time = "2023-05-24T20:25:47.612Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/65/f3/107a22063bf27bdccf2024833d3445f4eea42b2e598abfbd46f6a63b6cb0/typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f", size = 8827, upload-time = "2023-05-24T20:25:45.287Z" }, -] - -[[package]] -name = "typing-inspection" -version = "0.4.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } -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.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, -] - -[[package]] -name = "ujson" -version = "5.11.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/d9/3f17e3c5773fb4941c68d9a37a47b1a79c9649d6c56aefbed87cc409d18a/ujson-5.11.0.tar.gz", hash = "sha256:e204ae6f909f099ba6b6b942131cee359ddda2b6e4ea39c12eb8b991fe2010e0", size = 7156583, upload-time = "2025-08-20T11:57:02.452Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/ef/a9cb1fce38f699123ff012161599fb9f2ff3f8d482b4b18c43a2dc35073f/ujson-5.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7895f0d2d53bd6aea11743bd56e3cb82d729980636cd0ed9b89418bf66591702", size = 55434, upload-time = "2025-08-20T11:55:34.987Z" }, - { url = "https://files.pythonhosted.org/packages/b1/05/dba51a00eb30bd947791b173766cbed3492269c150a7771d2750000c965f/ujson-5.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12b5e7e22a1fe01058000d1b317d3b65cc3daf61bd2ea7a2b76721fe160fa74d", size = 53190, upload-time = "2025-08-20T11:55:36.384Z" }, - { url = "https://files.pythonhosted.org/packages/03/3c/fd11a224f73fbffa299fb9644e425f38b38b30231f7923a088dd513aabb4/ujson-5.11.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0180a480a7d099082501cad1fe85252e4d4bf926b40960fb3d9e87a3a6fbbc80", size = 57600, upload-time = "2025-08-20T11:55:37.692Z" }, - { url = "https://files.pythonhosted.org/packages/55/b9/405103cae24899df688a3431c776e00528bd4799e7d68820e7ebcf824f92/ujson-5.11.0-cp312-cp312-manylinux_2_24_i686.manylinux_2_28_i686.whl", hash = "sha256:fa79fdb47701942c2132a9dd2297a1a85941d966d8c87bfd9e29b0cf423f26cc", size = 59791, upload-time = "2025-08-20T11:55:38.877Z" }, - { url = "https://files.pythonhosted.org/packages/17/7b/2dcbc2bbfdbf68f2368fb21ab0f6735e872290bb604c75f6e06b81edcb3f/ujson-5.11.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8254e858437c00f17cb72e7a644fc42dad0ebb21ea981b71df6e84b1072aaa7c", size = 57356, upload-time = "2025-08-20T11:55:40.036Z" }, - { url = "https://files.pythonhosted.org/packages/d1/71/fea2ca18986a366c750767b694430d5ded6b20b6985fddca72f74af38a4c/ujson-5.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1aa8a2ab482f09f6c10fba37112af5f957689a79ea598399c85009f2f29898b5", size = 1036313, upload-time = "2025-08-20T11:55:41.408Z" }, - { url = "https://files.pythonhosted.org/packages/a3/bb/d4220bd7532eac6288d8115db51710fa2d7d271250797b0bfba9f1e755af/ujson-5.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a638425d3c6eed0318df663df44480f4a40dc87cc7c6da44d221418312f6413b", size = 1195782, upload-time = "2025-08-20T11:55:43.357Z" }, - { url = "https://files.pythonhosted.org/packages/80/47/226e540aa38878ce1194454385701d82df538ccb5ff8db2cf1641dde849a/ujson-5.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7e3cff632c1d78023b15f7e3a81c3745cd3f94c044d1e8fa8efbd6b161997bbc", size = 1088817, upload-time = "2025-08-20T11:55:45.262Z" }, - { url = "https://files.pythonhosted.org/packages/7e/81/546042f0b23c9040d61d46ea5ca76f0cc5e0d399180ddfb2ae976ebff5b5/ujson-5.11.0-cp312-cp312-win32.whl", hash = "sha256:be6b0eaf92cae8cdee4d4c9e074bde43ef1c590ed5ba037ea26c9632fb479c88", size = 39757, upload-time = "2025-08-20T11:55:46.522Z" }, - { url = "https://files.pythonhosted.org/packages/44/1b/27c05dc8c9728f44875d74b5bfa948ce91f6c33349232619279f35c6e817/ujson-5.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:b7b136cc6abc7619124fd897ef75f8e63105298b5ca9bdf43ebd0e1fa0ee105f", size = 43859, upload-time = "2025-08-20T11:55:47.987Z" }, - { url = "https://files.pythonhosted.org/packages/22/2d/37b6557c97c3409c202c838aa9c960ca3896843b4295c4b7bb2bbd260664/ujson-5.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:6cd2df62f24c506a0ba322d5e4fe4466d47a9467b57e881ee15a31f7ecf68ff6", size = 38361, upload-time = "2025-08-20T11:55:49.122Z" }, -] - -[[package]] -name = "unstract-core" -version = "0.0.1" -source = { editable = "../unstract/core" } -dependencies = [ - { name = "httpx" }, - { name = "kombu" }, - { name = "redis" }, - { name = "requests" }, -] - -[package.metadata] -requires-dist = [ - { name = "flask", marker = "extra == 'flask'", specifier = "~=3.1.0" }, - { name = "httpx", specifier = ">=0.27.0" }, - { name = "kombu", specifier = "~=5.5.3" }, - { name = "redis", specifier = "~=5.2.1" }, - { name = "requests", specifier = "==2.31.0" }, -] -provides-extras = ["flask"] - -[[package]] -name = "unstract-flags" -version = "0.0.1" -source = { editable = "../unstract/flags" } -dependencies = [ - { name = "grpcio" }, - { name = "grpcio-tools" }, - { name = "protobuf" }, -] - -[package.metadata] -requires-dist = [ - { name = "grpcio", specifier = ">=1.60.0" }, - { name = "grpcio-tools", specifier = ">=1.60.0" }, - { name = "protobuf", specifier = ">=4.25.0" }, -] - -[[package]] -name = "unstract-prompt-service" -version = "0.0.1" -source = { editable = "." } -dependencies = [ - { name = "flask" }, - { name = "json-repair" }, - { name = "llama-index" }, - { name = "nltk" }, - { name = "peewee" }, - { name = "python-dotenv" }, - { name = "redis" }, - { name = "requests" }, - { name = "unstract-core" }, - { name = "unstract-flags" }, - { name = "unstract-sdk1", extra = ["aws", "azure", "gcs"] }, -] - -[package.dev-dependencies] -deploy = [ - { name = "gunicorn" }, - { name = "opentelemetry-distro" }, - { name = "opentelemetry-exporter-otlp" }, -] -dev = [ - { name = "debugpy" }, - { name = "poethepoet" }, -] -test = [ - { name = "flask-wtf" }, - { name = "pytest" }, - { name = "pytest-asyncio" }, - { name = "pytest-dotenv" }, - { name = "pytest-md-report" }, - { name = "pytest-mock" }, - { name = "python-dotenv" }, -] - -[package.metadata] -requires-dist = [ - { name = "flask", specifier = "~=3.0" }, - { name = "json-repair", specifier = "~=0.42.0" }, - { name = "llama-index", specifier = ">=0.14.13" }, - { name = "nltk", specifier = "~=3.8" }, - { name = "peewee", specifier = "~=3.16" }, - { name = "python-dotenv", specifier = "==1.0.1" }, - { name = "redis", specifier = ">=5.0.3,<5.3" }, - { name = "requests", specifier = ">=2.28,<3.0" }, - { name = "unstract-core", editable = "../unstract/core" }, - { name = "unstract-flags", editable = "../unstract/flags" }, - { name = "unstract-sdk1", extras = ["aws", "gcs", "azure"], editable = "../unstract/sdk1" }, -] - -[package.metadata.requires-dev] -deploy = [ - { name = "gunicorn", specifier = "~=23.0" }, - { name = "opentelemetry-distro" }, - { name = "opentelemetry-exporter-otlp" }, -] -dev = [ - { name = "debugpy", specifier = ">=1.8.14" }, - { name = "poethepoet", specifier = ">=0.33.1" }, -] -test = [ - { name = "flask-wtf", specifier = "~=1.1" }, - { name = "pytest", specifier = "~=8.0.1" }, - { name = "pytest-asyncio", specifier = ">=0.23.0" }, - { name = "pytest-dotenv", specifier = "==0.5.2" }, - { name = "pytest-md-report", specifier = ">=0.6.2" }, - { name = "pytest-mock", specifier = "~=3.14.0" }, - { name = "python-dotenv", specifier = "==1.0.1" }, -] - -[[package]] -name = "unstract-sdk1" -source = { editable = "../unstract/sdk1" } -dependencies = [ - { name = "filetype" }, - { name = "httpx" }, - { name = "jsonschema" }, - { name = "litellm" }, - { name = "llama-index" }, - { name = "llama-index-vector-stores-milvus" }, - { name = "llama-index-vector-stores-pinecone" }, - { name = "llama-index-vector-stores-postgres" }, - { name = "llama-index-vector-stores-qdrant" }, - { name = "llama-index-vector-stores-weaviate" }, - { name = "llama-parse" }, - { name = "llmwhisperer-client" }, - { name = "pdfplumber" }, - { name = "python-dotenv" }, - { name = "python-magic" }, - { name = "qdrant-client" }, - { name = "redis" }, - { name = "singleton-decorator" }, - { name = "tiktoken" }, - { name = "unstract-core" }, -] - -[package.optional-dependencies] -aws = [ - { name = "boto3" }, - { name = "s3fs", extra = ["boto3"] }, -] -azure = [ - { name = "adlfs" }, -] -gcs = [ - { name = "gcsfs" }, -] - -[package.metadata] -requires-dist = [ - { name = "adlfs", marker = "extra == 'azure'", specifier = "~=2024.7.0" }, - { name = "boto3", marker = "extra == 'aws'", specifier = "~=1.34.131" }, - { name = "filetype", specifier = "~=1.2.0" }, - { name = "gcsfs", marker = "extra == 'gcs'", specifier = "~=2024.10.0" }, - { name = "httpx", specifier = ">=0.25.2" }, - { name = "jsonschema" }, - { name = "litellm", specifier = "==1.83.10" }, - { name = "llama-index", specifier = ">=0.14.13" }, - { name = "llama-index-vector-stores-milvus", specifier = ">=0.9.6" }, - { name = "llama-index-vector-stores-pinecone", specifier = ">=0.7.1" }, - { name = "llama-index-vector-stores-postgres", specifier = ">=0.7.3" }, - { name = "llama-index-vector-stores-qdrant", specifier = ">=0.9.1" }, - { name = "llama-index-vector-stores-weaviate", specifier = ">=1.4.1" }, - { name = "llama-parse", specifier = ">=0.6.0" }, - { name = "llmwhisperer-client", specifier = ">=2.6.2" }, - { name = "pdfplumber", specifier = ">=0.11.2" }, - { name = "python-dotenv", specifier = "==1.0.1" }, - { name = "python-magic", specifier = "~=0.4.27" }, - { name = "qdrant-client", specifier = ">=1.16.0,<1.17.0" }, - { name = "redis", specifier = ">=5.2.1" }, - { name = "s3fs", extras = ["boto3"], marker = "extra == 'aws'", specifier = "~=2024.10.0" }, - { name = "singleton-decorator", specifier = "~=1.0.0" }, - { name = "tiktoken", specifier = "~=0.12.0" }, - { name = "unstract-core", editable = "../unstract/core" }, -] -provides-extras = ["aws", "azure", "gcs"] - -[package.metadata.requires-dev] -dev = [ - { name = "docutils", specifier = "~=0.20.1" }, - { name = "mypy", specifier = "~=1.2.0" }, - { name = "pre-commit", specifier = "~=3.3.1" }, - { name = "pycln", specifier = ">=2.5.0" }, - { name = "pytest", specifier = ">=8.0.1" }, - { name = "ruff", specifier = ">=0.2.2,<1.0.0" }, - { name = "yamllint", specifier = ">=1.35.1" }, -] -docs = [{ name = "lazydocs", specifier = "~=0.4.8" }] -test = [ - { name = "parameterized", specifier = "==0.9.0" }, - { name = "pytest", specifier = "==8.3.3" }, - { name = "pytest-asyncio", specifier = ">=0.24.0" }, - { name = "pytest-cov", specifier = ">=6.0.0" }, - { name = "pytest-md-report", specifier = ">=0.6.2" }, - { name = "pytest-mock", specifier = "==3.14.0" }, -] - -[[package]] -name = "urllib3" -version = "2.5.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, -] - -[[package]] -name = "validators" -version = "0.35.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/53/66/a435d9ae49850b2f071f7ebd8119dd4e84872b01630d6736761e6e7fd847/validators-0.35.0.tar.gz", hash = "sha256:992d6c48a4e77c81f1b4daba10d16c3a9bb0dbb79b3a19ea847ff0928e70497a", size = 73399, upload-time = "2025-05-01T05:42:06.7Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/6e/3e955517e22cbdd565f2f8b2e73d52528b14b8bcfdb04f62466b071de847/validators-0.35.0-py3-none-any.whl", hash = "sha256:e8c947097eae7892cb3d26868d637f79f47b4a0554bc6b80065dfe5aac3705dd", size = 44712, upload-time = "2025-05-01T05:42:04.203Z" }, -] - -[[package]] -name = "vine" -version = "5.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bd/e4/d07b5f29d283596b9727dd5275ccbceb63c44a1a82aa9e4bfd20426762ac/vine-5.1.0.tar.gz", hash = "sha256:8b62e981d35c41049211cf62a0a1242d8c1ee9bd15bb196ce38aefd6799e61e0", size = 48980, upload-time = "2023-11-05T08:46:53.857Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/03/ff/7c0c86c43b3cbb927e0ccc0255cb4057ceba4799cd44ae95174ce8e8b5b2/vine-5.1.0-py3-none-any.whl", hash = "sha256:40fdf3c48b2cfe1c38a49e9ae2da6fda88e4794c810050a728bd7413811fb1dc", size = 9636, upload-time = "2023-11-05T08:46:51.205Z" }, -] - -[[package]] -name = "weaviate-client" -version = "4.17.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "authlib" }, - { name = "deprecation" }, - { name = "grpcio" }, - { name = "httpx" }, - { name = "protobuf" }, - { name = "pydantic" }, - { name = "validators" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/bd/0e/e4582b007427187a9fde55fa575db4b766c81929d2b43a3dd8becce50567/weaviate_client-4.17.0.tar.gz", hash = "sha256:731d58d84b0989df4db399b686357ed285fb95971a492ccca8dec90bb2343c51", size = 769019, upload-time = "2025-09-26T11:20:27.381Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5b/c5/2da3a45866da7a935dab8ad07be05dcaee48b3ad4955144583b651929be7/weaviate_client-4.17.0-py3-none-any.whl", hash = "sha256:60e4a355b90537ee1e942ab0b76a94750897a13d9cf13c5a6decbd166d0ca8b5", size = 582763, upload-time = "2025-09-26T11:20:25.864Z" }, -] - -[[package]] -name = "werkzeug" -version = "3.1.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markupsafe" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9f/69/83029f1f6300c5fb2471d621ab06f6ec6b3324685a2ce0f9777fd4a8b71e/werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746", size = 806925, upload-time = "2024-11-08T15:52:18.093Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/52/24/ab44c871b0f07f491e5d2ad12c9bd7358e527510618cb1b803a88e986db1/werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", size = 224498, upload-time = "2024-11-08T15:52:16.132Z" }, -] - -[[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 = "wtforms" -version = "3.2.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markupsafe" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/01/e4/633d080897e769ed5712dcfad626e55dbd6cf45db0ff4d9884315c6a82da/wtforms-3.2.1.tar.gz", hash = "sha256:df3e6b70f3192e92623128123ec8dca3067df9cfadd43d59681e210cfb8d4682", size = 137801, upload-time = "2024-10-21T11:34:00.108Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/08/c9/2088fb5645cd289c99ebe0d4cdcc723922a1d8e1beaefb0f6f76dff9b21c/wtforms-3.2.1-py3-none-any.whl", hash = "sha256:583bad77ba1dd7286463f21e11aa3043ca4869d03575921d1a1698d0715e0fd4", size = 152454, upload-time = "2024-10-21T11:33:58.44Z" }, -] - -[[package]] -name = "yarl" -version = "1.20.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "idna" }, - { name = "multidict" }, - { name = "propcache" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3c/fb/efaa23fa4e45537b827620f04cf8f3cd658b76642205162e072703a5b963/yarl-1.20.1.tar.gz", hash = "sha256:d017a4997ee50c91fd5466cef416231bb82177b93b029906cefc542ce14c35ac", size = 186428, upload-time = "2025-06-10T00:46:09.923Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/9a/cb7fad7d73c69f296eda6815e4a2c7ed53fc70c2f136479a91c8e5fbdb6d/yarl-1.20.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdcc4cd244e58593a4379fe60fdee5ac0331f8eb70320a24d591a3be197b94a9", size = 133667, upload-time = "2025-06-10T00:43:44.369Z" }, - { url = "https://files.pythonhosted.org/packages/67/38/688577a1cb1e656e3971fb66a3492501c5a5df56d99722e57c98249e5b8a/yarl-1.20.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b29a2c385a5f5b9c7d9347e5812b6f7ab267193c62d282a540b4fc528c8a9d2a", size = 91025, upload-time = "2025-06-10T00:43:46.295Z" }, - { url = "https://files.pythonhosted.org/packages/50/ec/72991ae51febeb11a42813fc259f0d4c8e0507f2b74b5514618d8b640365/yarl-1.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1112ae8154186dfe2de4732197f59c05a83dc814849a5ced892b708033f40dc2", size = 89709, upload-time = "2025-06-10T00:43:48.22Z" }, - { url = "https://files.pythonhosted.org/packages/99/da/4d798025490e89426e9f976702e5f9482005c548c579bdae792a4c37769e/yarl-1.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90bbd29c4fe234233f7fa2b9b121fb63c321830e5d05b45153a2ca68f7d310ee", size = 352287, upload-time = "2025-06-10T00:43:49.924Z" }, - { url = "https://files.pythonhosted.org/packages/1a/26/54a15c6a567aac1c61b18aa0f4b8aa2e285a52d547d1be8bf48abe2b3991/yarl-1.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:680e19c7ce3710ac4cd964e90dad99bf9b5029372ba0c7cbfcd55e54d90ea819", size = 345429, upload-time = "2025-06-10T00:43:51.7Z" }, - { url = "https://files.pythonhosted.org/packages/d6/95/9dcf2386cb875b234353b93ec43e40219e14900e046bf6ac118f94b1e353/yarl-1.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a979218c1fdb4246a05efc2cc23859d47c89af463a90b99b7c56094daf25a16", size = 365429, upload-time = "2025-06-10T00:43:53.494Z" }, - { url = "https://files.pythonhosted.org/packages/91/b2/33a8750f6a4bc224242a635f5f2cff6d6ad5ba651f6edcccf721992c21a0/yarl-1.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255b468adf57b4a7b65d8aad5b5138dce6a0752c139965711bdcb81bc370e1b6", size = 363862, upload-time = "2025-06-10T00:43:55.766Z" }, - { url = "https://files.pythonhosted.org/packages/98/28/3ab7acc5b51f4434b181b0cee8f1f4b77a65919700a355fb3617f9488874/yarl-1.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a97d67108e79cfe22e2b430d80d7571ae57d19f17cda8bb967057ca8a7bf5bfd", size = 355616, upload-time = "2025-06-10T00:43:58.056Z" }, - { url = "https://files.pythonhosted.org/packages/36/a3/f666894aa947a371724ec7cd2e5daa78ee8a777b21509b4252dd7bd15e29/yarl-1.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8570d998db4ddbfb9a590b185a0a33dbf8aafb831d07a5257b4ec9948df9cb0a", size = 339954, upload-time = "2025-06-10T00:43:59.773Z" }, - { url = "https://files.pythonhosted.org/packages/f1/81/5f466427e09773c04219d3450d7a1256138a010b6c9f0af2d48565e9ad13/yarl-1.20.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:97c75596019baae7c71ccf1d8cc4738bc08134060d0adfcbe5642f778d1dca38", size = 365575, upload-time = "2025-06-10T00:44:02.051Z" }, - { url = "https://files.pythonhosted.org/packages/2e/e3/e4b0ad8403e97e6c9972dd587388940a032f030ebec196ab81a3b8e94d31/yarl-1.20.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1c48912653e63aef91ff988c5432832692ac5a1d8f0fb8a33091520b5bbe19ef", size = 365061, upload-time = "2025-06-10T00:44:04.196Z" }, - { url = "https://files.pythonhosted.org/packages/ac/99/b8a142e79eb86c926f9f06452eb13ecb1bb5713bd01dc0038faf5452e544/yarl-1.20.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4c3ae28f3ae1563c50f3d37f064ddb1511ecc1d5584e88c6b7c63cf7702a6d5f", size = 364142, upload-time = "2025-06-10T00:44:06.527Z" }, - { url = "https://files.pythonhosted.org/packages/34/f2/08ed34a4a506d82a1a3e5bab99ccd930a040f9b6449e9fd050320e45845c/yarl-1.20.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c5e9642f27036283550f5f57dc6156c51084b458570b9d0d96100c8bebb186a8", size = 381894, upload-time = "2025-06-10T00:44:08.379Z" }, - { url = "https://files.pythonhosted.org/packages/92/f8/9a3fbf0968eac704f681726eff595dce9b49c8a25cd92bf83df209668285/yarl-1.20.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2c26b0c49220d5799f7b22c6838409ee9bc58ee5c95361a4d7831f03cc225b5a", size = 383378, upload-time = "2025-06-10T00:44:10.51Z" }, - { url = "https://files.pythonhosted.org/packages/af/85/9363f77bdfa1e4d690957cd39d192c4cacd1c58965df0470a4905253b54f/yarl-1.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:564ab3d517e3d01c408c67f2e5247aad4019dcf1969982aba3974b4093279004", size = 374069, upload-time = "2025-06-10T00:44:12.834Z" }, - { url = "https://files.pythonhosted.org/packages/35/99/9918c8739ba271dcd935400cff8b32e3cd319eaf02fcd023d5dcd487a7c8/yarl-1.20.1-cp312-cp312-win32.whl", hash = "sha256:daea0d313868da1cf2fac6b2d3a25c6e3a9e879483244be38c8e6a41f1d876a5", size = 81249, upload-time = "2025-06-10T00:44:14.731Z" }, - { url = "https://files.pythonhosted.org/packages/eb/83/5d9092950565481b413b31a23e75dd3418ff0a277d6e0abf3729d4d1ce25/yarl-1.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:48ea7d7f9be0487339828a4de0360d7ce0efc06524a48e1810f945c45b813698", size = 86710, upload-time = "2025-06-10T00:44:16.716Z" }, - { url = "https://files.pythonhosted.org/packages/b4/2d/2345fce04cfd4bee161bf1e7d9cdc702e3e16109021035dbb24db654a622/yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77", size = 46542, upload-time = "2025-06-10T00:46:07.521Z" }, -] - -[[package]] -name = "zipp" -version = "3.23.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, -] diff --git a/tools/classifier/.dockerignore b/tools/classifier/.dockerignore deleted file mode 100644 index c26352afcc..0000000000 --- a/tools/classifier/.dockerignore +++ /dev/null @@ -1,3 +0,0 @@ -venv/ -.venv/ -.env diff --git a/tools/classifier/Dockerfile b/tools/classifier/Dockerfile deleted file mode 100644 index ae45416c67..0000000000 --- a/tools/classifier/Dockerfile +++ /dev/null @@ -1,41 +0,0 @@ -FROM python:3.12-slim-trixie - -LABEL maintainer="Zipstack Inc." - -ENV \ - # Extended PYTHONPATH to include all unstract module source directories - APP_HOME=/app \ - BUILD_PACKAGES_PATH=unstract \ - # Increase timeout for large packages (flipt-client is ~45MB) - PIP_DEFAULT_TIMEOUT=120 - -# Install dependencies for unstructured library's partition -RUN apt-get update && apt-get --no-install-recommends -y install dumb-init libmagic-dev poppler-utils\ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* - -RUN pip install --no-cache-dir -U pip -# Set the working directory in the container -WORKDIR ${APP_HOME} -COPY tools/classifier/requirements.txt /app/ - -# Copy specific subdirectories while preserving structure -COPY ${BUILD_PACKAGES_PATH}/sdk1 /unstract/sdk1 -COPY ${BUILD_PACKAGES_PATH}/core /unstract/core -COPY ${BUILD_PACKAGES_PATH}/flags /unstract/flags - -RUN pip install --no-cache-dir -r requirements.txt && \ - pip install --no-cache-dir \ - opentelemetry-distro \ - opentelemetry-exporter-otlp \ - platformdirs>=3.0.0 \ - && pip install opentelemetry-instrumentation-openai \ - && opentelemetry-bootstrap -a install \ - && pip uninstall -y opentelemetry-instrumentation-openai-v2 - -# Copy the contents of your project directory into the container at /app -COPY tools/classifier/src /app/src/ -WORKDIR /app/src - - -ENTRYPOINT ["opentelemetry-instrument","python", "main.py"] diff --git a/tools/classifier/README.md b/tools/classifier/README.md deleted file mode 100644 index 34dd5a88c4..0000000000 --- a/tools/classifier/README.md +++ /dev/null @@ -1,120 +0,0 @@ -# Document Classifier Tool - -The document classifier tool classifies documents and copies them to a folder based on the classification. - -## Required environment variables - -| Variable | Description | -| -------------------------- |-----------------------------------------------------------------------| -| `PLATFORM_SERVICE_HOST` | The host in which the platform service is running | -| `PLATFORM_SERVICE_PORT` | The port in which the service is listening | -| `PLATFORM_SERVICE_API_KEY` | The API key for the platform | -| `EXECUTION_DATA_DIR` | The directory in the filesystem which has contents for tool execution | - -## Testing the tool locally - -### Setting up a dev environment - -Setup a virtual environment and activate it - -```commandline -python -m venv .venv -source .venv/bin/activate -``` - -Install the dependencies for the tool - -```commandline -pip install -r requirements.txt -``` - -To use the local development version of the [unstract-sdk](https://pypi.org/project/unstract-sdk/) install it from the local repository. -Replace the path with the path to your local repository - -```commandline -pip install -e ~/path_to_repo/sdks/. -``` - -### Tool execution preparation - -Load the environment variables for the tool. -Make a copy of the `sample.env` file and name it `.env`. Fill in the required values. -They get loaded with [python-dotenv](https://pypi.org/project/python-dotenv/) through the SDK. - -Update the tool's `data_dir` marked by the `EXECUTION_DATA_DIR` env. This has to be done before each tool execution since the tool updates the `INFILE` and `METADATA.json`. - -### Run SPEC command - -Represents the JSON schema for the runtime configurable `settings` of a tool - -```commandline -python main.py --command SPEC -``` - -### Run PROPERTIES command - -Describes some metadata for the tool such as its `version`, `description`, `inputs` and `outputs` - -```commandline -python main.py --command PROPERTIES -``` - -### Run ICON command - -Returns the SVG icon for the tool, used by Unstract's frontend - -```commandline -python main.py --command ICON -``` - -### Run VARIABLES command - -Represents the runtime variables or envs that will be used by the tool - -```commandline -python main.py --command VARIABLES -``` - -### Run RUN command - -The schema of the JSON required for settings can be found by running the [SPEC](#run-spec-command) command. Alternatively if you have access to the code base, it is located in the `config` folder as `spec.json`. - -```commandline -python main.py \ - --command RUN \ - --settings '{ - "llmAdapterId": "e9884c72-3920-43e7-9904-bb59f308f50d", - "classificationBins": ["business", "sports", "politics", "entertainment", "tech"], - "useCache": true - }' \ - --log-level DEBUG - -``` - -## Testing the tool from its docker image - -Build the tool docker image from the folder containing the `Dockerfile` with - -```commandline -docker build -t unstract/tool-classifier:0.0.1 . -``` - -Make sure the directory pointed by `EXECUTION_DATA_DIR` has the required information for the tool to run and -necessary services like the `platform-service` is up. -To test the tool from its docker image, run the following command - -```commandline -docker run -it \ - --network unstract-network \ - --env-file .env \ - -v "$(pwd)"/data_dir:/app/data_dir \ - unstract/tool-classifier:0.0.1 \ - --command RUN \ - --settings '{ - "llmAdapterId": "e9884c72-3920-43e7-9904-bb59f308f50d", - "classificationBins": ["business", "sports", "politics", "entertainment", "tech"], - "useCache": true - }' \ - --log-level DEBUG - -``` diff --git a/tools/classifier/__init__.py b/tools/classifier/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tools/classifier/requirements.txt b/tools/classifier/requirements.txt deleted file mode 100644 index 4347f319b3..0000000000 --- a/tools/classifier/requirements.txt +++ /dev/null @@ -1,9 +0,0 @@ -# Add your dependencies here - -# Required for all unstract tools -# aws alone is needed here -# because tools use transient temporary storage. - --e file:/unstract/core --e file:/unstract/sdk1[aws] --e file:/unstract/flags diff --git a/tools/classifier/sample.env b/tools/classifier/sample.env deleted file mode 100644 index b1d6474d75..0000000000 --- a/tools/classifier/sample.env +++ /dev/null @@ -1,14 +0,0 @@ -PLATFORM_SERVICE_HOST=http://unstract-platform-service -PLATFORM_SERVICE_PORT=3001 -PLATFORM_SERVICE_API_KEY= -EXECUTION_DATA_DIR=../data_dir - -X2TEXT_HOST=http://unstract-x2text-service -X2TEXT_PORT=3004 - -# File System Configuration for Workflow Execution -# Directory path for execution data storage -# (e.g., bucket/execution/org_id/workflow_id/execution_id) -EXECUTION_DATA_DIR= -# Storage provider for Workflow Execution (e.g., minio, S3) -WORKFLOW_EXECUTION_FILE_STORAGE_CREDENTIALS='{"provider":"minio","credentials"={"endpoint_url":"http://localhost:9000","key":"XXX","secret":"XXX"}}' diff --git a/tools/classifier/src/config/icon.svg b/tools/classifier/src/config/icon.svg deleted file mode 100644 index e3a507bafa..0000000000 --- a/tools/classifier/src/config/icon.svg +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - - - - diff --git a/tools/classifier/src/config/properties.json b/tools/classifier/src/config/properties.json deleted file mode 100644 index 664e770762..0000000000 --- a/tools/classifier/src/config/properties.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "schemaVersion": "0.0.1", - "displayName": "File Classifier", - "functionName": "classify", - "toolVersion": "0.0.80", - "description": "Classifies a file into a bin based on its contents", - "input": { - "description": "File to be classified" - }, - "output": { - "description": "Places the file into a folder (bin) that its classified into." - }, - "result": { - "type": "JSON", - "description": "JSON response containing the bin to which the file was classified into", - "schema": {} - }, - "adapter": { - "languageModels": [ - { - "isEnabled": true, - "title": "Classifier LLM", - "isRequired": true, - "description": "LLM to use for classification" - } - ], - "embeddingServices": [ - { - "isEnabled": false - } - ], - "vectorStores": [ - { - "isEnabled": false - } - ], - "textExtractors": [ - { - "isEnabled": true, - "adapterId": "textExtractorId", - "title": "Text Extraction Adapter", - "isRequired": true, - "description": "Choose extractor adapter to extract text" - } - ] - }, - "ioCompatibility": { - "api": { - "sourceSupport": true, - "destinationSupport": true, - "additionalArgs": { - "sync": true - } - }, - "file": { - "sourceSupport": true, - "destinationSupport": true, - "additionalArgs": {} - }, - "db": { - "destinationSupport": true, - "additionalArgs": {} - } - }, - "restrictions": { - "maxFileSize": "200MB", - "allowedFileTypes": [ - "*" - ] - } -} diff --git a/tools/classifier/src/config/runtime_variables.json b/tools/classifier/src/config/runtime_variables.json deleted file mode 100644 index 3f72970c3e..0000000000 --- a/tools/classifier/src/config/runtime_variables.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "title": "Runtime Variables", - "description": "Runtime Variables for classifier", - "type": "object", - "required": [], - "properties": {} -} diff --git a/tools/classifier/src/config/spec.json b/tools/classifier/src/config/spec.json deleted file mode 100644 index 5b5b59d14a..0000000000 --- a/tools/classifier/src/config/spec.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "title": "Document classifier tool settings", - "description": "Classify documents based on their content", - "type": "object", - "required": [ - "classificationBins" - ], - "properties": { - "classificationBins": { - "type": "array", - "title": "Classification bins", - "description": "Specify at least two unique classification bins. `unknown` and `__unstract_failed` are reserved bins.\n - `unknown` indicates the LLM can't determine the classification\n - `__unstract_failed` indicates a tool run failure for the given file.", - "items": { - "type": "string" - }, - "minItems": 2, - "uniqueItems": true - }, - "useCache": { - "type": "boolean", - "title": "Cache and use cached results", - "default": true, - "description": "Use cached results" - } - } -} diff --git a/tools/classifier/src/helper.py b/tools/classifier/src/helper.py deleted file mode 100644 index fe2c6eeab5..0000000000 --- a/tools/classifier/src/helper.py +++ /dev/null @@ -1,261 +0,0 @@ -import re -from pathlib import Path -from typing import Any - -from unstract.sdk1.adapters.x2text.dto import TextExtractionResult -from unstract.sdk1.constants import LogLevel, MetadataKey, UsageKwargs -from unstract.sdk1.llm import LLM -from unstract.sdk1.tool.base import BaseTool -from unstract.sdk1.x2txt import X2Text - - -class ReservedBins: - UNKNOWN = "unknown" - FAILED = "__unstract_failed" - - -class ClassifierHelper: - """Helper functions for Classifier.""" - - def __init__(self, tool: BaseTool, output_dir: str) -> None: - """Creates a helper class for the Classifier tool. - - Args: - tool (BaseTool): Base tool instance - output_dir (str): Output directory in EXECUTION_DATA_DIR - """ - self.tool = tool - self.output_dir = output_dir - - def stream_error_and_exit( - self, message: str, bin_to_copy_to: str = ReservedBins.FAILED - ) -> None: - """Streams error logs and performs required cleanup. - - Helper which copies files to a reserved bin in case of an error. - - Args: - message (str): Error message to log - bin_to_copy_to (str): The folder to copy the failed source file to. - Defaults to `__unstract_failed`. - input_file (Optional[str], optional): Input file to copy. Defaults to None. - output_dir (Optional[str], optional): Output directory to copy to. - Defaults to None. - """ - source_name = self.tool.get_exec_metadata.get(MetadataKey.SOURCE_NAME) - self.copy_source_to_output_bin( - classification=bin_to_copy_to, - source_file=self.tool.get_source_file(), - source_name=source_name, - ) - - self.tool.stream_error_and_exit(message=message) - - def copy_source_to_output_bin( - self, - classification: str, - source_file: str, - source_name: str, - ) -> None: - """Method to save result in output folder and the data directory. - - Args: - classification (str): classification result - source_file (str): Path to source file used in the workflow - source_name (str): Name of the actual input from the source - """ - try: - output_folder_bin = Path(self.output_dir) / classification - output_file = output_folder_bin / source_name - self._copy_file( - source_fs=self.tool.workflow_filestorage, - destination_fs=self.tool.workflow_filestorage, - source_path=source_file, - destination_path=str(output_file), - ) - except Exception as e: - self.tool.stream_error_and_exit(f"Error creating output file: {e}") - - def _copy_file( - self, - source_fs: Any, - destination_fs: Any, - source_path: str, - destination_path: str, - ) -> None: - """Helps copy a file from source to destination. - - Args: - src (str): Path to the source file - dest (str): Path to the destination file - """ - try: - # TODO: Move it to the top once SDK released with fileStorage Feature - # Change the source fs and destination fs type to to FileStorage - from unstract.sdk1.utils import FileStorageUtils - - FileStorageUtils.copy_file_to_destination( - source_storage=source_fs, - destination_storage=destination_fs, - source_path=source_path, - destination_paths=[destination_path], - ) - except Exception as e: - self.stream_error_and_exit(f"Error copying file: {e}") - - def extract_text( - self, file: str, text_extraction_adapter_id: str | None - ) -> str | None: - """Extract text from file. - - Args: - file (str): The path to the input file - - Returns: - str: page content - """ - if not text_extraction_adapter_id: - return self._extract_from_file(file) - - return self._extract_from_adapter(file, text_extraction_adapter_id) - - def _extract_from_adapter(self, file: str, adapter_id: str) -> str | None: - """Extract text from adapter. - - Args: - file: The path to the input file - adapter_id: The id of the adapter - Returns: - str: page content - """ - self.tool.stream_log( - f"Creating text extraction adapter using adapter_id: {adapter_id}" - ) - usage_kwargs: dict[Any, Any] = dict() - usage_kwargs[UsageKwargs.FILE_NAME] = self.tool.source_file_name - usage_kwargs[UsageKwargs.RUN_ID] = self.tool.file_execution_id - - x2text = X2Text( - tool=self.tool, adapter_instance_id=adapter_id, usage_kwargs=usage_kwargs - ) - - self.tool.stream_log("Text extraction adapter has been created successfully.") - - try: - extraction_result: TextExtractionResult = x2text.process( - input_file_path=file, - fs=self.tool.workflow_filestorage, - tags=self.tool.tags, - ) - extracted_text: str = extraction_result.extracted_text - return extracted_text - except Exception as e: - self.tool.stream_log(f"Adapter error: {e}") - return None - - def _extract_from_file(self, file: str) -> str | None: - """Extract text from file. - - Args: - file: The path to the input file - Returns: - str: page content - """ - self.tool.stream_log("Extracting text from file") - try: - text = self.tool.workflow_filestorage.read(path=file, mode="rb").decode( - "utf-8" - ) - except Exception as e: - self.tool.stream_log(f"File error: {e}") - return None - - self.tool.stream_log("Text extracted from file") - return text - - def find_classification( - self, - use_cache: bool, - settings_string: str, - bins: list[str], - prompt: str, - llm: LLM, - ) -> str | None: - """Find classification for text. - - Args: - use_cache (bool): Whether to use cache (deprecated, not used) - settings_string (str): hash of settings (deprecated, not used) - prompt (str): Prompt - bins (list[str]): Classification Bins - llm (LLM): LLM - - Returns: - Optional[str]: Classification from the LLM. - """ - self.tool.stream_log("Calling LLM for classification.") - llm_response = self.call_llm(prompt=prompt, llm=llm) - classification = self.clean_llm_response(llm_response=llm_response, bins=bins) - return classification - - def call_llm(self, prompt: str, llm: LLM) -> str: - """Call LLM. - - Args: - prompt (str): Prompt - llm (LLM): LLM - - Returns: - str: Classification - """ - try: - completion = llm.complete(prompt)[LLM.RESPONSE] - classification: str = completion.text.strip() - self.tool.stream_log(f"LLM response: {completion}", level=LogLevel.DEBUG) - return classification - except Exception as e: - self.stream_error_and_exit(f"Error calling LLM: {e}") - raise e - - def clean_llm_response(self, llm_response: str, bins: list[str]) -> str: - """Cleans the response from the LLM. - - Performs a substring search to find the returned classification. - Treats it as `unknown` if the classification is not clear - from the output. - - Args: - llm_response (str): Response from LLM to clean - bins (list(str)): List of bins to classify the file into. - - Returns: - str: Cleaned classification that matches one of the bins. - """ - classification = ReservedBins.UNKNOWN - cleaned_response = llm_response.strip().lower() - bins = [bin.lower() for bin in bins] - - # Truncate llm_response to the first 100 words - words = cleaned_response.split() - truncated_response = " ".join(words[:100]) - - # Count occurrences of each bin in the truncated text - bin_counts = { - bin: len(re.findall(r"\b" + re.escape(bin) + r"\b", truncated_response)) - for bin in bins - } - - # Filter bins that have a count greater than 0 - matching_bins = [bin for bin, count in bin_counts.items() if count > 0] - - # Determine classification based on the number of matching bins - if len(matching_bins) == 1: - classification = matching_bins[0] - else: - self.stream_error_and_exit( - f"Unable to deduce classified bin from possible values of " - f"'{matching_bins}', moving file to '{ReservedBins.UNKNOWN}' " - "folder instead.", - bin_to_copy_to=ReservedBins.UNKNOWN, - ) - return classification diff --git a/tools/classifier/src/main.py b/tools/classifier/src/main.py deleted file mode 100644 index 4903d727b2..0000000000 --- a/tools/classifier/src/main.py +++ /dev/null @@ -1,162 +0,0 @@ -import sys -from typing import Any - -from helper import ( - ClassifierHelper, # type: ignore - ReservedBins, -) - -from unstract.sdk1.constants import ( - LogLevel, - LogState, - MetadataKey, - ToolSettingsKey, - UsageKwargs, -) -from unstract.sdk1.exceptions import SdkError -from unstract.sdk1.llm import LLM -from unstract.sdk1.tool.base import BaseTool -from unstract.sdk1.tool.entrypoint import ToolEntrypoint - - -class UnstractClassifier(BaseTool): - def __init__(self, log_level: str = LogLevel.INFO) -> None: - super().__init__(log_level) - - def validate(self, input_file: str, settings: dict[str, Any]) -> None: - bins: list[str] | None = settings.get("classificationBins") - llm_adapter_instance_id = settings.get(ToolSettingsKey.LLM_ADAPTER_ID) - text_extraction_adapter_id = settings.get("textExtractorId") - if not bins: - self.stream_error_and_exit("Classification bins are required.") - elif len(bins) < 2: - self.stream_error_and_exit("At least two classification bins are required.") - elif ReservedBins.UNKNOWN in bins: - self.stream_error_and_exit( - f"Classification bin '{ReservedBins.UNKNOWN}' is reserved to mark " - "files which cannot be classified." - ) - - if not llm_adapter_instance_id: - self.stream_error_and_exit("Choose an LLM to perform the classification.") - if not text_extraction_adapter_id: - self.stream_error_and_exit( - "Choose a text extractor to extract the documents." - ) - - def run( - self, - settings: dict[str, Any], - input_file: str, - output_dir: str, - ) -> None: - bins = settings["classificationBins"] - use_cache = settings["useCache"] - text_extraction_adapter_id = settings["textExtractorId"] - llm_adapter_instance_id = settings[ToolSettingsKey.LLM_ADAPTER_ID] - self.helper = ClassifierHelper(tool=self, output_dir=output_dir) - - # Update GUI - input_log = f"### Classification bins:\n```text\n{bins}\n```\n\n" - output_log = "" - self.stream_update(input_log, state=LogState.INPUT_UPDATE) - self.stream_update(output_log, state=LogState.OUTPUT_UPDATE) - - self.stream_log(f"Reading file... {input_file}") - text: str | None = self.helper.extract_text( - file=input_file, - text_extraction_adapter_id=text_extraction_adapter_id, - ) - if not text: - self.helper.stream_error_and_exit("Unable to extract text") - return - self.stream_log(f"Text length: {len(text)}") - - # Update GUI - input_text_for_log = text - if len(input_text_for_log) > 500: - input_text_for_log = input_text_for_log[:500] + "...(truncated)" - input_log = ( - f"### Classification bins:\n```text\n{bins}\n```\n\n" - f"### Input text:\n\n```text\n{input_text_for_log}\n```\n\n" - ) - output_log = "" - self.stream_update(input_log, state=LogState.INPUT_UPDATE) - self.stream_update(output_log, state=LogState.OUTPUT_UPDATE) - - if ReservedBins.UNKNOWN not in bins: - bins.append(ReservedBins.UNKNOWN) - bins_with_quotes = [f"'{b}'" for b in bins] - - usage_kwargs: dict[Any, Any] = dict() - usage_kwargs[UsageKwargs.WORKFLOW_ID] = self.workflow_id - usage_kwargs[UsageKwargs.EXECUTION_ID] = self.execution_id - usage_kwargs[UsageKwargs.FILE_NAME] = self.source_file_name - usage_kwargs[UsageKwargs.RUN_ID] = self.file_execution_id - - try: - llm = LLM( - adapter_instance_id=llm_adapter_instance_id, - tool=self, - kwargs=usage_kwargs, - ) - - max_tokens = llm.get_max_tokens( - adapter_instance_id=llm_adapter_instance_id, - tool=self, - reserved_for_output=50 + 1000, - ) - except SdkError: - self.helper.stream_error_and_exit("Unable to get llm instance") - return - - max_bytes = int(max_tokens * 1.3) - self.stream_log(f"LLM Max tokens: {max_tokens} ==> Max bytes: {max_bytes}") - limited_text = "" - for byte in text.encode(): - if len(limited_text.encode()) < max_bytes: - limited_text += chr(byte) - else: - break - text = limited_text - self.stream_log(f"Length of text: {len(text.encode())} {len(text)}") - - prompt = ( - f"Classify the following text into one of the following categories: {' '.join(bins_with_quotes)}.\n\n" # noqa: E501 - "Your categorization should be strictly exactly one of the items in the " # noqa: E501 - "categories given, do not provide any explanation. Find a semantic match of category if possible. " # noqa: E501 - "If it does not categorize well into any of the listed categories, categorize it as 'unknown'." # noqa: E501 - f"Do not enclose the result within single quotes.\n\nText:\n\n{text}\n\n\nCategory:" # noqa: E501 - ) - - settings_string = "".join(str(value) for value in settings.values()) - classification = self.helper.find_classification( - use_cache=use_cache, - settings_string=settings_string, - prompt=prompt, - bins=bins, - llm=llm, - ) - - source_name = self.get_exec_metadata.get(MetadataKey.SOURCE_NAME) - self.helper.copy_source_to_output_bin( - classification=classification, - source_file=self.get_source_file(), - source_name=source_name, - ) - output_log = "### Classifier output\n\n" - output_log += f"```bash\nCLASSIFICATION={classification}\n```\n\n" - self.stream_single_step_message(output_log) - self.stream_update(output_log, state=LogState.OUTPUT_UPDATE) - - classification_dict = { - "input_file": source_name, - "result": classification, - } - self.write_tool_result(data=classification_dict) - - -if __name__ == "__main__": - args = sys.argv[1:] - tool = UnstractClassifier.from_tool_args(args=args) - ToolEntrypoint.launch(tool=tool, args=args) diff --git a/tools/structure/.dockerignore b/tools/structure/.dockerignore deleted file mode 100644 index c26352afcc..0000000000 --- a/tools/structure/.dockerignore +++ /dev/null @@ -1,3 +0,0 @@ -venv/ -.venv/ -.env diff --git a/tools/structure/.gitignore b/tools/structure/.gitignore deleted file mode 100644 index be0e458d46..0000000000 --- a/tools/structure/.gitignore +++ /dev/null @@ -1 +0,0 @@ -helper.py diff --git a/tools/structure/Dockerfile b/tools/structure/Dockerfile deleted file mode 100644 index 6af880bad3..0000000000 --- a/tools/structure/Dockerfile +++ /dev/null @@ -1,68 +0,0 @@ -FROM python:3.12-slim-trixie - -LABEL maintainer="Zipstack Inc." \ - description="Structure Tool Container" \ - version="1.0" - -ENV \ - # Keeps Python from generating .pyc files in the container - PYTHONDONTWRITEBYTECODE=1 \ - # Set to immediately flush stdout and stderr streams without first buffering - PYTHONUNBUFFERED=1 \ - APP_HOME=/app \ - BUILD_PACKAGES_PATH=unstract \ - # OpenTelemetry configuration - OTEL_SERVICE_NAME="structure-tool" \ - OTEL_TRACES_EXPORTER=none \ - OTEL_METRICS_EXPORTER=none \ - OTEL_LOGS_EXPORTER=none \ - # Enable context propagation - OTEL_PROPAGATORS="tracecontext" \ - PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python \ - # Increase timeout for large packages (flipt-client is ~45MB) - UV_HTTP_TIMEOUT=120 - -# Install system dependencies in one layer -RUN apt-get update && \ - apt-get install -y --no-install-recommends \ - ffmpeg libsm6 libxext6 libmagic-dev poppler-utils \ - libreoffice freetds-dev freetds-bin dumb-init && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/* - -# Install uv package manager -COPY --from=ghcr.io/astral-sh/uv:0.6.14 /uv /uvx /bin/ - -# Set the working directory in the container -WORKDIR ${APP_HOME} - -# Copy only requirements file first for better caching -COPY tools/structure/requirements.txt ${APP_HOME}/ - -# Copy specific subdirectories while preserving structure -COPY ${BUILD_PACKAGES_PATH}/sdk1 /unstract/sdk1 -COPY ${BUILD_PACKAGES_PATH}/core /unstract/core -COPY ${BUILD_PACKAGES_PATH}/flags /unstract/flags - -# Install OpenTelemetry packages first (less likely to change) -RUN uv pip install --system \ - opentelemetry-distro \ - opentelemetry-exporter-otlp \ - platformdirs>=3.0.0 - -# Set shell options for better error handling -SHELL ["/bin/bash", "-o", "pipefail", "-c"] - -# Install project dependencies separately from OpenTelemetry -# This allows for better caching when only project dependencies change -RUN uv pip install --system -r requirements.txt && \ - opentelemetry-bootstrap -a requirements | uv pip install --system --requirement - && \ - pip uninstall -y opentelemetry-instrumentation-openai-v2 && \ - pip install opentelemetry-instrumentation-openai - -# Copy the source code after installing all dependencies -# This ensures that changes to the source code don't invalidate the dependency layers -COPY tools/structure/src ${APP_HOME}/src/ -WORKDIR ${APP_HOME}/src - -ENTRYPOINT ["opentelemetry-instrument", "python", "main.py"] diff --git a/tools/structure/README.md b/tools/structure/README.md deleted file mode 100644 index 458f55895c..0000000000 --- a/tools/structure/README.md +++ /dev/null @@ -1,121 +0,0 @@ -# Structure Tool - -This is a helper tool that needs to be used along with `Prompt Studio`. It helps process input files based on the prompts that have been designed and exported as a tool from `Prompt Studio`. - -## Required environment variables - -| Variable | Description | -| -------------------------- |-----------------------------------------------------------------------| -| `PLATFORM_SERVICE_HOST` | The host in which the platform service is running | -| `PLATFORM_SERVICE_PORT` | The port in which the service is listening | -| `PLATFORM_SERVICE_API_KEY` | The API key for the platform | -| `EXECUTION_DATA_DIR` | The directory in the filesystem which has contents for tool execution | -| `PROMPT_HOST` | The host in which the prompt service is running | -| `PROMPT_PORT` | The port in which the prompt service is listening | -| `PROMPT_PORT` | The port in which the prompt service is listening | -| `X2TEXT_HOST` | The host where the x2text service is running | -| `X2TEXT_PORT` | The port where the x2text service is listening | - -## Testing the tool locally - -### Setting up a dev environment - -Setup a virtual environment and activate it - -```commandline -python -m venv .venv -source .venv/bin/activate -``` - -Install the dependencies for the tool - -```commandline -pip install -r requirements.txt -``` - -To use the local development version of the [unstract-sdk](https://pypi.org/project/unstract-sdk/) install it from the local repository. -Replace the path with the path to your local repository - -```commandline -pip install -e ~/path_to_repo/sdks/. -``` - -### Tool execution preparation - -Load the environment variables for the tool. -Make a copy of the `sample.env` file and name it `.env`. Fill in the required values. -They get loaded with [python-dotenv](https://pypi.org/project/python-dotenv/) through the SDK. - -Update the tool's `data_dir` marked by the `EXECUTION_DATA_DIR` env. This has to be done before each tool execution since the tool updates the `INFILE` and `METADATA.json`. - -### Run SPEC command - -Represents the JSON schema for the runtime configurable `settings` of a tool - -```commandline -python main.py --command SPEC -``` - -### Run PROPERTIES command - -Describes some metadata for the tool such as its `version`, `description`, `inputs` and `outputs` - -```commandline -python main.py --command PROPERTIES -``` - -### Run ICON command - -Returns the SVG icon for the tool, used by Unstract's frontend - -```commandline -python main.py --command ICON -``` - -### Run VARIABLES command - -Represents the runtime variables or envs that will be used by the tool - -```commandline -python main.py --command VARIABLES -``` - -### Run RUN command - -The schema of the JSON required for settings can be found by running the [SPEC](#run-spec-command) command. Alternatively if you have access to the code base, it is located in the `config` folder as `spec.json`. - -```commandline -python main.py \ - --command RUN \ - --settings '{ - "prompt_registry_id": "" - }' \ - --log-level DEBUG - -``` - -## Testing the tool from its docker image - -Build the tool docker image from the folder containing the `Dockerfile` with - -```commandline -docker build -t unstract/tool-structure:0.0.1 . -``` - -Make sure the directory pointed by `EXECUTION_DATA_DIR` has the required information for the tool to run and -necessary services like the `platform-service` is up. -To test the tool from its docker image, run the following command - -```commandline -docker run -it \ - --network unstract-network \ - --env-file .env \ - -v "$(pwd)"/data_dir:/app/data_dir \ - unstract/tool-structure:0.0.1 \ - --command RUN \ - --settings '{ - "prompt_registry_id": "" - }' \ - --log-level DEBUG - -``` diff --git a/tools/structure/__init__.py b/tools/structure/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tools/structure/requirements.txt b/tools/structure/requirements.txt deleted file mode 100644 index aba611c0e5..0000000000 --- a/tools/structure/requirements.txt +++ /dev/null @@ -1,9 +0,0 @@ -# Add your dependencies here - -# Required for all unstract tools -# aws alone is needed here -# because tools use transient temporary storage. --e file:/unstract/core --e file:/unstract/sdk1[aws] --e file:/unstract/flags -json-repair>=0.25.0 diff --git a/tools/structure/sample.env b/tools/structure/sample.env deleted file mode 100644 index 0e5f82c242..0000000000 --- a/tools/structure/sample.env +++ /dev/null @@ -1,16 +0,0 @@ -PLATFORM_SERVICE_HOST=http://unstract-platform-service -PLATFORM_SERVICE_PORT=3001 -PLATFORM_SERVICE_API_KEY= -EXECUTION_DATA_DIR=../data_dir -PROMPT_HOST=http://unstract-prompt-service -PROMPT_PORT=3003 - -X2TEXT_HOST=http://unstract-x2text-service -X2TEXT_PORT=3004 - -# File System Configuration for Workflow Execution -# Directory path for execution data storage -# (e.g., bucket/execution/org_id/workflow_id/execution_id) -EXECUTION_DATA_DIR= -# Storage provider for Workflow Execution (e.g., minio, S3) -WORKFLOW_EXECUTION_FILE_STORAGE_CREDENTIALS='{"provider":"minio","credentials"={"endpoint_url":"http://localhost:9000","key":"","secret":""}}' diff --git a/tools/structure/src/config/icon.svg b/tools/structure/src/config/icon.svg deleted file mode 100644 index e3a507bafa..0000000000 --- a/tools/structure/src/config/icon.svg +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - - - - diff --git a/tools/structure/src/config/properties.json b/tools/structure/src/config/properties.json deleted file mode 100644 index c8697e7307..0000000000 --- a/tools/structure/src/config/properties.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "schemaVersion": "0.0.1", - "displayName": "Structure Tool", - "functionName": "structure_tool", - "toolVersion": "0.0.101", - "description": "This is a template tool which can answer set of input prompts designed in the Prompt Studio", - "input": { - "description": "File that needs to be indexed and parsed for answers" - }, - "output": { - "description": "A JSON file containing the parsed answers for the prompts" - }, - "result": { - "type": "JSON", - "description": "A JSON containing the parsed answers for the prompts", - "schema": {} - }, - "ioCompatibility": { - "api": { - "sourceSupport": true, - "destinationSupport": true, - "additionalArgs": { - "sync": true - } - }, - "file": { - "sourceSupport": true, - "destinationSupport": true, - "additionalArgs": {} - }, - "db": { - "destinationSupport": true, - "additionalArgs": {} - } - }, - "restrictions": { - "maxFileSize": "200MB", - "allowedFileTypes": [ - "*" - ] - } -} diff --git a/tools/structure/src/config/runtime_variables.json b/tools/structure/src/config/runtime_variables.json deleted file mode 100644 index 3fc0936224..0000000000 --- a/tools/structure/src/config/runtime_variables.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "title": "Runtime Variables", - "description": "Runtime Variables for Structure Tool ", - "type": "object" -} diff --git a/tools/structure/src/config/spec.json b/tools/structure/src/config/spec.json deleted file mode 100644 index cd73e39ec5..0000000000 --- a/tools/structure/src/config/spec.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "title": "Structure Tool", - "description": "Helper tool used to export tools from Prompt Studio", - "type": "object", - "required": [ - ], - "properties": { - } -} diff --git a/tools/structure/src/constants.py b/tools/structure/src/constants.py deleted file mode 100644 index 8da6a5701a..0000000000 --- a/tools/structure/src/constants.py +++ /dev/null @@ -1,108 +0,0 @@ -class SettingsKeys: - TOOL_INSTANCE_ID = "tool_instance_id" - PROMPT_REGISTRY_ID = "prompt_registry_id" - PROMPT_HOST = "PROMPT_HOST" - PROMPT_PORT = "PROMPT_PORT" - TOOL_METADATA = "tool_metadata" - TOOL_ID = "tool_id" - OUTPUTS = "outputs" - NAME = "name" - ACTIVE = "active" - PROMPT = "prompt" - CHUNK_SIZE = "chunk-size" - VECTOR_DB = "vector-db" - EMBEDDING = "embedding" - X2TEXT_ADAPTER = "x2text_adapter" - CHUNK_OVERLAP = "chunk-overlap" - LLM = "llm" - RETRIEVAL_STRATEGY = "retrieval-strategy" - SIMPLE = "simple" - TYPE = "type" - NUMBER = "number" - EMAIL = "email" - DATE = "date" - BOOLEAN = "boolean" - JSON = "json" - PREAMBLE = "preamble" - SIMILARITY_TOP_K = "similarity-top-k" - PROMPT_TOKENS = "prompt_tokens" - COMPLETION_TOKENS = "completion_tokens" - TOTAL_TOKENS = "total_tokens" - RESPONSE = "response" - POSTAMBLE = "postamble" - GRAMMAR = "grammar" - WORD = "word" - SYNONYMS = "synonyms" - OUTPUTS = "outputs" - SECTION = "section" - DEFAULT = "default" - AUTHOR = "author" - ICON = "icon" - TOOL_ID = "tool_id" - # PDF_TO_TEXT_CONVERTER = "pdf-to-text-converters" - REINDEX = "reindex" - STRUCTURE_OUTPUT = "structure_output" - TOOL_SETTINGS = "tool_settings" - ENABLE_SINGLE_PASS_EXTRACTION = "enable_single_pass_extraction" - CHALLENGE_LLM = "challenge_llm" - ENABLE_CHALLENGE = "enable_challenge" - SINGLE_PASS_EXTRACTION_MODE = "single_pass_extraction_mode" - CHALLENGE_LLM_ADAPTER_ID = "challenge_llm_adapter_id" - SUMMARIZE_AS_SOURCE = "summarize_as_source" - SUMMARIZE_PROMPT = "summarize_prompt" - CONTEXT = "context" - ERROR = "error" - LLM_ADAPTER_INSTANCE_ID = "llm_adapter_instance_id" - RUN_ID = "run_id" - PROMPT_KEYS = "prompt_keys" - DATA = "data" - EXTRACT = "EXTRACT" - SUMMARIZE = "SUMMARIZE" - STATUS = "status" - OK = "OK" - FILE_NAME = "file_name" - FILE_HASH = "file_hash" - ENABLE_HIGHLIGHT = "enable_highlight" - ENABLE_WORD_CONFIDENCE = "enable_word_confidence" - NAME = "name" - INCLUDE_METADATA = "include_metadata" - TABLE_SETTINGS = "table_settings" - INPUT_FILE = "input_file" - METADATA = "metadata" - EPILOGUE = "epilogue" - HIGHLIGHT_DATA = "highlight_data" - CONFIDENCE_DATA = "confidence_data" - FILE_PATH = "file_path" - EXECUTION_SOURCE = "execution_source" - TOOL = "tool" - METRICS = "metrics" - INDEXING = "indexing" - EXECUTION_ID = "execution_id" - IS_DIRECTORY_MODE = "is_directory_mode" - LLM_PROFILE_ID = "llm_profile_id" - CUSTOM_DATA = "custom_data" - OUTPUT = "output" # For API deployment response format compatibility - - -class IndexingConstants: - TOOL_ID = "tool_id" - EMBEDDING_INSTANCE_ID = "embedding_instance_id" - VECTOR_DB_INSTANCE_ID = "vector_db_instance_id" - X2TEXT_INSTANCE_ID = "x2text_instance_id" - FILE_PATH = "file_path" - CHUNK_SIZE = "chunk_size" - CHUNK_OVERLAP = "chunk_overlap" - REINDEX = "reindex" - FILE_HASH = "file_hash" - OUTPUT_FILE_PATH = "output_file_path" - ENABLE_HIGHLIGHT = "enable_highlight" - USAGE_KWARGS = "usage_kwargs" - PROCESS_TEXT = "process_text" - EXTRACTED_TEXT = "extracted_text" - TAGS = "tags" - EXECUTION_SOURCE = "execution_source" - DOC_ID = "doc_id" - TOOL_EXECUTION_METATADA = "tool_execution_metadata" - EXECUTION_DATA_DIR = "execution_data_dir" - RUN_ID = "run_id" - EXECUTION_ID = "execution_id" diff --git a/tools/structure/src/helpers.py b/tools/structure/src/helpers.py deleted file mode 100644 index 2cb0d9bf72..0000000000 --- a/tools/structure/src/helpers.py +++ /dev/null @@ -1,134 +0,0 @@ -import datetime -import logging -from collections.abc import Callable -from typing import Any - -from constants import IndexingConstants as IKeys -from constants import SettingsKeys # type: ignore [attr-defined] - -from unstract.sdk1.prompt import PromptTool -from unstract.sdk1.tool.base import BaseTool - -logger = logging.getLogger(__name__) - - -class StructureToolHelper: - @staticmethod - def dynamic_extraction( - file_path: str, - enable_highlight: bool, - usage_kwargs: dict[str, Any], - run_id: str, - tool_settings: dict[str, Any], - extract_file_path: str, - tool: BaseTool, - execution_run_data_folder: str, - ) -> str: - x2text = tool_settings[SettingsKeys.X2TEXT_ADAPTER] - payload = { - IKeys.X2TEXT_INSTANCE_ID: x2text, - IKeys.FILE_PATH: file_path, - IKeys.ENABLE_HIGHLIGHT: enable_highlight, - IKeys.USAGE_KWARGS: usage_kwargs.copy(), - IKeys.RUN_ID: run_id, - IKeys.EXECUTION_SOURCE: SettingsKeys.TOOL, - IKeys.OUTPUT_FILE_PATH: str(extract_file_path), - IKeys.TAGS: tool.tags, - IKeys.TOOL_EXECUTION_METATADA: tool.get_exec_metadata, - IKeys.EXECUTION_DATA_DIR: str(execution_run_data_folder), - } - - logger.info(f"Prompt service payload for text extraction:\n{payload}") - - prompt_tool = PromptTool( - tool=tool, - prompt_host=tool.get_env_or_die(SettingsKeys.PROMPT_HOST), - prompt_port=tool.get_env_or_die(SettingsKeys.PROMPT_PORT), - request_id=run_id, - ) - return prompt_tool.extract(payload=payload) - - @staticmethod - def dynamic_indexing( - file_path: str, - tool_settings: dict[str, Any], - run_id: str, - tool: BaseTool, - execution_run_data_folder: str, - reindex: bool, - usage_kwargs: dict[str, Any], - enable_highlight: bool, - chunk_size: int, - chunk_overlap: int, - file_hash: str | None = None, - tool_id: str = None, - extracted_text: str = None, - ) -> str: - x2text = tool_settings[SettingsKeys.X2TEXT_ADAPTER] - - payload = { - IKeys.TOOL_ID: tool_id, - IKeys.EMBEDDING_INSTANCE_ID: tool_settings[SettingsKeys.EMBEDDING], - IKeys.VECTOR_DB_INSTANCE_ID: tool_settings[SettingsKeys.VECTOR_DB], - IKeys.X2TEXT_INSTANCE_ID: x2text, - IKeys.FILE_HASH: file_hash, - IKeys.CHUNK_SIZE: chunk_size, - IKeys.CHUNK_OVERLAP: chunk_overlap, - IKeys.REINDEX: reindex, - IKeys.FILE_PATH: str(file_path), - IKeys.ENABLE_HIGHLIGHT: enable_highlight, - IKeys.USAGE_KWARGS: usage_kwargs.copy(), - IKeys.RUN_ID: run_id, - IKeys.EXECUTION_SOURCE: SettingsKeys.TOOL, - IKeys.TAGS: tool.tags, - IKeys.TOOL_EXECUTION_METATADA: tool.get_exec_metadata, - IKeys.EXECUTION_DATA_DIR: str(execution_run_data_folder), - IKeys.EXTRACTED_TEXT: extracted_text, - } - - sensitive_keys = [IKeys.EXTRACTED_TEXT] - payload_to_log = {k: v for k, v in payload.items() if k not in sensitive_keys} - logger.info(f"Prompt service payload for indexing:\n{payload_to_log}") - responder = PromptTool( - tool=tool, - prompt_host=tool.get_env_or_die(SettingsKeys.PROMPT_HOST), - prompt_port=tool.get_env_or_die(SettingsKeys.PROMPT_PORT), - request_id=run_id, - ) - return responder.index(payload=payload) - - @staticmethod - def handle_profile_overrides( - tool: BaseTool, - llm_profile_to_override: dict, - llm_profile_id: str, - tool_metadata: dict, - apply_profile_overrides_func: Callable[[dict, dict], list[str]], - ) -> None: - """Handle profile overrides and logging. - - Args: - tool: The tool instance for logging - llm_profile_to_override: The profile data to apply, or None if no profile - llm_profile_id: The profile ID for logging purposes - tool_metadata: The tool metadata dictionary to modify - apply_profile_overrides_func: Function to apply profile overrides - """ - if llm_profile_to_override: - tool.stream_log( - f"Applying profile overrides from profile: {llm_profile_to_override.get('profile_name', llm_profile_id)}" - ) - changes = apply_profile_overrides_func(tool_metadata, llm_profile_to_override) - if changes: - tool.stream_log("Profile overrides applied successfully. Changes made:") - for change in changes: - tool.stream_log(f" - {change}") - else: - tool.stream_log( - "Profile overrides applied - no changes needed (values already matched)" - ) - - @staticmethod - def elapsed_time(start_time) -> float: - """Returns the elapsed time since the process was started.""" - return (datetime.datetime.now() - start_time).total_seconds() diff --git a/tools/structure/src/main.py b/tools/structure/src/main.py deleted file mode 100644 index f68143a6c8..0000000000 --- a/tools/structure/src/main.py +++ /dev/null @@ -1,772 +0,0 @@ -import datetime -import json -import logging -import os -import sys -from pathlib import Path -from typing import Any - -from constants import SettingsKeys # type: ignore [attr-defined] -from helpers import StructureToolHelper as STHelper -from utils import json_to_markdown, repair_json_with_best_structure - -from unstract.sdk1.constants import LogState, MetadataKey, ToolEnv, UsageKwargs -from unstract.sdk1.platform import PlatformHelper -from unstract.sdk1.prompt import PromptTool -from unstract.sdk1.tool.base import BaseTool -from unstract.sdk1.tool.entrypoint import ToolEntrypoint - -logger = logging.getLogger(__name__) - -PAID_FEATURE_MSG = ( - "It is a cloud / enterprise feature. If you have purchased a plan and still " - "face this issue, please contact support" -) - - -class StructureTool(BaseTool): - def _apply_profile_overrides( - self, tool_metadata: dict, profile_data: dict - ) -> list[str]: - """Apply profile overrides to tool metadata. - - Args: - tool_metadata: The tool metadata dictionary to modify - profile_data: The profile data containing override values - - Returns: - List of change descriptions - """ - changes = [] - - # Mapping between profile keys and tool metadata keys - profile_to_tool_mapping = { - "chunk_overlap": "chunk-overlap", - "chunk_size": "chunk-size", - "embedding_model_id": "embedding", - "llm_id": "llm", - "similarity_top_k": "similarity-top-k", - "vector_store_id": "vector-db", - "x2text_id": "x2text_adapter", - "retrieval_strategy": "retrieval-strategy", - } - - # Override tool_settings section - if "tool_settings" in tool_metadata: - tool_changes = self._override_section( - tool_metadata["tool_settings"], - profile_data, - profile_to_tool_mapping, - section_name="tool_settings", - ) - changes.extend(tool_changes) - - # Override each output in outputs section - if "outputs" in tool_metadata: - for i, output in enumerate(tool_metadata["outputs"]): - output_name = output.get("name", f"output_{i}") - output_changes = self._override_section( - output, - profile_data, - profile_to_tool_mapping, - section_name=f"output[{output_name}]", - ) - changes.extend(output_changes) - - return changes - - def _override_section( - self, - section: dict, - profile_data: dict, - mapping: dict, - section_name: str = "section", - ) -> list[str]: - """Override values in a section using profile data. - - Args: - section: The section dictionary to modify - profile_data: The profile data containing override values - mapping: Mapping between profile keys and section keys - section_name: Name of the section for logging purposes - - Returns: - List of change descriptions - """ - changes = [] - for profile_key, section_key in mapping.items(): - if profile_key in profile_data and section_key in section: - old_value = section[section_key] - new_value = profile_data[profile_key] - if old_value != new_value: # Only track actual changes - section[section_key] = new_value - change_desc = ( - f"{section_name}.{section_key}: {old_value} -> {new_value}" - ) - changes.append(change_desc) - self.stream_log(f"Overrode {change_desc}") - return changes - - def _should_skip_extraction_for_smart_table( - self, input_file: str, outputs: list[dict[str, Any]] - ) -> bool: - """Check if extraction and indexing should be skipped for smart table extraction. - - Args: - input_file: Path to the input file - outputs: List of output configurations - - Returns: - True if extraction/indexing should be skipped, False otherwise - """ - # Check if any output has table_settings with valid JSON prompt - for output in outputs: - if SettingsKeys.TABLE_SETTINGS in output: - prompt = output.get(SettingsKeys.PROMPT, "") - if prompt and isinstance(prompt, str): - try: - # Try to parse the prompt as JSON - schema_data = repair_json_with_best_structure(prompt) - # If it's a valid dict (schema object), skip extraction - if schema_data and isinstance(schema_data, dict): - return True - except Exception as e: - logger.warning( - "Failed to parse prompt as JSON for smart table extraction: %s", - str(e), - ) - continue - return False - - def validate(self, input_file: str, settings: dict[str, Any]) -> None: - enable_challenge: bool = settings.get(SettingsKeys.ENABLE_CHALLENGE, False) - challenge_llm: str = settings.get(SettingsKeys.CHALLENGE_LLM_ADAPTER_ID, "") - if enable_challenge and not challenge_llm: - raise ValueError("Challenge LLM is not set after enabling Challenge") - - def _is_agentic_project(self, tool_metadata: dict[str, Any]) -> bool: - """Check if metadata indicates an agentic project. - - Agentic projects have project_id and json_schema fields. - Prompt studio projects have tool_id and outputs fields. - """ - return "project_id" in tool_metadata and "json_schema" in tool_metadata - - def run( - self, - settings: dict[str, Any], - input_file: str, - output_dir: str, - ) -> None: - prompt_registry_id: str = settings[SettingsKeys.PROMPT_REGISTRY_ID] - is_challenge_enabled: bool = settings.get(SettingsKeys.ENABLE_CHALLENGE, False) - is_summarization_enabled: bool = settings.get( - SettingsKeys.SUMMARIZE_AS_SOURCE, False - ) - is_single_pass_enabled: bool = settings.get( - SettingsKeys.SINGLE_PASS_EXTRACTION_MODE, False - ) - challenge_llm: str = settings.get(SettingsKeys.CHALLENGE_LLM_ADAPTER_ID, "") - is_highlight_enabled: bool = settings.get(SettingsKeys.ENABLE_HIGHLIGHT, False) - responder: PromptTool = PromptTool( - tool=self, - prompt_port=self.get_env_or_die(SettingsKeys.PROMPT_PORT), - prompt_host=self.get_env_or_die(SettingsKeys.PROMPT_HOST), - request_id=self.file_execution_id, - ) - self.stream_log(f"Fetching exported tool with UUID '{prompt_registry_id}'") - - platform_helper: PlatformHelper = PlatformHelper( - tool=self, - platform_port=self.get_env_or_die(ToolEnv.PLATFORM_PORT), - platform_host=self.get_env_or_die(ToolEnv.PLATFORM_HOST), - request_id=self.file_execution_id, - ) - - # Try to fetch as prompt studio tool first - tool_metadata = None - is_agentic = False - exported_tool = None - - try: - exported_tool = platform_helper.get_prompt_studio_tool( - prompt_registry_id=prompt_registry_id - ) - except Exception as e: - # If prompt studio lookup fails, try as agentic project - self.stream_log( - f"Not found as prompt studio project, trying agentic registry: {e}" - ) - - if exported_tool and SettingsKeys.TOOL_METADATA in exported_tool: - tool_metadata = exported_tool[SettingsKeys.TOOL_METADATA] - is_agentic = False - # Explicitly mark metadata for clarity - tool_metadata["is_agentic"] = False - else: - # Try agentic registry as fallback - try: - agentic_tool = platform_helper.get_agentic_studio_tool( - agentic_registry_id=prompt_registry_id - ) - if not agentic_tool or SettingsKeys.TOOL_METADATA not in agentic_tool: - self.stream_error_and_exit( - f"Error fetching project: Registry returned empty response for {prompt_registry_id}" - ) - tool_metadata = agentic_tool[SettingsKeys.TOOL_METADATA] - is_agentic = True - # Explicitly mark metadata for clarity - tool_metadata["is_agentic"] = True - self.stream_log( - f"Retrieved agentic project: {tool_metadata.get('name', prompt_registry_id)}" - ) - except Exception as agentic_error: - self.stream_error_and_exit( - f"Error fetching project from both registries " - f"for ID '{prompt_registry_id}': {agentic_error}" - ) - - # Route to appropriate extraction method - if is_agentic: - return self._run_agentic_extraction( - tool_metadata=tool_metadata, - input_file=input_file, - output_dir=output_dir, - settings=settings, - responder=responder, - platform_helper=platform_helper, - ) - - # Continue with regular prompt studio extraction - llm_profile_id = self.get_exec_metadata.get(SettingsKeys.LLM_PROFILE_ID) - llm_profile_to_override = None - try: - if llm_profile_id: - llm_profile = platform_helper.get_llm_profile(llm_profile_id) - llm_profile_to_override = llm_profile - - # Apply profile overrides if available - STHelper.handle_profile_overrides( - self, - llm_profile_to_override, - llm_profile_id, - tool_metadata, - self._apply_profile_overrides, - ) - except Exception as e: - self.stream_error_and_exit(f"Error fetching prompt studio project: {e}") - ps_project_name = tool_metadata.get("name", prompt_registry_id) - # Count only the active (enabled) prompts - total_prompt_count = len(tool_metadata[SettingsKeys.OUTPUTS]) - self.stream_log( - f"Retrieved prompt studio exported tool '{ps_project_name}' having " - f"'{total_prompt_count}' prompts" - ) - - active_prompt_count = len( - [ - output - for output in tool_metadata[SettingsKeys.OUTPUTS] - if output.get("active", False) - ] - ) - # Update GUI - input_log = f"## Loaded '{ps_project_name}'\n{json_to_markdown(tool_metadata)}\n" - output_log = ( - f"## Processing '{self.source_file_name}'\nThis might take a while and " - "involve...\n- Extracting text\n- Indexing\n- Retrieving answers " - f"for '{active_prompt_count}' prompts" - ) - self.stream_update(input_log, state=LogState.INPUT_UPDATE) - self.stream_update(output_log, state=LogState.OUTPUT_UPDATE) - - file_hash = self.get_exec_metadata.get(MetadataKey.SOURCE_HASH) - tool_id = tool_metadata[SettingsKeys.TOOL_ID] - tool_settings = tool_metadata[SettingsKeys.TOOL_SETTINGS] - outputs = tool_metadata[SettingsKeys.OUTPUTS] - tool_settings[SettingsKeys.CHALLENGE_LLM] = challenge_llm - tool_settings[SettingsKeys.ENABLE_CHALLENGE] = is_challenge_enabled - tool_settings[SettingsKeys.ENABLE_SINGLE_PASS_EXTRACTION] = is_single_pass_enabled - tool_settings[SettingsKeys.SUMMARIZE_AS_SOURCE] = is_summarization_enabled - tool_settings[SettingsKeys.ENABLE_HIGHLIGHT] = is_highlight_enabled - _, file_name = os.path.split(input_file) - if is_summarization_enabled: - file_name = SettingsKeys.SUMMARIZE - tool_data_dir = Path(self.get_env_or_die(ToolEnv.EXECUTION_DATA_DIR)) - execution_run_data_folder = Path(self.get_env_or_die(ToolEnv.EXECUTION_DATA_DIR)) - - extracted_input_file = str(execution_run_data_folder / SettingsKeys.EXTRACT) - # Resolve and pass log events ID - payload = { - SettingsKeys.RUN_ID: self.file_execution_id, - SettingsKeys.EXECUTION_ID: self.execution_id, - SettingsKeys.TOOL_SETTINGS: tool_settings, - SettingsKeys.OUTPUTS: outputs, - SettingsKeys.TOOL_ID: tool_id, - SettingsKeys.FILE_HASH: file_hash, - SettingsKeys.FILE_NAME: file_name, - SettingsKeys.FILE_PATH: extracted_input_file, - SettingsKeys.EXECUTION_SOURCE: SettingsKeys.TOOL, - } - - custom_data = self.get_exec_metadata.get(SettingsKeys.CUSTOM_DATA, {}) - payload["custom_data"] = custom_data - - # Check if we should skip extraction and indexing for Excel table extraction with valid JSON - skip_extraction_and_indexing = self._should_skip_extraction_for_smart_table( - input_file, outputs - ) - - extracted_text = "" - usage_kwargs: dict[Any, Any] = dict() - if skip_extraction_and_indexing: - self.stream_log( - "Skipping extraction and indexing for Excel table with valid JSON schema" - ) - else: - self.stream_log(f"Extracting document '{self.source_file_name}'") - usage_kwargs[UsageKwargs.RUN_ID] = self.file_execution_id - usage_kwargs[UsageKwargs.FILE_NAME] = self.source_file_name - usage_kwargs[UsageKwargs.EXECUTION_ID] = self.execution_id - extracted_text = STHelper.dynamic_extraction( - file_path=input_file, - enable_highlight=is_highlight_enabled, - usage_kwargs=usage_kwargs, - run_id=self.file_execution_id, - tool_settings=tool_settings, - extract_file_path=tool_data_dir / SettingsKeys.EXTRACT, - tool=self, - execution_run_data_folder=str(execution_run_data_folder), - ) - - index_metrics = {} - if is_summarization_enabled: - summarize_file_path, summarize_file_hash = self._summarize( - tool_settings=tool_settings, - tool_data_dir=tool_data_dir, - responder=responder, - outputs=outputs, - usage_kwargs=usage_kwargs, - ) - payload[SettingsKeys.FILE_HASH] = summarize_file_hash - payload[SettingsKeys.FILE_PATH] = summarize_file_path - elif skip_extraction_and_indexing: - # Use source file directly for Excel with valid JSON - payload[SettingsKeys.FILE_PATH] = input_file - pass - elif not is_single_pass_enabled: - # Track seen parameter combinations to avoid duplicate indexing - seen_params = set() - - for output in outputs: - # Get current parameter combination - chunk_size = output[SettingsKeys.CHUNK_SIZE] - chunk_overlap = output[SettingsKeys.CHUNK_OVERLAP] - vector_db = tool_settings[SettingsKeys.VECTOR_DB] - embedding = tool_settings[SettingsKeys.EMBEDDING] - x2text = tool_settings[SettingsKeys.X2TEXT_ADAPTER] - - # Create a unique key for this parameter combination - param_key = ( - f"chunk_size={chunk_size}_" - f"chunk_overlap={chunk_overlap}_" - f"vector_db={vector_db}_" - f"embedding={embedding}_" - f"x2text={x2text}" - ) - - # Only process if we haven't seen this combination yet and chunk_size is not zero - if chunk_size != 0 and param_key not in seen_params: - seen_params.add(param_key) - - indexing_start_time = datetime.datetime.now() - self.stream_log( - f"Indexing document with: chunk_size={chunk_size}, " - f"chunk_overlap={chunk_overlap}, vector_db={vector_db}, " - f"embedding={embedding}, x2text={x2text}" - ) - - STHelper.dynamic_indexing( - tool_settings=tool_settings, - run_id=self.file_execution_id, - file_path=tool_data_dir / SettingsKeys.EXTRACT, - tool=self, - execution_run_data_folder=str(execution_run_data_folder), - chunk_overlap=chunk_overlap, - reindex=True, - usage_kwargs=usage_kwargs, - enable_highlight=is_highlight_enabled, - chunk_size=chunk_size, - tool_id=tool_metadata[SettingsKeys.TOOL_ID], - file_hash=file_hash, - extracted_text=extracted_text, - ) - - index_metrics[output[SettingsKeys.NAME]] = { - SettingsKeys.INDEXING: { - "time_taken(s)": STHelper.elapsed_time( - start_time=indexing_start_time - ) - } - } - - if is_single_pass_enabled: - self.stream_log("Fetching response for single pass extraction...") - structured_output = responder.single_pass_extraction( - payload=payload, - ) - else: - for output in outputs: - if SettingsKeys.TABLE_SETTINGS in output: - table_settings = output[SettingsKeys.TABLE_SETTINGS] - is_directory_mode: bool = table_settings.get( - SettingsKeys.IS_DIRECTORY_MODE, False - ) - # Use source file directly for Excel with valid JSON, otherwise use extracted file - if skip_extraction_and_indexing: - table_settings[SettingsKeys.INPUT_FILE] = input_file - payload[SettingsKeys.FILE_PATH] = input_file - else: - table_settings[SettingsKeys.INPUT_FILE] = extracted_input_file - table_settings[SettingsKeys.IS_DIRECTORY_MODE] = is_directory_mode - self.stream_log(f"Performing table extraction with: {table_settings}") - output.update({SettingsKeys.TABLE_SETTINGS: table_settings}) - - self.stream_log(f"Fetching responses for '{len(outputs)}' prompt(s)...") - structured_output = responder.answer_prompt( - payload=payload, - ) - - # HACK: Replacing actual file's name instead of INFILE - # Ensure metadata section exists - if SettingsKeys.METADATA not in structured_output: - structured_output[SettingsKeys.METADATA] = {} - self.stream_log("Created metadata section in structured_output") - - structured_output[SettingsKeys.METADATA][SettingsKeys.FILE_NAME] = ( - self.source_file_name - ) - - # Add extracted text for HITL raw view - if extracted_text: - structured_output[SettingsKeys.METADATA]["extracted_text"] = extracted_text - self.stream_log( - f"Added text extracted from the document to metadata (length: {len(extracted_text)} characters)" - ) - else: - self.stream_log( - "No text is extracted from the document to add to the metadata" - ) - if merged_metrics := self._merge_metrics( - structured_output.get(SettingsKeys.METRICS, {}), index_metrics - ): - structured_output[SettingsKeys.METRICS] = merged_metrics - # Update GUI - output_log = ( - f"## Result\n**NOTE:** In case of a deployed pipeline, the result would " - "be a JSON. This has been rendered for readability here\n" - f"{json_to_markdown(structured_output)}\n" - ) - self.stream_update(output_log, state=LogState.OUTPUT_UPDATE) - - try: - self.stream_log( - "Writing prompt studio project's output to workflow's storage" - ) - output_path = Path(output_dir) / f"{Path(self.source_file_name).stem}.json" - self.workflow_filestorage.json_dump(path=output_path, data=structured_output) - self.stream_log( - "Prompt studio project's output written successfully to workflow's storage" - ) - except OSError as e: - self.stream_error_and_exit(f"Error creating output file: {e}") - except json.JSONDecodeError as e: - self.stream_error_and_exit(f"Error encoding JSON: {e}") - self.write_tool_result(data=structured_output) - - def _remove_source_refs(self, data: Any) -> Any: - """Recursively remove _source_refs from data structure. - - Args: - data: Data structure (dict, list, or primitive) to clean - - Returns: - Cleaned data structure without _source_refs fields - """ - if isinstance(data, dict): - return { - key: self._remove_source_refs(value) - for key, value in data.items() - if key != "_source_refs" - } - elif isinstance(data, list): - return [self._remove_source_refs(item) for item in data] - else: - return data - - def _merge_metrics(self, metrics1: dict, metrics2: dict) -> dict: - """Intelligently merge two metrics dictionaries. - - For keys that exist in both dictionaries with dictionary values, merge the dictionaries. - For keys that exist in only one dictionary or have non-dictionary values, use the value as-is. - - Args: - metrics1 (dict): First metrics dictionary - metrics2 (dict): Second metrics dictionary - - Returns: - dict: Merged metrics dictionary - """ - merged_metrics = {} - - # Get all unique keys from both dictionaries - all_keys = set(metrics1) | set(metrics2) - - for key in all_keys: - # If key exists in both dictionaries and both values are dictionaries, merge them - if ( - key in metrics1 - and key in metrics2 - and isinstance(metrics1[key], dict) - and isinstance(metrics2[key], dict) - ): - merged_metrics[key] = {**metrics1[key], **metrics2[key]} - # Otherwise just take the value from whichever dictionary has it - elif key in metrics1: - merged_metrics[key] = metrics1[key] - else: - merged_metrics[key] = metrics2[key] - - return merged_metrics - - def _run_agentic_extraction( - self, - tool_metadata: dict[str, Any], - input_file: str, - output_dir: str, - settings: dict[str, Any], - responder: PromptTool, - platform_helper: PlatformHelper, - ) -> None: - """Execute agentic extraction pipeline. - - Args: - tool_metadata: Complete agentic project metadata - input_file: Path to input document - output_dir: Directory for output files - settings: Workflow settings (contains adapter overrides) - responder: PromptTool instance for API calls - platform_helper: PlatformHelper instance for platform API calls - """ - from unstract.sdk1.x2txt import X2Text - - # Extract agentic-specific metadata - project_id = tool_metadata.get("project_id") - project_name = tool_metadata.get("name", project_id) - json_schema = tool_metadata.get("json_schema", {}) - prompt_text = tool_metadata.get("prompt_text", "") - prompt_version = tool_metadata.get("prompt_version", 1) - schema_version = tool_metadata.get("schema_version", 1) - adapter_config = tool_metadata.get("adapter_config", {}) - - self.stream_log( - f"Executing agentic extraction for project '{project_name}' " - f"(schema v{schema_version}, prompt v{prompt_version})" - ) - - # Get adapter IDs from settings (workflow UI overrides) - # or fall back to exported defaults - extractor_llm = settings.get( - "extractor_llm_adapter_id", adapter_config.get("extractor_llm") - ) - llmwhisperer = settings.get( - "llmwhisperer_adapter_id", adapter_config.get("llmwhisperer") - ) - enable_highlight = settings.get( - SettingsKeys.ENABLE_HIGHLIGHT, tool_metadata.get("enable_highlight", False) - ) - - # Get platform details for organization_id - platform_details = platform_helper.get_platform_details() - organization_id = ( - platform_details.get("organization_id") if platform_details else None - ) - - if not organization_id: - self.stream_error_and_exit("Failed to get organization_id from platform") - - # Update GUI - input_log = ( - f"## Loaded Agentic Project '{project_name}'\n" - f"- **Project ID**: {project_id}\n" - f"- **Schema Version**: {schema_version}\n" - f"- **Prompt Version**: {prompt_version}\n" - f"- **Extractor LLM**: {extractor_llm}\n" - ) - output_log = ( - f"## Processing '{self.source_file_name}'\n" - "Executing agentic extraction pipeline...\n" - "- Extracting document text\n" - "- Running LLM extraction\n" - ) - self.stream_update(input_log, state=LogState.INPUT_UPDATE) - self.stream_update(output_log, state=LogState.OUTPUT_UPDATE) - - try: - # Step 1: Extract text from document using X2Text/LLMWhisperer - self.stream_log("Extracting text from document...") - x2text = X2Text(tool=self, adapter_instance_id=llmwhisperer) - - extraction_result = x2text.process( - input_file_path=input_file, - enable_highlight=enable_highlight, - fs=self.workflow_filestorage, - ) - - document_text = extraction_result.extracted_text - line_metadata = ( - extraction_result.extraction_metadata.line_metadata - if extraction_result.extraction_metadata - else None - ) - - self.stream_log(f"Extracted {len(document_text)} characters of text") - - # Step 2: Build extraction payload for /agentic/extract endpoint - payload = { - "document_id": self.file_execution_id, # Use run ID as document ID - "prompt_text": prompt_text, - "document_text": document_text, - "schema": json_schema, - "organization_id": organization_id, - "adapter_instance_id": extractor_llm, - "include_source_refs": enable_highlight, - } - - # Add line_metadata if available for highlighting - if line_metadata and enable_highlight: - payload["line_metadata"] = line_metadata - self.stream_log( - f"Including {len(line_metadata)} line metadata entries for highlighting" - ) - - # Step 3: Call agentic extraction endpoint - self.stream_log("Calling agentic extraction endpoint...") - extraction_response = responder.agentic_extraction(payload=payload) - - # Step 4: Process response from agentic extraction - extracted_data = extraction_response.get(SettingsKeys.OUTPUT, {}) - - # Remove _source_refs from extracted data - try: - extracted_data = self._remove_source_refs(extracted_data) - except Exception as e: - self.stream_log( - f"Warning: Failed to remove _source_refs: {e}. " - "Proceeding with original data." - ) - - # Build final structured output in prompt studio format - structured_output = { - SettingsKeys.OUTPUT: extracted_data, - SettingsKeys.METADATA: { - **extraction_response.get(SettingsKeys.METADATA, {}), - SettingsKeys.FILE_NAME: self.source_file_name, - "project_id": project_id, - "schema_version": schema_version, - "prompt_version": prompt_version, - "document_id": self.file_execution_id, - }, - } - output_log = ( - f"## Agentic Extraction Complete\n" - f"Successfully extracted data from '{self.source_file_name}'\n" - f"{json_to_markdown(structured_output)}\n" - ) - self.stream_update(output_log, state=LogState.OUTPUT_UPDATE) - - # Write output to file - self.stream_log("Writing agentic extraction output to workflow storage") - output_path = Path(output_dir) / f"{Path(self.source_file_name).stem}.json" - self.workflow_filestorage.json_dump(path=output_path, data=structured_output) - self.stream_log("Output written successfully to workflow storage") - - # Write tool result - self.write_tool_result(data=structured_output) - - except Exception as e: - self.stream_error_and_exit(f"Error during agentic extraction: {e}") - - def _summarize( - self, - tool_settings: dict[str, Any], - tool_data_dir: Path, - responder: PromptTool, - outputs: dict[str, Any], - usage_kwargs: dict[Any, Any] = {}, - ) -> tuple[str, str]: - """Summarizes the context of the file. - - Args: - tool_settings (dict[str, Any]): Settings for the tool. - tool_data_dir (Path): Directory where tool data is stored. - responder (PromptTool): Instance of a tool used to generate the summary. - outputs (dict[str, Any]): Dictionary containing prompt details. - usage_kwargs (dict[Any, Any]): Used to capture usage metrics. - - Returns: - tuple[str, str]: Tuple containing the path to the summarized file and its hash. - """ - llm_adapter_instance_id: str = tool_settings[SettingsKeys.LLM] - embedding_instance_id: str = tool_settings[SettingsKeys.EMBEDDING] - vector_db_instance_id: str = tool_settings[SettingsKeys.VECTOR_DB] - x2text_instance_id: str = tool_settings[SettingsKeys.X2TEXT_ADAPTER] - summarize_prompt: str = tool_settings[SettingsKeys.SUMMARIZE_PROMPT] - run_id: str = usage_kwargs.get(UsageKwargs.RUN_ID) - extract_file_path = tool_data_dir / SettingsKeys.EXTRACT - summarize_file_path = tool_data_dir / SettingsKeys.SUMMARIZE - - summarized_context = "" - self.stream_log( - f"Checking if summarized context exists at '{summarize_file_path}'..." - ) - if self.workflow_filestorage.exists(summarize_file_path): - summarized_context = self.workflow_filestorage.read( - path=summarize_file_path, mode="r" - ) - if not summarized_context: - context = "" - context = self.workflow_filestorage.read(path=extract_file_path, mode="r") - prompt_keys = [] - for output in outputs: - prompt_keys.append(output[SettingsKeys.NAME]) - output[SettingsKeys.EMBEDDING] = embedding_instance_id - output[SettingsKeys.VECTOR_DB] = vector_db_instance_id - output[SettingsKeys.X2TEXT_ADAPTER] = x2text_instance_id - output[SettingsKeys.CHUNK_SIZE] = 0 - output[SettingsKeys.CHUNK_OVERLAP] = 0 - self.stream_log("Summarized context not found, summarizing...") - payload = { - SettingsKeys.RUN_ID: run_id, - SettingsKeys.LLM_ADAPTER_INSTANCE_ID: llm_adapter_instance_id, - SettingsKeys.SUMMARIZE_PROMPT: summarize_prompt, - SettingsKeys.CONTEXT: context, - SettingsKeys.PROMPT_KEYS: prompt_keys, - } - structure_output = responder.summarize(payload=payload) - summarized_context = structure_output.get(SettingsKeys.DATA, "") - self.stream_log(f"Writing summarized context to '{summarize_file_path}'") - self.workflow_filestorage.write( - path=summarize_file_path, mode="w", data=summarized_context - ) - - summarize_file_hash: str = self.workflow_filestorage.get_hash_from_file( - path=summarize_file_path - ) - return str(summarize_file_path), summarize_file_hash - - -if __name__ == "__main__": - args = sys.argv[1:] - tool = StructureTool.from_tool_args(args=args) - ToolEntrypoint.launch(tool=tool, args=args) diff --git a/tools/structure/src/utils.py b/tools/structure/src/utils.py deleted file mode 100644 index a374918aba..0000000000 --- a/tools/structure/src/utils.py +++ /dev/null @@ -1,100 +0,0 @@ -from typing import Any - -from json_repair import repair_json - - -def json_to_markdown(data: Any, level: int = 0, parent_key: str = "") -> str: - markdown = "" - indent = " " * level # Increase indentation by 2 for nested levels - - if isinstance(data, dict): - for key, value in data.items(): - if isinstance(value, (dict, list)): - # If value is a dict or list, make it expandable - markdown += f"{indent}- **{key}**:\n" - markdown += json_to_markdown(value, level + 1, key) - else: - markdown += f"{indent}- **{key}**: {value}\n" - elif isinstance(data, list): - for index, item in enumerate(data, 1): - # Use parent key for list item naming - # Fall back to "Item" if parent_key is empty - # TODO: Determine child key using parent key for all plural combinations - item_label = ( - f"{parent_key[:-1] if parent_key.endswith('s') else parent_key} {index}" - if parent_key - else f"Item {index}" - ) - markdown += f"{indent}- **{item_label}**\n" - markdown += json_to_markdown(item, level + 1, parent_key) - else: - markdown += f"{indent}- {data}\n" - - return markdown - - -def repair_json_with_best_structure(json_str: str) -> Any: - """Repair and parse a potentially malformed JSON string with optimal structure detection. - - This function attempts to repair and parse a JSON string using two different strategies - and returns the result that produces the most useful data structure. It handles cases - where the input might be incomplete, malformed, or ambiguous JSON. - - The function tries two parsing approaches: - 1. Parse the JSON string as-is - 2. Parse the JSON string wrapped in array brackets [...] - - It then intelligently selects the best result based on the following logic: - - If both results are strings (failed to parse as objects), return the as-is result - - If one result is a string and the other is an object/array, return the object/array - - If wrapping produces a single-element list that equals the as-is result, return as-is - - If as-is produces an object/array and wrapping produces multiple elements, prefer wrapped - - Otherwise, prefer the as-is result - - Args: - json_str: A string containing potentially malformed JSON data. Can be a complete - JSON object, array, or partial JSON that needs repair. - - Returns: - The parsed JSON structure (dict, list, str, or other JSON-compatible type) that - represents the most meaningful interpretation of the input string. The return type - depends on the input and which parsing strategy produces the better result. - - Example: - >>> repair_json_with_best_structure('{"name": "John", "age": 30}') - {'name': 'John', 'age': 30} - - >>> repair_json_with_best_structure('{"incomplete": "object"') - {'incomplete': 'object'} - - >>> repair_json_with_best_structure('{"a": 1}{"b": 2}') - [{'a': 1}, {'b': 2}] - - Note: - This function is specifically designed for the structure-tool and uses the - json_repair library's repair_json function with return_objects=True and - ensure_ascii=False parameters. - """ - parsed_as_is = repair_json(json_str=json_str, return_objects=True, ensure_ascii=False) - parsed_with_wrap = repair_json( - "[" + json_str + "]", return_objects=True, ensure_ascii=False - ) - - if all(isinstance(x, str) for x in (parsed_as_is, parsed_with_wrap)): - return parsed_as_is - - if isinstance(parsed_as_is, str): - return parsed_with_wrap - if isinstance(parsed_with_wrap, str): - return parsed_as_is - - if isinstance(parsed_with_wrap, list) and len(parsed_with_wrap) == 1: - if parsed_with_wrap[0] == parsed_as_is: - return parsed_as_is - - if isinstance(parsed_as_is, (dict, list)): - if isinstance(parsed_with_wrap, list) and len(parsed_with_wrap) > 1: - return parsed_with_wrap - return parsed_as_is - - return parsed_with_wrap diff --git a/tox.ini b/tox.ini index c684321ed7..dd13211aef 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -env_list = py{312}, runner, sdk1, prompt-service +env_list = py{312}, runner, sdk1 requires = tox-uv>=0.2.0 @@ -45,18 +45,3 @@ commands_pre = uv sync --group test commands = uv run pytest -v -m "not slow" --cov=src/unstract/sdk1 --cov-report=term --cov-report=html --md-report-verbose=1 --md-report --md-report-flavor gfm --md-report-output ../../sdk1-report.md - -[testenv:prompt-service] -changedir = prompt-service -deps = uv -allowlist_externals= - sh - uv - pytest -commands_pre = - uv sync --group test -commands = - # --noconftest is required because the parent tests/conftest.py imports - # Flask blueprints which trigger the full adapter chain (pinecone etc.). - # Unit tests must not depend on integration fixtures. - uv run pytest src/unstract/prompt_service/tests/unit/ -v -m "not slow" --noconftest --md-report-verbose=1 --md-report --md-report-flavor gfm --md-report-output ../prompt-service-report.md diff --git a/unstract/sdk1/src/unstract/sdk1/prompt.py b/unstract/sdk1/src/unstract/sdk1/prompt.py deleted file mode 100644 index c648506209..0000000000 --- a/unstract/sdk1/src/unstract/sdk1/prompt.py +++ /dev/null @@ -1,260 +0,0 @@ -import functools -import logging -from collections.abc import Callable -from typing import Any, ParamSpec, TypeVar - -import requests -from requests import ConnectionError, RequestException, Response -from unstract.sdk1.constants import MimeType, RequestHeader, ToolEnv -from unstract.sdk1.platform import PlatformHelper -from unstract.sdk1.tool.base import BaseTool -from unstract.sdk1.utils.common import log_elapsed -from unstract.sdk1.utils.retry_utils import retry_prompt_service_call - -logger = logging.getLogger(__name__) - -P = ParamSpec("P") -R = TypeVar("R") - - -def handle_service_exceptions(context: str) -> Callable[[Callable[P, R]], Callable[P, R]]: - """Decorator to handle exceptions in PromptTool service calls. - - Args: - context (str): Context string describing where the error occurred - Returns: - Callable: Decorated function that handles service exceptions - """ - - def decorator(func: Callable[P, R]) -> Callable[P, R]: - @functools.wraps(func) - def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: - try: - return func(*args, **kwargs) - except ConnectionError as e: - msg = f"Error while {context}. Unable to connect to prompt service." - logger.error(f"{msg}\n{e}") - args[0].tool.stream_error_and_exit(msg, e) - return None - except RequestException as e: - error_message = str(e) - response = getattr(e, "response", None) - if response is not None: - if ( - MimeType.JSON in response.headers.get("Content-Type", "").lower() - and "error" in response.json() - ): - error_message = response.json()["error"] - elif response.text: - error_message = response.text - msg = f"Error while {context}. {error_message}" - args[0].tool.stream_error_and_exit(msg, e) - return None - except Exception as e: - # Handle any other unexpected exceptions - msg = f"Error while {context}. An unexpected error occurred" - logger.error(f"{msg}: {type(e).__name__}: {str(e)}", exc_info=True) - args[0].tool.stream_error_and_exit(msg, e) - return None - - return wrapper - - return decorator - - -class PromptTool: - """Class to handle prompt service methods for Unstract Tools.""" - - def __init__( - self, - tool: BaseTool, - prompt_host: str, - prompt_port: str, - is_public_call: bool = False, - request_id: str | None = None, - ) -> None: - """Class to interact with prompt-service. - - Args: - tool (AbstractTool): Instance of AbstractTool - prompt_host (str): Host of platform service - prompt_port (str): Port of platform service - is_public_call (bool): Whether the call is public. Defaults to False - """ - self.tool = tool - self.base_url = PlatformHelper.get_platform_base_url(prompt_host, prompt_port) - self.is_public_call = is_public_call - self.request_id = request_id - if not is_public_call: - self.bearer_token = tool.get_env_or_die(ToolEnv.PLATFORM_API_KEY) - - @log_elapsed(operation="ANSWER_PROMPTS") - @handle_service_exceptions("answering prompt(s)") - def answer_prompt( - self, - payload: dict[str, Any], - params: dict[str, str] | None = None, - headers: dict[str, str] | None = None, - ) -> dict[str, Any]: - url_path = "answer-prompt" - if self.is_public_call: - url_path = "answer-prompt-public" - return self._call_service( - url_path=url_path, payload=payload, params=params, headers=headers - ) - - @log_elapsed(operation="INDEX") - @handle_service_exceptions("indexing") - def index( - self, - payload: dict[str, Any], - params: dict[str, str] | None = None, - headers: dict[str, str] | None = None, - ) -> str: - url_path = "index" - if self.is_public_call: - url_path = "index-public" - prompt_service_response = self._call_service( - url_path=url_path, - payload=payload, - params=params, - headers=headers, - ) - return prompt_service_response.get("doc_id") - - @log_elapsed(operation="EXTRACT") - @handle_service_exceptions("extracting") - def extract( - self, - payload: dict[str, Any], - params: dict[str, str] | None = None, - headers: dict[str, str] | None = None, - ) -> dict[str, Any]: - url_path = "extract" - if self.is_public_call: - url_path = "extract-public" - prompt_service_response = self._call_service( - url_path=url_path, - payload=payload, - params=params, - headers=headers, - ) - return prompt_service_response.get("extracted_text") - - @log_elapsed(operation="SINGLE_PASS_EXTRACTION") - @handle_service_exceptions("single pass extraction") - def single_pass_extraction( - self, - payload: dict[str, Any], - params: dict[str, str] | None = None, - headers: dict[str, str] | None = None, - ) -> dict[str, Any]: - return self._call_service( - url_path="single-pass-extraction", - payload=payload, - params=params, - headers=headers, - ) - - @log_elapsed(operation="SUMMARIZATION") - @handle_service_exceptions("summarizing") - def summarize( - self, - payload: dict[str, Any], - params: dict[str, str] | None = None, - headers: dict[str, str] | None = None, - ) -> dict[str, Any]: - return self._call_service( - url_path="summarize", - payload=payload, - params=params, - headers=headers, - ) - - @log_elapsed(operation="AGENTIC_EXTRACTION") - @handle_service_exceptions("executing agentic extraction") - def agentic_extraction( - self, - payload: dict[str, Any], - params: dict[str, str] | None = None, - headers: dict[str, str] | None = None, - ) -> dict[str, Any]: - """Execute agentic extraction via prompt service. - - Args: - payload: Extraction payload containing project_id, json_schema, - prompt_text, adapter IDs, etc. - params: Optional query parameters - headers: Optional headers - - Returns: - dict: Extraction results with data, metadata, and metrics - """ - return self._call_service( - url_path="agentic/extract", - payload=payload, - params=params, - headers=headers, - ) - - def _get_headers(self, headers: dict[str, str] | None = None) -> dict[str, str]: - """Get default headers for requests. - - Returns: - dict[str, str]: Default headers including request ID and authorization - """ - request_headers = {RequestHeader.REQUEST_ID: self.request_id} - if self.is_public_call: - return request_headers - request_headers.update( - {RequestHeader.AUTHORIZATION: f"Bearer {self.bearer_token}"} - ) - - if headers: - request_headers.update(headers) - return request_headers - - @retry_prompt_service_call - def _call_service( - self, - url_path: str, - payload: dict[str, Any] | None = None, - params: dict[str, str] | None = None, - headers: dict[str, str] | None = None, - method: str = "POST", - ) -> dict[str, Any]: - """Communicates to prompt service to fetch response for the prompt. - - Only POST calls are made to prompt-service though functionality exists. - This method automatically retries on connection errors with exponential backoff. - - Retry behavior is configurable via environment variables: - - PROMPT_SERVICE_MAX_RETRIES (default: 3) - - PROMPT_SERVICE_BASE_DELAY (default: 1.0s) - - PROMPT_SERVICE_MULTIPLIER (default: 2.0) - - PROMPT_SERVICE_JITTER (default: true) - - Args: - url_path (str): URL path to the service endpoint - payload (dict, optional): Payload to send in the request body - params (dict, optional): Query parameters to include in the request - headers (dict, optional): Headers to include in the request - method (str): HTTP method to use for the request (GET or POST) - - Returns: - dict: Response from the prompt service - """ - url: str = f"{self.base_url}/{url_path}" - req_headers = self._get_headers(headers) - response: Response = Response() - if method.upper() == "POST": - response = requests.post( - url=url, json=payload, params=params, headers=req_headers - ) - elif method.upper() == "GET": - response = requests.get(url=url, params=params, headers=req_headers) - else: - raise ValueError(f"Unsupported HTTP method: {method}") - - response.raise_for_status() - return response.json() diff --git a/unstract/sdk1/src/unstract/sdk1/utils/retry_utils.py b/unstract/sdk1/src/unstract/sdk1/utils/retry_utils.py index 0a13331474..a4bf5d63b7 100644 --- a/unstract/sdk1/src/unstract/sdk1/utils/retry_utils.py +++ b/unstract/sdk1/src/unstract/sdk1/utils/retry_utils.py @@ -516,10 +516,3 @@ def create_retry_decorator( # - PLATFORM_SERVICE_MULTIPLIER (default: 2.0) # - PLATFORM_SERVICE_JITTER (default: true) retry_platform_service_call = create_retry_decorator("PLATFORM_SERVICE") - -# Retry configured through below envs. -# - PROMPT_SERVICE_MAX_RETRIES (default: 3) -# - PROMPT_SERVICE_BASE_DELAY (default: 1.0s) -# - PROMPT_SERVICE_MULTIPLIER (default: 2.0) -# - PROMPT_SERVICE_JITTER (default: true) -retry_prompt_service_call = create_retry_decorator("PROMPT_SERVICE") diff --git a/unstract/sdk1/tests/conftest.py b/unstract/sdk1/tests/conftest.py index b3858b7e2f..f01ce56e5e 100644 --- a/unstract/sdk1/tests/conftest.py +++ b/unstract/sdk1/tests/conftest.py @@ -25,10 +25,6 @@ def clean_env(monkeypatch: MonkeyPatch) -> MonkeyPatch: "PLATFORM_SERVICE_BASE_DELAY", "PLATFORM_SERVICE_MULTIPLIER", "PLATFORM_SERVICE_JITTER", - "PROMPT_SERVICE_MAX_RETRIES", - "PROMPT_SERVICE_BASE_DELAY", - "PROMPT_SERVICE_MULTIPLIER", - "PROMPT_SERVICE_JITTER", ] for var in env_vars: monkeypatch.delenv(var, raising=False) diff --git a/unstract/sdk1/tests/test_prompt.py b/unstract/sdk1/tests/test_prompt.py deleted file mode 100644 index 798db9106c..0000000000 --- a/unstract/sdk1/tests/test_prompt.py +++ /dev/null @@ -1,174 +0,0 @@ -"""Integration tests for prompt module with retry logic.""" - -from typing import Any, Self -from unittest.mock import MagicMock, Mock, patch - -import pytest -from pytest import MonkeyPatch -from requests.exceptions import ConnectionError, Timeout -from unstract.sdk1.prompt import PromptTool - - -class TestPromptToolRetry: - """Tests for PromptTool retry functionality.""" - - @pytest.fixture - def mock_tool(self: Self) -> MagicMock: - """Create a mock tool for testing.""" - tool = MagicMock() - tool.get_env_or_die.side_effect = lambda key: { - "PLATFORM_API_KEY": "test-api-key", - }.get(key, "mock-value") - tool.stream_log = MagicMock() - tool.stream_error_and_exit = MagicMock() - return tool - - @pytest.fixture - def prompt_tool(self: Self, mock_tool: MagicMock) -> PromptTool: - """Create a PromptTool instance.""" - return PromptTool( - tool=mock_tool, - prompt_host="http://localhost", - prompt_port="3003", - is_public_call=False, - request_id="test-request-id", - ) - - def test_success_on_first_attempt( - self: Self, prompt_tool: PromptTool, clean_env: MonkeyPatch - ) -> None: - """Test successful service call on first attempt.""" - expected_response = {"result": "success"} - payload = {"prompt": "test"} - - with patch("requests.post") as mock_post: - mock_response = Mock() - mock_response.json.return_value = expected_response - mock_response.raise_for_status = Mock() - mock_post.return_value = mock_response - - result = prompt_tool._call_service("answer-prompt", payload=payload) - - assert result == expected_response - assert mock_post.call_count == 1 - - @pytest.mark.parametrize( - "error_type,error_instance", - [ - ("ConnectionError", ConnectionError("Connection failed")), - ("Timeout", Timeout("Request timed out")), - ], - ) - def test_retry_on_errors( - self: Self, - prompt_tool: PromptTool, - error_type: str, - error_instance: Exception, - clean_env: MonkeyPatch, - ) -> None: - """Test service retries on ConnectionError and Timeout.""" - expected_response = {"result": "success"} - payload = {"prompt": "test"} - - with patch("requests.post") as mock_post: - mock_response = Mock() - mock_response.json.return_value = expected_response - mock_response.raise_for_status = Mock() - - mock_post.side_effect = [ - error_instance, - mock_response, - ] - - result = prompt_tool._call_service("answer-prompt", payload=payload) - - assert result == expected_response - assert mock_post.call_count == 2 - - @pytest.mark.slow - def test_max_retries_exceeded( - self: Self, mock_tool: MagicMock, clean_env: MonkeyPatch - ) -> None: - """Test service call fails after exceeding max retries.""" - prompt_tool = PromptTool( - tool=mock_tool, - prompt_host="http://localhost", - prompt_port="3003", - is_public_call=False, - request_id="test-request-id", - ) - - payload = {"prompt": "test"} - - with patch("requests.post") as mock_post: - mock_post.side_effect = ConnectionError("Persistent failure") - - # Exception handled by decorator - with pytest.raises(ConnectionError): - prompt_tool._call_service("answer-prompt", payload=payload) - - # Default: 3 retries + 1 initial = 4 attempts - assert mock_post.call_count == 4 - - @pytest.mark.parametrize( - "method_name,payload", - [ - ("answer_prompt", {"prompts": ["test"]}), - ("index", {"document": "test"}), - ("extract", {"doc_id": "123"}), - ("summarize", {"text": "test"}), - ], - ) - def test_wrapper_methods_retry( - self: Self, - prompt_tool: PromptTool, - method_name: str, - payload: dict[str, Any], - clean_env: MonkeyPatch, - ) -> None: - """Test that wrapper methods inherit retry behavior.""" - expected_response = { - "answers": ["result"], - "doc_id": "doc-123", - "extracted_text": "text", - "summary": "summary", - } - - with patch("requests.post") as mock_post: - mock_response = Mock() - mock_response.json.return_value = expected_response - mock_response.raise_for_status = Mock() - - mock_post.side_effect = [ - ConnectionError("Transient failure"), - mock_response, - ] - - getattr(prompt_tool, method_name)(payload) - - assert mock_post.call_count == 2 - - @pytest.mark.slow - def test_error_handling_with_retry( - self: Self, mock_tool: MagicMock, clean_env: MonkeyPatch - ) -> None: - """Test error handling decorator works with retry.""" - prompt_tool = PromptTool( - tool=mock_tool, - prompt_host="http://localhost", - prompt_port="3003", - is_public_call=False, - request_id="test-request-id", - ) - - payload = {"prompt": "test"} - - with patch("requests.post") as mock_post: - mock_post.side_effect = ConnectionError("Persistent failure") - - # Error handler should catch after all retries - result = prompt_tool.answer_prompt(payload) - - # handle_service_exceptions decorator calls stream_error_and_exit - assert result is None - prompt_tool.tool.stream_error_and_exit.assert_called() diff --git a/unstract/sdk1/tests/utils/test_retry_utils.py b/unstract/sdk1/tests/utils/test_retry_utils.py index cbf73c731a..227734414a 100644 --- a/unstract/sdk1/tests/utils/test_retry_utils.py +++ b/unstract/sdk1/tests/utils/test_retry_utils.py @@ -12,7 +12,6 @@ create_retry_decorator, is_retryable_error, retry_platform_service_call, - retry_prompt_service_call, retry_with_exponential_backoff, ) @@ -485,10 +484,6 @@ def test_retry_platform_service_call_exists(self) -> None: """Test that retry_platform_service_call decorator exists.""" assert retry_platform_service_call is not None - def test_retry_prompt_service_call_exists(self) -> None: - """Test that retry_prompt_service_call decorator exists.""" - assert retry_prompt_service_call is not None - def test_platform_service_decorator_retries_on_connection_error( self, clean_env: MonkeyPatch ) -> None: @@ -504,21 +499,6 @@ def test_platform_service_decorator_retries_on_connection_error( assert result == "success" assert mock_func.call_count == 2 - def test_prompt_service_decorator_retries_on_timeout( - self, clean_env: MonkeyPatch - ) -> None: - """Test prompt service decorator retries Timeout.""" - mock_func = Mock( - __name__="test_func", side_effect=[Timeout("Timed out"), "success"] - ) - - decorated_func = retry_prompt_service_call(mock_func) - - result = decorated_func() - - assert result == "success" - assert mock_func.call_count == 2 - class TestRetryLogging: """Tests for retry logging behavior.""" diff --git a/unstract/workflow-execution/src/unstract/workflow_execution/constants.py b/unstract/workflow-execution/src/unstract/workflow_execution/constants.py index e7b9d46167..3786706cd2 100644 --- a/unstract/workflow-execution/src/unstract/workflow_execution/constants.py +++ b/unstract/workflow-execution/src/unstract/workflow_execution/constants.py @@ -14,8 +14,6 @@ class ToolRuntimeVariable: PLATFORM_HOST = "PLATFORM_SERVICE_HOST" PLATFORM_PORT = "PLATFORM_SERVICE_PORT" PLATFORM_SERVICE_API_KEY = "PLATFORM_SERVICE_API_KEY" - PROMPT_HOST = "PROMPT_HOST" - PROMPT_PORT = "PROMPT_PORT" X2TEXT_HOST = "X2TEXT_HOST" X2TEXT_PORT = "X2TEXT_PORT" ADAPTER_LLMW_POLL_INTERVAL = "ADAPTER_LLMW_POLL_INTERVAL" diff --git a/unstract/workflow-execution/src/unstract/workflow_execution/tools_utils.py b/unstract/workflow-execution/src/unstract/workflow_execution/tools_utils.py index 6778eafe17..040c9485cb 100644 --- a/unstract/workflow-execution/src/unstract/workflow_execution/tools_utils.py +++ b/unstract/workflow-execution/src/unstract/workflow_execution/tools_utils.py @@ -44,8 +44,6 @@ def __init__( self.platform_service_port = ToolsUtils.get_env( ToolRV.PLATFORM_PORT, raise_exception=True ) - self.prompt_host = ToolsUtils.get_env(ToolRV.PROMPT_HOST, raise_exception=True) - self.prompt_port = ToolsUtils.get_env(ToolRV.PROMPT_PORT, raise_exception=True) self.x2text_host = ToolsUtils.get_env(ToolRV.X2TEXT_HOST, raise_exception=True) self.x2text_port = ToolsUtils.get_env(ToolRV.X2TEXT_PORT, raise_exception=True) self.llmw_poll_interval = ToolsUtils.get_env( @@ -232,8 +230,6 @@ def get_tool_environment_variables(self) -> dict[str, Any]: ToolRV.PLATFORM_HOST: self.platform_service_host, ToolRV.PLATFORM_PORT: self.platform_service_port, ToolRV.PLATFORM_SERVICE_API_KEY: self.platform_service_api_key, - ToolRV.PROMPT_HOST: self.prompt_host, - ToolRV.PROMPT_PORT: self.prompt_port, ToolRV.X2TEXT_HOST: self.x2text_host, ToolRV.X2TEXT_PORT: self.x2text_port, ToolRV.EXECUTION_BY_TOOL: True, diff --git a/workers/sample.env b/workers/sample.env index 5c0bce2d4b..d98bb9c986 100644 --- a/workers/sample.env +++ b/workers/sample.env @@ -254,10 +254,6 @@ NOTIFICATION_QUEUE_NAME=notifications PLATFORM_SERVICE_HOST=http://unstract-platform-service PLATFORM_SERVICE_PORT=3001 -# Prompt Service -PROMPT_HOST=http://unstract-prompt-service -PROMPT_PORT=3003 - # X2Text Service X2TEXT_HOST=http://unstract-x2text-service X2TEXT_PORT=3004 @@ -395,7 +391,6 @@ GOOGLE_OAUTH2_SECRET= # REDIS_HOST=localhost # CACHE_REDIS_HOST=localhost # PLATFORM_SERVICE_HOST=http://localhost -# PROMPT_HOST=http://localhost # X2TEXT_HOST=http://localhost # UNSTRACT_RUNNER_HOST=http://localhost # WORKFLOW_EXECUTION_FILE_STORAGE_CREDENTIALS={"provider": "minio", "credentials": {"endpoint_url": "http://localhost:9000", "key": "minio", "secret": "minio123"}}