From d57b5d76ae3e375b4134034b6263625133e774b1 Mon Sep 17 00:00:00 2001 From: dangzerong <429714019@qq.com> Date: Tue, 4 Nov 2025 16:06:36 +0800 Subject: [PATCH] v0.21.1-fastapi --- .gitignore | 2 +- Dockerfile | 204 +- Dockerfile.base | 189 - README.md | 14 +- README_id.md | 14 +- README_ja.md | 14 +- README_ko.md | 14 +- README_pt_br.md | 14 +- README_tzh.md | 14 +- README_zh.md | 16 +- admin/admin_client.py | 590 - admin/auth.py | 57 - admin/build_cli_release.sh | 47 + admin/{ => client}/README.md | 63 +- admin/client/admin_client.py | 931 + admin/client/pyproject.toml | 24 + admin/models.py | 0 admin/responses.py | 15 - admin/routes.py | 190 - admin/{ => server}/admin_server.py | 28 + admin/server/auth.py | 193 + admin/{ => server}/config.py | 31 +- admin/{ => server}/exceptions.py | 0 .../__init__.py => admin/server/models.py | 2 +- .../server/responses.py | 58 +- admin/server/roles.py | 76 + admin/server/routes.py | 371 + admin/{ => server}/services.py | 47 +- agent/canvas.py | 3 - agent/component/agent_with_tools.py | 8 +- .../advanced_ingestion_pipeline.json | 726 + agent/templates/chunk_summary.json | 493 + agent/templates/image_lingo.json | 515 +- agent/templates/stock_research_report.json | 1172 + agent/templates/title_chunker.json | 369 + agent/tools/exesql.py | 44 +- agent/tools/pubmed.py | 53 +- agent/tools/retrieval.py | 33 +- api/apps/__init___fastapi.py | 50 +- api/apps/api_app.py | 8 +- api/apps/canvas_app.py | 458 +- api/apps/chunk_app.py | 78 +- api/apps/document_app.py | 473 +- api/apps/file2document_app.py | 109 +- api/apps/file_app.py | 384 +- api/apps/kb_app.py | 399 +- api/apps/llm_app.py | 128 +- api/apps/mcp_server_app.py | 290 +- api/apps/models/auth_dependencies.py | 53 + api/apps/models/canvas_models.py | 129 + api/apps/models/chunk_models.py | 80 + api/apps/models/document_models.py | 204 + api/apps/models/kb_models.py | 159 + api/apps/models/llm_models.py | 101 + api/apps/models/mcp_models.py | 99 + api/apps/plugin_app.py | 18 + api/apps/sdk/agent.py | 5 +- api/apps/sdk/dataset.py | 41 +- api/apps/sdk/dify_retrieval.py | 109 +- api/apps/sdk/doc.py | 109 +- api/apps/sdk/files.py | 84 +- api/apps/sdk/session.py | 60 +- api/apps/system_app.py | 6 +- api/apps/tenant_app.py | 61 +- api/apps/user_app.py | 187 +- api/apps/user_app_fastapi.py | 61 +- api/common/exceptions.py | 24 +- api/db/db_models.py | 85 +- api/db/services/canvas_service.py | 9 +- api/db/services/dialog_service.py | 14 +- api/db/services/document_service.py | 30 +- api/db/services/file_service.py | 46 +- api/db/services/knowledgebase_service.py | 5 +- api/db/services/llm_service.py | 31 +- api/db/services/mcp_server_service.py | 3 +- api/db/services/search_service.py | 3 +- api/db/services/task_service.py | 21 +- api/db/services/tenant_llm_service.py | 41 +- api/db/services/user_canvas_version.py | 26 +- api/models/chunk_models.py | 88 - api/models/kb_models.py | 98 - api/models/llm_models.py | 84 - api/models/tenant_models.py | 30 - api/settings.py | 10 +- api/utils/api_utils.py | 83 +- api/utils/email_templates.py | 25 + api/utils/file_utils.py | 136 +- api/utils/health_utils.py | 34 +- api/utils/web_utils.py | 70 +- chat_demo/index.html | 19 + chat_demo/widget_demo.html | 154 + conf/infinity_mapping.json | 5 +- conf/llm_factories.json | 67 +- conf/os_mapping.json | 55 + deepdoc/README.md | 122 - deepdoc/README_zh.md | 116 - deepdoc/__init__.py | 18 - deepdoc/parser/__init__.py | 40 - deepdoc/parser/docx_parser.py | 139 - deepdoc/parser/excel_parser.py | 189 - deepdoc/parser/figure_parser.py | 105 - deepdoc/parser/html_parser.py | 214 - deepdoc/parser/json_parser.py | 179 - deepdoc/parser/markdown_parser.py | 273 - deepdoc/parser/ocr_http_client.py | 175 - deepdoc/parser/ppt_parser.py | 99 - deepdoc/parser/resume/__init__.py | 109 - .../parser/resume/entities/corporations.py | 128 - deepdoc/parser/resume/entities/degrees.py | 44 - deepdoc/parser/resume/entities/industries.py | 712 - deepdoc/parser/resume/entities/regions.py | 789 - .../resume/entities/res/corp.tks.freq.json | 65 - .../resume/entities/res/corp_baike_len.csv | 31480 ---------------- .../parser/resume/entities/res/corp_tag.json | 14939 -------- .../parser/resume/entities/res/good_corp.json | 911 - .../parser/resume/entities/res/good_sch.json | 595 - .../resume/entities/res/school.rank.csv | 1627 - .../parser/resume/entities/res/schools.csv | 5713 --- deepdoc/parser/resume/entities/schools.py | 91 - deepdoc/parser/resume/step_one.py | 189 - deepdoc/parser/resume/step_two.py | 696 - deepdoc/parser/txt_parser.py | 64 - deepdoc/parser/utils.py | 32 - deepdoc/vision/__init__.py | 90 - deepdoc/vision/layout_recognizer.py | 456 - deepdoc/vision/ocr.py | 746 - deepdoc/vision/operators.py | 725 - deepdoc/vision/postprocess.py | 370 - deepdoc/vision/recognizer.py | 442 - deepdoc/vision/seeit.py | 87 - deepdoc/vision/t_ocr.py | 93 - deepdoc/vision/t_recognizer.py | 186 - deepdoc/vision/table_structure_recognizer.py | 612 - docker/.env | 22 +- docker/README.md | 308 +- docker/docker-compose-base.yml | 172 +- docker/docker-compose.yml | 48 +- docker/entrypoint.sh | 17 +- docker/infinity_conf.toml | 2 +- docker/ragflow.sh | 63 - docker/service_conf.yaml.template | 16 +- docker/start-ragflow.sh | 27 - docker/stop-ragflow.sh | 12 - download_deps.py | 2 +- example/http/dataset_example.sh | 52 + example/sdk/dataset_example.py | 53 + graphrag/entity_resolution.py | 6 +- graphrag/general/extractor.py | 36 +- graphrag/general/index.py | 4 +- graphrag/general/smoke.py | 2 +- graphrag/light/smoke.py | 2 +- graphrag/utils.py | 19 +- helm/.helmignore | 23 + helm/Chart.yaml | 24 + helm/templates/_helpers.tpl | 62 + helm/templates/elasticsearch-config.yaml | 17 + helm/templates/elasticsearch.yaml | 131 + helm/templates/env.yaml | 53 + helm/templates/infinity.yaml | 122 + helm/templates/ingress.yaml | 43 + helm/templates/minio.yaml | 105 + helm/templates/mysql-config.yaml | 9 + helm/templates/mysql.yaml | 110 + helm/templates/opensearch-config.yaml | 18 + helm/templates/opensearch.yaml | 135 + helm/templates/ragflow.yaml | 119 + helm/templates/ragflow_config.yaml | 89 + helm/templates/redis.yaml | 133 + helm/templates/tests/test-connection.yaml | 17 + helm/values.yaml | 234 + main-ocr.py | 191 - main.py | 16 - ocr/__init__.py | 41 - ocr/api.py | 525 - ocr/client.py | 239 - ocr/config.py | 42 - ocr/ocr.py | 785 - ocr/operators.py | 726 - ocr/pdf_parser.py | 1319 - ocr/postprocess.py | 371 - ocr/requirements.txt | 25 - ocr/service.py | 290 - ocr/utils.py | 40 - pyproject.toml | 20 +- rag/app/book.py | 14 +- rag/app/manual.py | 4 +- rag/app/naive.py | 135 +- rag/app/one.py | 17 +- rag/app/paper.py | 5 +- rag/app/picture.py | 72 +- rag/app/tag.py | 4 +- rag/benchmark.py | 2 +- .../hierarchical_merger.py | 23 +- rag/flow/parser/parser.py | 97 +- rag/flow/splitter/schema.py | 2 +- rag/flow/splitter/splitter.py | 24 +- rag/flow/tokenizer/tokenizer.py | 4 +- rag/llm/chat_model.py | 239 +- rag/llm/cv_model.py | 186 +- rag/llm/sequence2txt_model.py | 48 +- rag/nlp/__init__.py | 90 +- rag/nlp/search.py | 100 +- rag/prompts/assign_toc_levels.md | 32 +- rag/prompts/generator.py | 123 +- rag/prompts/toc_from_text_system.md | 62 +- rag/prompts/toc_relevance_system.md | 118 + rag/prompts/toc_relevance_user.md | 17 + rag/raptor.py | 2 +- rag/svr/task_executor.py | 123 +- rag/utils/infinity_conn.py | 4 +- rag/utils/minio_conn.py | 44 +- rag/utils/oss_conn.py | 10 +- sdk/python/pyproject.toml | 2 +- sdk/python/ragflow_sdk/modules/dataset.py | 41 +- sdk/python/ragflow_sdk/modules/session.py | 49 +- sdk/python/uv.lock | 2 +- startup.md | 6 - uv.lock | 7785 ++++ 218 files changed, 19617 insertions(+), 72339 deletions(-) delete mode 100644 Dockerfile.base delete mode 100644 admin/admin_client.py delete mode 100644 admin/auth.py create mode 100644 admin/build_cli_release.sh rename admin/{ => client}/README.md (79%) create mode 100644 admin/client/admin_client.py create mode 100644 admin/client/pyproject.toml delete mode 100644 admin/models.py delete mode 100644 admin/responses.py delete mode 100644 admin/routes.py rename admin/{ => server}/admin_server.py (57%) create mode 100644 admin/server/auth.py rename admin/{ => server}/config.py (89%) rename admin/{ => server}/exceptions.py (100%) rename deepdoc/parser/resume/entities/__init__.py => admin/server/models.py (99%) rename deepdoc/parser/pdf_parser.py => admin/server/responses.py (63%) create mode 100644 admin/server/roles.py create mode 100644 admin/server/routes.py rename admin/{ => server}/services.py (82%) create mode 100644 agent/templates/advanced_ingestion_pipeline.json create mode 100644 agent/templates/chunk_summary.json create mode 100644 agent/templates/stock_research_report.json create mode 100644 agent/templates/title_chunker.json create mode 100644 api/apps/models/auth_dependencies.py create mode 100644 api/apps/models/canvas_models.py create mode 100644 api/apps/models/chunk_models.py create mode 100644 api/apps/models/document_models.py create mode 100644 api/apps/models/kb_models.py create mode 100644 api/apps/models/llm_models.py create mode 100644 api/apps/models/mcp_models.py delete mode 100644 api/models/chunk_models.py delete mode 100644 api/models/kb_models.py delete mode 100644 api/models/llm_models.py delete mode 100644 api/models/tenant_models.py create mode 100644 api/utils/email_templates.py create mode 100644 chat_demo/index.html create mode 100644 chat_demo/widget_demo.html delete mode 100644 deepdoc/README.md delete mode 100644 deepdoc/README_zh.md delete mode 100644 deepdoc/__init__.py delete mode 100644 deepdoc/parser/__init__.py delete mode 100644 deepdoc/parser/docx_parser.py delete mode 100644 deepdoc/parser/excel_parser.py delete mode 100644 deepdoc/parser/figure_parser.py delete mode 100644 deepdoc/parser/html_parser.py delete mode 100644 deepdoc/parser/json_parser.py delete mode 100644 deepdoc/parser/markdown_parser.py delete mode 100644 deepdoc/parser/ocr_http_client.py delete mode 100644 deepdoc/parser/ppt_parser.py delete mode 100644 deepdoc/parser/resume/__init__.py delete mode 100644 deepdoc/parser/resume/entities/corporations.py delete mode 100644 deepdoc/parser/resume/entities/degrees.py delete mode 100644 deepdoc/parser/resume/entities/industries.py delete mode 100644 deepdoc/parser/resume/entities/regions.py delete mode 100644 deepdoc/parser/resume/entities/res/corp.tks.freq.json delete mode 100644 deepdoc/parser/resume/entities/res/corp_baike_len.csv delete mode 100644 deepdoc/parser/resume/entities/res/corp_tag.json delete mode 100644 deepdoc/parser/resume/entities/res/good_corp.json delete mode 100644 deepdoc/parser/resume/entities/res/good_sch.json delete mode 100644 deepdoc/parser/resume/entities/res/school.rank.csv delete mode 100644 deepdoc/parser/resume/entities/res/schools.csv delete mode 100644 deepdoc/parser/resume/entities/schools.py delete mode 100644 deepdoc/parser/resume/step_one.py delete mode 100644 deepdoc/parser/resume/step_two.py delete mode 100644 deepdoc/parser/txt_parser.py delete mode 100644 deepdoc/parser/utils.py delete mode 100644 deepdoc/vision/__init__.py delete mode 100644 deepdoc/vision/layout_recognizer.py delete mode 100644 deepdoc/vision/ocr.py delete mode 100644 deepdoc/vision/operators.py delete mode 100644 deepdoc/vision/postprocess.py delete mode 100644 deepdoc/vision/recognizer.py delete mode 100644 deepdoc/vision/seeit.py delete mode 100644 deepdoc/vision/t_ocr.py delete mode 100644 deepdoc/vision/t_recognizer.py delete mode 100644 deepdoc/vision/table_structure_recognizer.py delete mode 100644 docker/ragflow.sh delete mode 100644 docker/start-ragflow.sh delete mode 100644 docker/stop-ragflow.sh create mode 100644 example/http/dataset_example.sh create mode 100644 example/sdk/dataset_example.py create mode 100644 helm/.helmignore create mode 100644 helm/Chart.yaml create mode 100644 helm/templates/_helpers.tpl create mode 100644 helm/templates/elasticsearch-config.yaml create mode 100644 helm/templates/elasticsearch.yaml create mode 100644 helm/templates/env.yaml create mode 100644 helm/templates/infinity.yaml create mode 100644 helm/templates/ingress.yaml create mode 100644 helm/templates/minio.yaml create mode 100644 helm/templates/mysql-config.yaml create mode 100644 helm/templates/mysql.yaml create mode 100644 helm/templates/opensearch-config.yaml create mode 100644 helm/templates/opensearch.yaml create mode 100644 helm/templates/ragflow.yaml create mode 100644 helm/templates/ragflow_config.yaml create mode 100644 helm/templates/redis.yaml create mode 100644 helm/templates/tests/test-connection.yaml create mode 100644 helm/values.yaml delete mode 100644 main-ocr.py delete mode 100644 main.py delete mode 100644 ocr/__init__.py delete mode 100644 ocr/api.py delete mode 100644 ocr/client.py delete mode 100644 ocr/config.py delete mode 100644 ocr/ocr.py delete mode 100644 ocr/operators.py delete mode 100644 ocr/pdf_parser.py delete mode 100644 ocr/postprocess.py delete mode 100644 ocr/requirements.txt delete mode 100644 ocr/service.py delete mode 100644 ocr/utils.py create mode 100644 rag/prompts/toc_relevance_system.md create mode 100644 rag/prompts/toc_relevance_user.md delete mode 100644 startup.md create mode 100644 uv.lock diff --git a/.gitignore b/.gitignore index 956cd63..fbf80b3 100644 --- a/.gitignore +++ b/.gitignore @@ -149,7 +149,7 @@ out # Nuxt.js build / generate output .nuxt dist - +ragflow_cli.egg-info # Gatsby files .cache/ # Comment in the public line in if your project uses Gatsby and not Next.js diff --git a/Dockerfile b/Dockerfile index 4524d59..4279875 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,197 @@ -# Application stage - builds on top of the base image -# First build the base image using: docker build -f Dockerfile.base -t ragflow-base:latest . -FROM ragflow-base:latest AS production +# base stage +FROM ubuntu:22.04 AS base +USER root +SHELL ["/bin/bash", "-c"] + +ARG NEED_MIRROR=0 +ARG LIGHTEN=0 +ENV LIGHTEN=${LIGHTEN} + +WORKDIR /ragflow + +# Copy models downloaded via download_deps.py +RUN mkdir -p /ragflow/rag/res/deepdoc /root/.ragflow +RUN --mount=type=bind,from=infiniflow/ragflow_deps:latest,source=/huggingface.co,target=/huggingface.co \ + cp /huggingface.co/InfiniFlow/huqie/huqie.txt.trie /ragflow/rag/res/ && \ + tar --exclude='.*' -cf - \ + /huggingface.co/InfiniFlow/text_concat_xgb_v1.0 \ + /huggingface.co/InfiniFlow/deepdoc \ + | tar -xf - --strip-components=3 -C /ragflow/rag/res/deepdoc +RUN --mount=type=bind,from=infiniflow/ragflow_deps:latest,source=/huggingface.co,target=/huggingface.co \ + if [ "$LIGHTEN" != "1" ]; then \ + (tar -cf - \ + /huggingface.co/BAAI/bge-large-zh-v1.5 \ + /huggingface.co/maidalun1020/bce-embedding-base_v1 \ + | tar -xf - --strip-components=2 -C /root/.ragflow) \ + fi + +# https://github.com/chrismattmann/tika-python +# This is the only way to run python-tika without internet access. Without this set, the default is to check the tika version and pull latest every time from Apache. +RUN --mount=type=bind,from=infiniflow/ragflow_deps:latest,source=/,target=/deps \ + cp -r /deps/nltk_data /root/ && \ + cp /deps/tika-server-standard-3.0.0.jar /deps/tika-server-standard-3.0.0.jar.md5 /ragflow/ && \ + cp /deps/cl100k_base.tiktoken /ragflow/9b5ad71b2ce5302211f9c61530b329a4922fc6a4 + +ENV TIKA_SERVER_JAR="file:///ragflow/tika-server-standard-3.0.0.jar" +ENV DEBIAN_FRONTEND=noninteractive + +# Setup apt +# Python package and implicit dependencies: +# opencv-python: libglib2.0-0 libglx-mesa0 libgl1 +# aspose-slides: pkg-config libicu-dev libgdiplus libssl1.1_1.1.1f-1ubuntu2_amd64.deb +# python-pptx: default-jdk tika-server-standard-3.0.0.jar +# selenium: libatk-bridge2.0-0 chrome-linux64-121-0-6167-85 +# Building C extensions: libpython3-dev libgtk-4-1 libnss3 xdg-utils libgbm-dev +RUN --mount=type=cache,id=ragflow_apt,target=/var/cache/apt,sharing=locked \ + if [ "$NEED_MIRROR" == "1" ]; then \ + sed -i 's|http://ports.ubuntu.com|http://mirrors.tuna.tsinghua.edu.cn|g' /etc/apt/sources.list; \ + sed -i 's|http://archive.ubuntu.com|http://mirrors.tuna.tsinghua.edu.cn|g' /etc/apt/sources.list; \ + fi; \ + rm -f /etc/apt/apt.conf.d/docker-clean && \ + echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache && \ + chmod 1777 /tmp && \ + apt update && \ + apt --no-install-recommends install -y ca-certificates && \ + apt update && \ + apt install -y libglib2.0-0 libglx-mesa0 libgl1 && \ + apt install -y pkg-config libicu-dev libgdiplus && \ + apt install -y default-jdk && \ + apt install -y libatk-bridge2.0-0 && \ + apt install -y libpython3-dev libgtk-4-1 libnss3 xdg-utils libgbm-dev && \ + apt install -y libjemalloc-dev && \ + apt install -y python3-pip pipx nginx unzip curl wget git vim less && \ + apt install -y ghostscript + +RUN if [ "$NEED_MIRROR" == "1" ]; then \ + pip3 config set global.index-url https://mirrors.aliyun.com/pypi/simple && \ + pip3 config set global.trusted-host mirrors.aliyun.com; \ + mkdir -p /etc/uv && \ + echo "[[index]]" > /etc/uv/uv.toml && \ + echo 'url = "https://mirrors.aliyun.com/pypi/simple"' >> /etc/uv/uv.toml && \ + echo "default = true" >> /etc/uv/uv.toml; \ + fi; \ + pipx install uv + +ENV PYTHONDONTWRITEBYTECODE=1 DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 +ENV PATH=/root/.local/bin:$PATH + +# nodejs 12.22 on Ubuntu 22.04 is too old +RUN --mount=type=cache,id=ragflow_apt,target=/var/cache/apt,sharing=locked \ + curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \ + apt purge -y nodejs npm cargo && \ + apt autoremove -y && \ + apt update && \ + apt install -y nodejs + +# A modern version of cargo is needed for the latest version of the Rust compiler. +RUN apt update && apt install -y curl build-essential \ + && if [ "$NEED_MIRROR" == "1" ]; then \ + # Use TUNA mirrors for rustup/rust dist files + export RUSTUP_DIST_SERVER="https://mirrors.tuna.tsinghua.edu.cn/rustup"; \ + export RUSTUP_UPDATE_ROOT="https://mirrors.tuna.tsinghua.edu.cn/rustup/rustup"; \ + echo "Using TUNA mirrors for Rustup."; \ + fi; \ + # Force curl to use HTTP/1.1 + curl --proto '=https' --tlsv1.2 --http1.1 -sSf https://sh.rustup.rs | bash -s -- -y --profile minimal \ + && echo 'export PATH="/root/.cargo/bin:${PATH}"' >> /root/.bashrc + +ENV PATH="/root/.cargo/bin:${PATH}" + +RUN cargo --version && rustc --version + +# Add msssql ODBC driver +# macOS ARM64 environment, install msodbcsql18. +# general x86_64 environment, install msodbcsql17. +RUN --mount=type=cache,id=ragflow_apt,target=/var/cache/apt,sharing=locked \ + curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - && \ + curl https://packages.microsoft.com/config/ubuntu/22.04/prod.list > /etc/apt/sources.list.d/mssql-release.list && \ + apt update && \ + arch="$(uname -m)"; \ + if [ "$arch" = "arm64" ] || [ "$arch" = "aarch64" ]; then \ + # ARM64 (macOS/Apple Silicon or Linux aarch64) + ACCEPT_EULA=Y apt install -y unixodbc-dev msodbcsql18; \ + else \ + # x86_64 or others + ACCEPT_EULA=Y apt install -y unixodbc-dev msodbcsql17; \ + fi || \ + { echo "Failed to install ODBC driver"; exit 1; } + + + +# Add dependencies of selenium +RUN --mount=type=bind,from=infiniflow/ragflow_deps:latest,source=/chrome-linux64-121-0-6167-85,target=/chrome-linux64.zip \ + unzip /chrome-linux64.zip && \ + mv chrome-linux64 /opt/chrome && \ + ln -s /opt/chrome/chrome /usr/local/bin/ +RUN --mount=type=bind,from=infiniflow/ragflow_deps:latest,source=/chromedriver-linux64-121-0-6167-85,target=/chromedriver-linux64.zip \ + unzip -j /chromedriver-linux64.zip chromedriver-linux64/chromedriver && \ + mv chromedriver /usr/local/bin/ && \ + rm -f /usr/bin/google-chrome + +# https://forum.aspose.com/t/aspose-slides-for-net-no-usable-version-of-libssl-found-with-linux-server/271344/13 +# aspose-slides on linux/arm64 is unavailable +RUN --mount=type=bind,from=infiniflow/ragflow_deps:latest,source=/,target=/deps \ + if [ "$(uname -m)" = "x86_64" ]; then \ + dpkg -i /deps/libssl1.1_1.1.1f-1ubuntu2_amd64.deb; \ + elif [ "$(uname -m)" = "aarch64" ]; then \ + dpkg -i /deps/libssl1.1_1.1.1f-1ubuntu2_arm64.deb; \ + fi + + +# builder stage +FROM base AS builder USER root WORKDIR /ragflow -# Copy application source code (these files change frequently) +# install dependencies from uv.lock file +COPY pyproject.toml uv.lock ./ + +# https://github.com/astral-sh/uv/issues/10462 +# uv records index url into uv.lock but doesn't failover among multiple indexes +RUN --mount=type=cache,id=ragflow_uv,target=/root/.cache/uv,sharing=locked \ + if [ "$NEED_MIRROR" == "1" ]; then \ + sed -i 's|pypi.org|mirrors.aliyun.com/pypi|g' uv.lock; \ + else \ + sed -i 's|mirrors.aliyun.com/pypi|pypi.org|g' uv.lock; \ + fi; \ + if [ "$LIGHTEN" == "1" ]; then \ + uv sync --python 3.10 --frozen; \ + else \ + uv sync --python 3.10 --frozen --all-extras; \ + fi + +COPY web web +COPY docs docs +RUN --mount=type=cache,id=ragflow_npm,target=/root/.npm,sharing=locked \ + cd web && npm install && npm run build + +COPY .git /ragflow/.git + +RUN version_info=$(git describe --tags --match=v* --first-parent --always); \ + if [ "$LIGHTEN" == "1" ]; then \ + version_info="$version_info slim"; \ + else \ + version_info="$version_info full"; \ + fi; \ + echo "RAGFlow version: $version_info"; \ + echo $version_info > /ragflow/VERSION + +# production stage +FROM base AS production +USER root + +WORKDIR /ragflow + +# Copy Python environment and packages +ENV VIRTUAL_ENV=/ragflow/.venv +COPY --from=builder ${VIRTUAL_ENV} ${VIRTUAL_ENV} +ENV PATH="${VIRTUAL_ENV}/bin:${PATH}" + +ENV PYTHONPATH=/ragflow/ + +COPY web web +COPY admin admin COPY api api COPY conf conf COPY deepdoc deepdoc @@ -13,14 +199,16 @@ COPY rag rag COPY agent agent COPY graphrag graphrag COPY agentic_reasoning agentic_reasoning -COPY pyproject.toml ./ +COPY pyproject.toml uv.lock ./ COPY mcp mcp COPY plugin plugin -# Copy configuration templates and entrypoint COPY docker/service_conf.yaml.template ./conf/service_conf.yaml.template COPY docker/entrypoint.sh ./ RUN chmod +x ./entrypoint*.sh -# Set the entrypoint -ENTRYPOINT ["./entrypoint.sh"] \ No newline at end of file +# Copy compiled web pages +COPY --from=builder /ragflow/web/dist /ragflow/web/dist + +COPY --from=builder /ragflow/VERSION /ragflow/VERSION +ENTRYPOINT ["./entrypoint.sh"] diff --git a/Dockerfile.base b/Dockerfile.base deleted file mode 100644 index dded3f7..0000000 --- a/Dockerfile.base +++ /dev/null @@ -1,189 +0,0 @@ -# base stage -FROM ubuntu:22.04 AS base -USER root -SHELL ["/bin/bash", "-c"] - -ARG NEED_MIRROR=0 -ARG LIGHTEN=0 -ENV LIGHTEN=${LIGHTEN} - -WORKDIR /ragflow - -# Copy models downloaded via download_deps.py -RUN mkdir -p /ragflow/rag/res/deepdoc /root/.ragflow -RUN --mount=type=bind,from=infiniflow/ragflow_deps:latest,source=/huggingface.co,target=/huggingface.co \ - cp /huggingface.co/InfiniFlow/huqie/huqie.txt.trie /ragflow/rag/res/ && \ - tar --exclude='.*' -cf - \ - /huggingface.co/InfiniFlow/text_concat_xgb_v1.0 \ - /huggingface.co/InfiniFlow/deepdoc \ - | tar -xf - --strip-components=3 -C /ragflow/rag/res/deepdoc -RUN --mount=type=bind,from=infiniflow/ragflow_deps:latest,source=/huggingface.co,target=/huggingface.co \ - if [ "$LIGHTEN" != "1" ]; then \ - (tar -cf - \ - /huggingface.co/BAAI/bge-large-zh-v1.5 \ - /huggingface.co/maidalun1020/bce-embedding-base_v1 \ - | tar -xf - --strip-components=2 -C /root/.ragflow) \ - fi - -# https://github.com/chrismattmann/tika-python -# This is the only way to run python-tika without internet access. Without this set, the default is to check the tika version and pull latest every time from Apache. -RUN --mount=type=bind,from=infiniflow/ragflow_deps:latest,source=/,target=/deps \ - cp -r /deps/nltk_data /root/ && \ - cp /deps/tika-server-standard-3.0.0.jar /deps/tika-server-standard-3.0.0.jar.md5 /ragflow/ && \ - cp /deps/cl100k_base.tiktoken /ragflow/9b5ad71b2ce5302211f9c61530b329a4922fc6a4 - -ENV TIKA_SERVER_JAR="file:///ragflow/tika-server-standard-3.0.0.jar" -ENV DEBIAN_FRONTEND=noninteractive - -# Setup apt -# Python package and implicit dependencies: -# opencv-python: libglib2.0-0 libglx-mesa0 libgl1 -# aspose-slides: pkg-config libicu-dev libgdiplus libssl1.1_1.1.1f-1ubuntu2_amd64.deb -# python-pptx: default-jdk tika-server-standard-3.0.0.jar -# selenium: libatk-bridge2.0-0 chrome-linux64-121-0-6167-85 -# Building C extensions: libpython3-dev libgtk-4-1 libnss3 xdg-utils libgbm-dev -RUN --mount=type=cache,id=ragflow_apt,target=/var/cache/apt,sharing=locked \ - if [ "$NEED_MIRROR" == "1" ]; then \ - sed -i 's|http://ports.ubuntu.com|http://mirrors.tuna.tsinghua.edu.cn|g' /etc/apt/sources.list; \ - sed -i 's|http://archive.ubuntu.com|http://mirrors.tuna.tsinghua.edu.cn|g' /etc/apt/sources.list; \ - fi; \ - rm -f /etc/apt/apt.conf.d/docker-clean && \ - echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache && \ - chmod 1777 /tmp && \ - apt update && \ - apt --no-install-recommends install -y ca-certificates && \ - apt update && \ - apt install -y libglib2.0-0 libglx-mesa0 libgl1 && \ - apt install -y pkg-config libicu-dev libgdiplus && \ - apt install -y default-jdk && \ - apt install -y libatk-bridge2.0-0 && \ - apt install -y libpython3-dev libgtk-4-1 libnss3 xdg-utils libgbm-dev && \ - apt install -y libjemalloc-dev && \ - apt install -y python3-pip pipx nginx unzip curl wget git vim less && \ - apt install -y ghostscript - -RUN if [ "$NEED_MIRROR" == "1" ]; then \ - pip3 config set global.index-url https://mirrors.aliyun.com/pypi/simple && \ - pip3 config set global.trusted-host mirrors.aliyun.com; \ - mkdir -p /etc/uv && \ - echo "[[index]]" > /etc/uv/uv.toml && \ - echo 'url = "https://mirrors.aliyun.com/pypi/simple"' >> /etc/uv/uv.toml && \ - echo "default = true" >> /etc/uv/uv.toml; \ - fi; \ - pipx install uv - -ENV PYTHONDONTWRITEBYTECODE=1 DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 -ENV PATH=/root/.local/bin:$PATH - -# nodejs 12.22 on Ubuntu 22.04 is too old -RUN --mount=type=cache,id=ragflow_apt,target=/var/cache/apt,sharing=locked \ - curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \ - apt purge -y nodejs npm cargo && \ - apt autoremove -y && \ - apt update && \ - apt install -y nodejs - -# A modern version of cargo is needed for the latest version of the Rust compiler. -RUN apt update && apt install -y curl build-essential \ - && if [ "$NEED_MIRROR" == "1" ]; then \ - # Use TUNA mirrors for rustup/rust dist files - export RUSTUP_DIST_SERVER="https://mirrors.tuna.tsinghua.edu.cn/rustup"; \ - export RUSTUP_UPDATE_ROOT="https://mirrors.tuna.tsinghua.edu.cn/rustup/rustup"; \ - echo "Using TUNA mirrors for Rustup."; \ - fi; \ - # Force curl to use HTTP/1.1 - curl --proto '=https' --tlsv1.2 --http1.1 -sSf https://sh.rustup.rs | bash -s -- -y --profile minimal \ - && echo 'export PATH="/root/.cargo/bin:${PATH}"' >> /root/.bashrc - -ENV PATH="/root/.cargo/bin:${PATH}" - -RUN cargo --version && rustc --version - -# Add msssql ODBC driver -# macOS ARM64 environment, install msodbcsql18. -# general x86_64 environment, install msodbcsql17. -RUN --mount=type=cache,id=ragflow_apt,target=/var/cache/apt,sharing=locked \ - curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - && \ - curl https://packages.microsoft.com/config/ubuntu/22.04/prod.list > /etc/apt/sources.list.d/mssql-release.list && \ - apt update && \ - arch="$(uname -m)"; \ - if [ "$arch" = "arm64" ] || [ "$arch" = "aarch64" ]; then \ - # ARM64 (macOS/Apple Silicon or Linux aarch64) - ACCEPT_EULA=Y apt install -y unixodbc-dev msodbcsql18; \ - else \ - # x86_64 or others - ACCEPT_EULA=Y apt install -y unixodbc-dev msodbcsql17; \ - fi || \ - { echo "Failed to install ODBC driver"; exit 1; } - -# Add dependencies of selenium -RUN --mount=type=bind,from=infiniflow/ragflow_deps:latest,source=/chrome-linux64-121-0-6167-85,target=/chrome-linux64.zip \ - unzip /chrome-linux64.zip && \ - mv chrome-linux64 /opt/chrome && \ - ln -s /opt/chrome/chrome /usr/local/bin/ -RUN --mount=type=bind,from=infiniflow/ragflow_deps:latest,source=/chromedriver-linux64-121-0-6167-85,target=/chromedriver-linux64.zip \ - unzip -j /chromedriver-linux64.zip chromedriver-linux64/chromedriver && \ - mv chromedriver /usr/local/bin/ && \ - rm -f /usr/bin/google-chrome - -# https://forum.aspose.com/t/aspose-slides-for-net-no-usable-version-of-libssl-found-with-linux-server/271344/13 -# aspose-slides on linux/arm64 is unavailable -RUN --mount=type=bind,from=infiniflow/ragflow_deps:latest,source=/,target=/deps \ - if [ "$(uname -m)" = "x86_64" ]; then \ - dpkg -i /deps/libssl1.1_1.1.1f-1ubuntu2_amd64.deb; \ - elif [ "$(uname -m)" = "aarch64" ]; then \ - dpkg -i /deps/libssl1.1_1.1.1f-1ubuntu2_arm64.deb; \ - fi - -# builder stage -FROM base AS builder -USER root - -WORKDIR /ragflow - -# install dependencies from uv.lock file -COPY pyproject.toml ./ -RUN uv lock --python 3.10 - -# https://github.com/astral-sh/uv/issues/10462 -# uv records index url into uv.lock but doesn't failover among multiple indexes -RUN --mount=type=cache,id=ragflow_uv,target=/root/.cache/uv,sharing=locked \ - if [ "$NEED_MIRROR" == "1" ]; then \ - sed -i 's|pypi.org|mirrors.aliyun.com/pypi|g' uv.lock; \ - else \ - sed -i 's|mirrors.aliyun.com/pypi|pypi.org|g' uv.lock; \ - fi; \ - if [ "$LIGHTEN" == "1" ]; then \ - uv sync --python 3.10 --frozen; \ - else \ - uv sync --python 3.10 --frozen --all-extras; \ - fi - -RUN --mount=type=cache,id=ragflow_npm,target=/root/.npm,sharing=locked - -COPY .git /ragflow/.git - -RUN version_info=$(git describe --tags --match=v* --first-parent --always); \ - if [ "$LIGHTEN" == "1" ]; then \ - version_info="$version_info slim"; \ - else \ - version_info="$version_info full"; \ - fi; \ - echo "RAGFlow version: $version_info"; \ - echo $version_info > /ragflow/VERSION - -# Final base image with Python environment -FROM base AS ragflow-base -USER root - -WORKDIR /ragflow - -# Copy Python environment and packages from builder -ENV VIRTUAL_ENV=/ragflow/.venv -COPY --from=builder ${VIRTUAL_ENV} ${VIRTUAL_ENV} -ENV PATH="${VIRTUAL_ENV}/bin:${PATH}" - -ENV PYTHONPATH=/ragflow/ - -# Copy version info -COPY --from=builder /ragflow/VERSION /ragflow/VERSION diff --git a/README.md b/README.md index 915000b..2730ff1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@
-ragflow logo +ragflow logo
@@ -22,7 +22,7 @@ Static Badge - docker pull infiniflow/ragflow:v0.20.5 + docker pull infiniflow/ragflow:v0.21.1 Latest Release @@ -84,8 +84,8 @@ Try our demo at [https://demo.ragflow.io](https://demo.ragflow.io). ## 🔥 Latest Updates +- 2025-10-15 Supports orchestrable ingestion pipeline. - 2025-08-08 Supports OpenAI's latest GPT-5 series models. -- 2025-08-04 Supports new models, including Kimi K2 and Grok 4. - 2025-08-01 Supports agentic workflow and MCP. - 2025-05-23 Adds a Python/JavaScript code executor component to Agent. - 2025-05-05 Supports cross-language query. @@ -135,7 +135,7 @@ releases! 🌟 ## 🔎 System Architecture
- +
## 🎬 Get Started @@ -187,7 +187,7 @@ releases! 🌟 > All Docker images are built for x86 platforms. We don't currently offer Docker images for ARM64. > If you are on an ARM64 platform, follow [this guide](https://ragflow.io/docs/dev/build_docker_image) to build a Docker image compatible with your system. - > The command below downloads the `v0.20.5-slim` edition of the RAGFlow Docker image. See the following table for descriptions of different RAGFlow editions. To download a RAGFlow edition different from `v0.20.5-slim`, update the `RAGFLOW_IMAGE` variable accordingly in **docker/.env** before using `docker compose` to start the server. For example: set `RAGFLOW_IMAGE=infiniflow/ragflow:v0.20.5` for the full edition `v0.20.5`. + > The command below downloads the `v0.21.1-slim` edition of the RAGFlow Docker image. See the following table for descriptions of different RAGFlow editions. To download a RAGFlow edition different from `v0.21.1-slim`, update the `RAGFLOW_IMAGE` variable accordingly in **docker/.env** before using `docker compose` to start the server. For example: set `RAGFLOW_IMAGE=infiniflow/ragflow:v0.21.1` for the full edition `v0.21.1`. ```bash $ cd ragflow/docker @@ -200,8 +200,8 @@ releases! 🌟 | RAGFlow image tag | Image size (GB) | Has embedding models? | Stable? | |-------------------|-----------------|-----------------------|--------------------------| - | v0.20.5 | ≈9 | :heavy_check_mark: | Stable release | - | v0.20.5-slim | ≈2 | ❌ | Stable release | + | v0.21.1 | ≈9 | :heavy_check_mark: | Stable release | + | v0.21.1-slim | ≈2 | ❌ | Stable release | | nightly | ≈9 | :heavy_check_mark: | _Unstable_ nightly build | | nightly-slim | ≈2 | ❌ | _Unstable_ nightly build | diff --git a/README_id.md b/README_id.md index e1f4cf2..3361146 100644 --- a/README_id.md +++ b/README_id.md @@ -1,6 +1,6 @@
-Logo ragflow +Logo ragflow
@@ -22,7 +22,7 @@ Lencana Daring - docker pull infiniflow/ragflow:v0.20.5 + docker pull infiniflow/ragflow:v0.21.1 Rilis Terbaru @@ -80,8 +80,8 @@ Coba demo kami di [https://demo.ragflow.io](https://demo.ragflow.io). ## 🔥 Pembaruan Terbaru +- 2025-10-15 Dukungan untuk jalur data yang terorkestrasi. - 2025-08-08 Mendukung model seri GPT-5 terbaru dari OpenAI. -- 2025-08-04 Mendukung model baru, termasuk Kimi K2 dan Grok 4. - 2025-08-01 Mendukung alur kerja agen dan MCP. - 2025-05-23 Menambahkan komponen pelaksana kode Python/JS ke Agen. - 2025-05-05 Mendukung kueri lintas bahasa. @@ -129,7 +129,7 @@ Coba demo kami di [https://demo.ragflow.io](https://demo.ragflow.io). ## 🔎 Arsitektur Sistem
- +
## 🎬 Mulai @@ -181,7 +181,7 @@ Coba demo kami di [https://demo.ragflow.io](https://demo.ragflow.io). > Semua gambar Docker dibangun untuk platform x86. Saat ini, kami tidak menawarkan gambar Docker untuk ARM64. > Jika Anda menggunakan platform ARM64, [silakan gunakan panduan ini untuk membangun gambar Docker yang kompatibel dengan sistem Anda](https://ragflow.io/docs/dev/build_docker_image). -> Perintah di bawah ini mengunduh edisi v0.20.5-slim dari gambar Docker RAGFlow. Silakan merujuk ke tabel berikut untuk deskripsi berbagai edisi RAGFlow. Untuk mengunduh edisi RAGFlow yang berbeda dari v0.20.5-slim, perbarui variabel RAGFLOW_IMAGE di docker/.env sebelum menggunakan docker compose untuk memulai server. Misalnya, atur RAGFLOW_IMAGE=infiniflow/ragflow:v0.20.5 untuk edisi lengkap v0.20.5. +> Perintah di bawah ini mengunduh edisi v0.21.1-slim dari gambar Docker RAGFlow. Silakan merujuk ke tabel berikut untuk deskripsi berbagai edisi RAGFlow. Untuk mengunduh edisi RAGFlow yang berbeda dari v0.21.1-slim, perbarui variabel RAGFLOW_IMAGE di docker/.env sebelum menggunakan docker compose untuk memulai server. Misalnya, atur RAGFLOW_IMAGE=infiniflow/ragflow:v0.21.1 untuk edisi lengkap v0.21.1. ```bash $ cd ragflow/docker @@ -194,8 +194,8 @@ $ docker compose -f docker-compose.yml up -d | RAGFlow image tag | Image size (GB) | Has embedding models? | Stable? | | ----------------- | --------------- | --------------------- | ------------------------ | -| v0.20.5 | ≈9 | :heavy_check_mark: | Stable release | -| v0.20.5-slim | ≈2 | ❌ | Stable release | +| v0.21.1 | ≈9 | :heavy_check_mark: | Stable release | +| v0.21.1-slim | ≈2 | ❌ | Stable release | | nightly | ≈9 | :heavy_check_mark: | _Unstable_ nightly build | | nightly-slim | ≈2 | ❌ | _Unstable_ nightly build | diff --git a/README_ja.md b/README_ja.md index 6f5432c..7703d69 100644 --- a/README_ja.md +++ b/README_ja.md @@ -1,6 +1,6 @@
-ragflow logo +ragflow logo
@@ -22,7 +22,7 @@ Static Badge - docker pull infiniflow/ragflow:v0.20.5 + docker pull infiniflow/ragflow:v0.21.1 Latest Release @@ -60,8 +60,8 @@ ## 🔥 最新情報 +- 2025-10-15 オーケストレーションされたデータパイプラインのサポート。 - 2025-08-08 OpenAI の最新 GPT-5 シリーズモデルをサポートします。 -- 2025-08-04 新モデル、キミK2およびGrok 4をサポート。 - 2025-08-01 エージェントワークフローとMCPをサポート。 - 2025-05-23 エージェントに Python/JS コードエグゼキュータコンポーネントを追加しました。 - 2025-05-05 言語間クエリをサポートしました。 @@ -109,7 +109,7 @@ ## 🔎 システム構成
- +
## 🎬 初期設定 @@ -160,7 +160,7 @@ > 現在、公式に提供されているすべての Docker イメージは x86 アーキテクチャ向けにビルドされており、ARM64 用の Docker イメージは提供されていません。 > ARM64 アーキテクチャのオペレーティングシステムを使用している場合は、[このドキュメント](https://ragflow.io/docs/dev/build_docker_image)を参照して Docker イメージを自分でビルドしてください。 - > 以下のコマンドは、RAGFlow Docker イメージの v0.20.5-slim エディションをダウンロードします。異なる RAGFlow エディションの説明については、以下の表を参照してください。v0.20.5-slim とは異なるエディションをダウンロードするには、docker/.env ファイルの RAGFLOW_IMAGE 変数を適宜更新し、docker compose を使用してサーバーを起動してください。例えば、完全版 v0.20.5 をダウンロードするには、RAGFLOW_IMAGE=infiniflow/ragflow:v0.20.5 と設定します。 + > 以下のコマンドは、RAGFlow Docker イメージの v0.21.1-slim エディションをダウンロードします。異なる RAGFlow エディションの説明については、以下の表を参照してください。v0.21.1-slim とは異なるエディションをダウンロードするには、docker/.env ファイルの RAGFLOW_IMAGE 変数を適宜更新し、docker compose を使用してサーバーを起動してください。例えば、完全版 v0.21.1 をダウンロードするには、RAGFLOW_IMAGE=infiniflow/ragflow:v0.21.1 と設定します。 ```bash $ cd ragflow/docker @@ -173,8 +173,8 @@ | RAGFlow image tag | Image size (GB) | Has embedding models? | Stable? | | ----------------- | --------------- | --------------------- | ------------------------ | - | v0.20.5 | ≈9 | :heavy_check_mark: | Stable release | - | v0.20.5-slim | ≈2 | ❌ | Stable release | + | v0.21.1 | ≈9 | :heavy_check_mark: | Stable release | + | v0.21.1-slim | ≈2 | ❌ | Stable release | | nightly | ≈9 | :heavy_check_mark: | _Unstable_ nightly build | | nightly-slim | ≈2 | ❌ | _Unstable_ nightly build | diff --git a/README_ko.md b/README_ko.md index c579162..c249cbf 100644 --- a/README_ko.md +++ b/README_ko.md @@ -1,6 +1,6 @@
-ragflow logo +ragflow logo
@@ -22,7 +22,7 @@ Static Badge - docker pull infiniflow/ragflow:v0.20.5 + docker pull infiniflow/ragflow:v0.21.1 Latest Release @@ -60,8 +60,8 @@ ## 🔥 업데이트 +- 2025-10-15 조정된 데이터 파이프라인 지원. - 2025-08-08 OpenAI의 최신 GPT-5 시리즈 모델을 지원합니다. -- 2025-08-04 새로운 모델인 Kimi K2와 Grok 4를 포함하여 지원합니다. - 2025-08-01 에이전트 워크플로우와 MCP를 지원합니다. - 2025-05-23 Agent에 Python/JS 코드 실행기 구성 요소를 추가합니다. - 2025-05-05 언어 간 쿼리를 지원합니다. @@ -109,7 +109,7 @@ ## 🔎 시스템 아키텍처
- +
## 🎬 시작하기 @@ -160,7 +160,7 @@ > 모든 Docker 이미지는 x86 플랫폼을 위해 빌드되었습니다. 우리는 현재 ARM64 플랫폼을 위한 Docker 이미지를 제공하지 않습니다. > ARM64 플랫폼을 사용 중이라면, [시스템과 호환되는 Docker 이미지를 빌드하려면 이 가이드를 사용해 주세요](https://ragflow.io/docs/dev/build_docker_image). - > 아래 명령어는 RAGFlow Docker 이미지의 v0.20.5-slim 버전을 다운로드합니다. 다양한 RAGFlow 버전에 대한 설명은 다음 표를 참조하십시오. v0.20.5-slim과 다른 RAGFlow 버전을 다운로드하려면, docker/.env 파일에서 RAGFLOW_IMAGE 변수를 적절히 업데이트한 후 docker compose를 사용하여 서버를 시작하십시오. 예를 들어, 전체 버전인 v0.20.5을 다운로드하려면 RAGFLOW_IMAGE=infiniflow/ragflow:v0.20.5로 설정합니다. + > 아래 명령어는 RAGFlow Docker 이미지의 v0.21.1-slim 버전을 다운로드합니다. 다양한 RAGFlow 버전에 대한 설명은 다음 표를 참조하십시오. v0.21.1-slim과 다른 RAGFlow 버전을 다운로드하려면, docker/.env 파일에서 RAGFLOW_IMAGE 변수를 적절히 업데이트한 후 docker compose를 사용하여 서버를 시작하십시오. 예를 들어, 전체 버전인 v0.21.1을 다운로드하려면 RAGFLOW_IMAGE=infiniflow/ragflow:v0.21.1로 설정합니다. ```bash $ cd ragflow/docker @@ -173,8 +173,8 @@ | RAGFlow image tag | Image size (GB) | Has embedding models? | Stable? | | ----------------- | --------------- | --------------------- | ------------------------ | - | v0.20.5 | ≈9 | :heavy_check_mark: | Stable release | - | v0.20.5-slim | ≈2 | ❌ | Stable release | + | v0.21.1 | ≈9 | :heavy_check_mark: | Stable release | + | v0.21.1-slim | ≈2 | ❌ | Stable release | | nightly | ≈9 | :heavy_check_mark: | _Unstable_ nightly build | | nightly-slim | ≈2 | ❌ | _Unstable_ nightly build | diff --git a/README_pt_br.md b/README_pt_br.md index 88c48d2..5dd2c2f 100644 --- a/README_pt_br.md +++ b/README_pt_br.md @@ -1,6 +1,6 @@
-ragflow logo +ragflow logo
@@ -22,7 +22,7 @@ Badge Estático - docker pull infiniflow/ragflow:v0.20.5 + docker pull infiniflow/ragflow:v0.21.1 Última Versão @@ -80,8 +80,8 @@ Experimente nossa demo em [https://demo.ragflow.io](https://demo.ragflow.io). ## 🔥 Últimas Atualizações +- 10-15-2025 Suporte para pipelines de dados orquestrados. - 08-08-2025 Suporta a mais recente série GPT-5 da OpenAI. -- 04-08-2025 Suporta novos modelos, incluindo Kimi K2 e Grok 4. - 01-08-2025 Suporta fluxo de trabalho agente e MCP. - 23-05-2025 Adicione o componente executor de código Python/JS ao Agente. - 05-05-2025 Suporte a consultas entre idiomas. @@ -129,7 +129,7 @@ Experimente nossa demo em [https://demo.ragflow.io](https://demo.ragflow.io). ## 🔎 Arquitetura do Sistema
- +
## 🎬 Primeiros Passos @@ -180,7 +180,7 @@ Experimente nossa demo em [https://demo.ragflow.io](https://demo.ragflow.io). > Todas as imagens Docker são construídas para plataformas x86. Atualmente, não oferecemos imagens Docker para ARM64. > Se você estiver usando uma plataforma ARM64, por favor, utilize [este guia](https://ragflow.io/docs/dev/build_docker_image) para construir uma imagem Docker compatível com o seu sistema. - > O comando abaixo baixa a edição `v0.20.5-slim` da imagem Docker do RAGFlow. Consulte a tabela a seguir para descrições de diferentes edições do RAGFlow. Para baixar uma edição do RAGFlow diferente da `v0.20.5-slim`, atualize a variável `RAGFLOW_IMAGE` conforme necessário no **docker/.env** antes de usar `docker compose` para iniciar o servidor. Por exemplo: defina `RAGFLOW_IMAGE=infiniflow/ragflow:v0.20.5` para a edição completa `v0.20.5`. + > O comando abaixo baixa a edição `v0.21.1-slim` da imagem Docker do RAGFlow. Consulte a tabela a seguir para descrições de diferentes edições do RAGFlow. Para baixar uma edição do RAGFlow diferente da `v0.21.1-slim`, atualize a variável `RAGFLOW_IMAGE` conforme necessário no **docker/.env** antes de usar `docker compose` para iniciar o servidor. Por exemplo: defina `RAGFLOW_IMAGE=infiniflow/ragflow:v0.21.1` para a edição completa `v0.21.1`. ```bash $ cd ragflow/docker @@ -193,8 +193,8 @@ Experimente nossa demo em [https://demo.ragflow.io](https://demo.ragflow.io). | Tag da imagem RAGFlow | Tamanho da imagem (GB) | Possui modelos de incorporação? | Estável? | | --------------------- | ---------------------- | ------------------------------- | ------------------------ | - | v0.20.5 | ~9 | :heavy_check_mark: | Lançamento estável | - | v0.20.5-slim | ~2 | ❌ | Lançamento estável | + | v0.21.1 | ~9 | :heavy_check_mark: | Lançamento estável | + | v0.21.1-slim | ~2 | ❌ | Lançamento estável | | nightly | ~9 | :heavy_check_mark: | _Instável_ build noturno | | nightly-slim | ~2 | ❌ | _Instável_ build noturno | diff --git a/README_tzh.md b/README_tzh.md index 4a95be1..e350d37 100644 --- a/README_tzh.md +++ b/README_tzh.md @@ -1,6 +1,6 @@
-ragflow logo +ragflow logo
@@ -22,7 +22,7 @@ Static Badge - docker pull infiniflow/ragflow:v0.20.5 + docker pull infiniflow/ragflow:v0.21.1 Latest Release @@ -83,8 +83,8 @@ ## 🔥 近期更新 +- 2025-10-15 支援可編排的資料管道。 - 2025-08-08 支援 OpenAI 最新的 GPT-5 系列模型。 -- 2025-08-04 支援 Kimi K2 和 Grok 4 等模型. - 2025-08-01 支援 agentic workflow 和 MCP - 2025-05-23 為 Agent 新增 Python/JS 程式碼執行器元件。 - 2025-05-05 支援跨語言查詢。 @@ -132,7 +132,7 @@ ## 🔎 系統架構
- +
## 🎬 快速開始 @@ -183,7 +183,7 @@ > 所有 Docker 映像檔都是為 x86 平台建置的。目前,我們不提供 ARM64 平台的 Docker 映像檔。 > 如果您使用的是 ARM64 平台,請使用 [這份指南](https://ragflow.io/docs/dev/build_docker_image) 來建置適合您系統的 Docker 映像檔。 - > 執行以下指令會自動下載 RAGFlow slim Docker 映像 `v0.20.5-slim`。請參考下表查看不同 Docker 發行版的說明。如需下載不同於 `v0.20.5-slim` 的 Docker 映像,請在執行 `docker compose` 啟動服務之前先更新 **docker/.env** 檔案內的 `RAGFLOW_IMAGE` 變數。例如,你可以透過設定 `RAGFLOW_IMAGE=infiniflow/ragflow:v0.20.5` 來下載 RAGFlow 鏡像的 `v0.20.5` 完整發行版。 + > 執行以下指令會自動下載 RAGFlow slim Docker 映像 `v0.21.1-slim`。請參考下表查看不同 Docker 發行版的說明。如需下載不同於 `v0.21.1-slim` 的 Docker 映像,請在執行 `docker compose` 啟動服務之前先更新 **docker/.env** 檔案內的 `RAGFLOW_IMAGE` 變數。例如,你可以透過設定 `RAGFLOW_IMAGE=infiniflow/ragflow:v0.21.1` 來下載 RAGFlow 鏡像的 `v0.21.1` 完整發行版。 ```bash $ cd ragflow/docker @@ -196,8 +196,8 @@ | RAGFlow image tag | Image size (GB) | Has embedding models? | Stable? | | ----------------- | --------------- | --------------------- | ------------------------ | - | v0.20.5 | ≈9 | :heavy_check_mark: | Stable release | - | v0.20.5-slim | ≈2 | ❌ | Stable release | + | v0.21.1 | ≈9 | :heavy_check_mark: | Stable release | + | v0.21.1-slim | ≈2 | ❌ | Stable release | | nightly | ≈9 | :heavy_check_mark: | _Unstable_ nightly build | | nightly-slim | ≈2 | ❌ | _Unstable_ nightly build | diff --git a/README_zh.md b/README_zh.md index ad1a001..db13fda 100644 --- a/README_zh.md +++ b/README_zh.md @@ -1,6 +1,6 @@
-ragflow logo +ragflow logo
@@ -22,7 +22,7 @@ Static Badge - docker pull infiniflow/ragflow:v0.20.5 + docker pull infiniflow/ragflow:v0.21.1 Latest Release @@ -83,8 +83,8 @@ ## 🔥 近期更新 -- 2025-08-08 支持 OpenAI 最新的 GPT-5 系列模型. -- 2025-08-04 新增对 Kimi K2 和 Grok 4 等模型的支持. +- 2025-10-15 支持可编排的数据管道。 +- 2025-08-08 支持 OpenAI 最新的 GPT-5 系列模型。 - 2025-08-01 支持 agentic workflow 和 MCP。 - 2025-05-23 Agent 新增 Python/JS 代码执行器组件。 - 2025-05-05 支持跨语言查询。 @@ -132,7 +132,7 @@ ## 🔎 系统架构
- +
## 🎬 快速开始 @@ -183,7 +183,7 @@ > 请注意,目前官方提供的所有 Docker 镜像均基于 x86 架构构建,并不提供基于 ARM64 的 Docker 镜像。 > 如果你的操作系统是 ARM64 架构,请参考[这篇文档](https://ragflow.io/docs/dev/build_docker_image)自行构建 Docker 镜像。 - > 运行以下命令会自动下载 RAGFlow slim Docker 镜像 `v0.20.5-slim`。请参考下表查看不同 Docker 发行版的描述。如需下载不同于 `v0.20.5-slim` 的 Docker 镜像,请在运行 `docker compose` 启动服务之前先更新 **docker/.env** 文件内的 `RAGFLOW_IMAGE` 变量。比如,你可以通过设置 `RAGFLOW_IMAGE=infiniflow/ragflow:v0.20.5` 来下载 RAGFlow 镜像的 `v0.20.5` 完整发行版。 + > 运行以下命令会自动下载 RAGFlow slim Docker 镜像 `v0.21.1-slim`。请参考下表查看不同 Docker 发行版的描述。如需下载不同于 `v0.21.1-slim` 的 Docker 镜像,请在运行 `docker compose` 启动服务之前先更新 **docker/.env** 文件内的 `RAGFLOW_IMAGE` 变量。比如,你可以通过设置 `RAGFLOW_IMAGE=infiniflow/ragflow:v0.21.1` 来下载 RAGFlow 镜像的 `v0.21.1` 完整发行版。 ```bash $ cd ragflow/docker @@ -196,8 +196,8 @@ | RAGFlow image tag | Image size (GB) | Has embedding models? | Stable? | | ----------------- | --------------- | --------------------- | ------------------------ | - | v0.20.5 | ≈9 | :heavy_check_mark: | Stable release | - | v0.20.5-slim | ≈2 | ❌ | Stable release | + | v0.21.1 | ≈9 | :heavy_check_mark: | Stable release | + | v0.21.1-slim | ≈2 | ❌ | Stable release | | nightly | ≈9 | :heavy_check_mark: | _Unstable_ nightly build | | nightly-slim | ≈2 | ❌ | _Unstable_ nightly build | diff --git a/admin/admin_client.py b/admin/admin_client.py deleted file mode 100644 index 007b73e..0000000 --- a/admin/admin_client.py +++ /dev/null @@ -1,590 +0,0 @@ -import argparse -import base64 - -from Cryptodome.PublicKey import RSA -from Cryptodome.Cipher import PKCS1_v1_5 as Cipher_pkcs1_v1_5 -from typing import Dict, List, Any -from lark import Lark, Transformer, Tree -import requests -from requests.auth import HTTPBasicAuth -from api.common.base64 import encode_to_base64 - -GRAMMAR = r""" -start: command - -command: sql_command | meta_command - -sql_command: list_services - | show_service - | startup_service - | shutdown_service - | restart_service - | list_users - | show_user - | drop_user - | alter_user - | create_user - | activate_user - | list_datasets - | list_agents - -// meta command definition -meta_command: "\\" meta_command_name [meta_args] - -meta_command_name: /[a-zA-Z?]+/ -meta_args: (meta_arg)+ - -meta_arg: /[^\\s"']+/ | quoted_string - -// command definition - -LIST: "LIST"i -SERVICES: "SERVICES"i -SHOW: "SHOW"i -CREATE: "CREATE"i -SERVICE: "SERVICE"i -SHUTDOWN: "SHUTDOWN"i -STARTUP: "STARTUP"i -RESTART: "RESTART"i -USERS: "USERS"i -DROP: "DROP"i -USER: "USER"i -ALTER: "ALTER"i -ACTIVE: "ACTIVE"i -PASSWORD: "PASSWORD"i -DATASETS: "DATASETS"i -OF: "OF"i -AGENTS: "AGENTS"i - -list_services: LIST SERVICES ";" -show_service: SHOW SERVICE NUMBER ";" -startup_service: STARTUP SERVICE NUMBER ";" -shutdown_service: SHUTDOWN SERVICE NUMBER ";" -restart_service: RESTART SERVICE NUMBER ";" - -list_users: LIST USERS ";" -drop_user: DROP USER quoted_string ";" -alter_user: ALTER USER PASSWORD quoted_string quoted_string ";" -show_user: SHOW USER quoted_string ";" -create_user: CREATE USER quoted_string quoted_string ";" -activate_user: ALTER USER ACTIVE quoted_string status ";" - -list_datasets: LIST DATASETS OF quoted_string ";" -list_agents: LIST AGENTS OF quoted_string ";" - -identifier: WORD -quoted_string: QUOTED_STRING -status: WORD - -QUOTED_STRING: /'[^']+'/ | /"[^"]+"/ -WORD: /[a-zA-Z0-9_\-\.]+/ -NUMBER: /[0-9]+/ - -%import common.WS -%ignore WS -""" - - -class AdminTransformer(Transformer): - - def start(self, items): - return items[0] - - def command(self, items): - return items[0] - - def list_services(self, items): - result = {'type': 'list_services'} - return result - - def show_service(self, items): - service_id = int(items[2]) - return {"type": "show_service", "number": service_id} - - def startup_service(self, items): - service_id = int(items[2]) - return {"type": "startup_service", "number": service_id} - - def shutdown_service(self, items): - service_id = int(items[2]) - return {"type": "shutdown_service", "number": service_id} - - def restart_service(self, items): - service_id = int(items[2]) - return {"type": "restart_service", "number": service_id} - - def list_users(self, items): - return {"type": "list_users"} - - def show_user(self, items): - user_name = items[2] - return {"type": "show_user", "username": user_name} - - def drop_user(self, items): - user_name = items[2] - return {"type": "drop_user", "username": user_name} - - def alter_user(self, items): - user_name = items[3] - new_password = items[4] - return {"type": "alter_user", "username": user_name, "password": new_password} - - def create_user(self, items): - user_name = items[2] - password = items[3] - return {"type": "create_user", "username": user_name, "password": password, "role": "user"} - - def activate_user(self, items): - user_name = items[3] - activate_status = items[4] - return {"type": "activate_user", "activate_status": activate_status, "username": user_name} - - def list_datasets(self, items): - user_name = items[3] - return {"type": "list_datasets", "username": user_name} - - def list_agents(self, items): - user_name = items[3] - return {"type": "list_agents", "username": user_name} - - def meta_command(self, items): - command_name = str(items[0]).lower() - args = items[1:] if len(items) > 1 else [] - - # handle quoted parameter - parsed_args = [] - for arg in args: - if hasattr(arg, 'value'): - parsed_args.append(arg.value) - else: - parsed_args.append(str(arg)) - - return {'type': 'meta', 'command': command_name, 'args': parsed_args} - - def meta_command_name(self, items): - return items[0] - - def meta_args(self, items): - return items - - -def encrypt(input_string): - pub = '-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArq9XTUSeYr2+N1h3Afl/z8Dse/2yD0ZGrKwx+EEEcdsBLca9Ynmx3nIB5obmLlSfmskLpBo0UACBmB5rEjBp2Q2f3AG3Hjd4B+gNCG6BDaawuDlgANIhGnaTLrIqWrrcm4EMzJOnAOI1fgzJRsOOUEfaS318Eq9OVO3apEyCCt0lOQK6PuksduOjVxtltDav+guVAA068NrPYmRNabVKRNLJpL8w4D44sfth5RvZ3q9t+6RTArpEtc5sh5ChzvqPOzKGMXW83C95TxmXqpbK6olN4RevSfVjEAgCydH6HN6OhtOQEcnrU97r9H0iZOWwbw3pVrZiUkuRD1R56Wzs2wIDAQAB\n-----END PUBLIC KEY-----' - pub_key = RSA.importKey(pub) - cipher = Cipher_pkcs1_v1_5.new(pub_key) - cipher_text = cipher.encrypt(base64.b64encode(input_string.encode('utf-8'))) - return base64.b64encode(cipher_text).decode("utf-8") - - -class AdminCommandParser: - def __init__(self): - self.parser = Lark(GRAMMAR, start='start', parser='lalr', transformer=AdminTransformer()) - self.command_history = [] - - def parse_command(self, command_str: str) -> Dict[str, Any]: - if not command_str.strip(): - return {'type': 'empty'} - - self.command_history.append(command_str) - - try: - result = self.parser.parse(command_str) - return result - except Exception as e: - return {'type': 'error', 'message': f'Parse error: {str(e)}'} - - -class AdminCLI: - def __init__(self): - self.parser = AdminCommandParser() - self.is_interactive = False - self.admin_account = "admin@ragflow.io" - self.admin_password: str = "admin" - self.host: str = "" - self.port: int = 0 - - def verify_admin(self, args): - - conn_info = self._parse_connection_args(args) - if 'error' in conn_info: - print(f"Error: {conn_info['error']}") - return - - self.host = conn_info['host'] - self.port = conn_info['port'] - print(f"Attempt to access ip: {self.host}, port: {self.port}") - url = f'http://{self.host}:{self.port}/api/v1/admin/auth' - - try_count = 0 - while True: - try_count += 1 - if try_count > 3: - return False - - admin_passwd = input(f"password for {self.admin_account}: ").strip() - try: - self.admin_password = encode_to_base64(admin_passwd) - response = requests.get(url, auth=HTTPBasicAuth(self.admin_account, self.admin_password)) - if response.status_code == 200: - res_json = response.json() - error_code = res_json.get('code', -1) - if error_code == 0: - print("Authentication successful.") - return True - else: - error_message = res_json.get('message', 'Unknown error') - print(f"Authentication failed: {error_message}, try again") - continue - else: - print(f"Bad response,status: {response.status_code}, try again") - except Exception: - print(f"Can't access {self.host}, port: {self.port}") - - def _print_table_simple(self, data): - if not data: - print("No data to print") - return - if isinstance(data, dict): - # handle single row data - data = [data] - - columns = list(data[0].keys()) - col_widths = {} - - for col in columns: - max_width = len(str(col)) - for item in data: - value_len = len(str(item.get(col, ''))) - if value_len > max_width: - max_width = value_len - col_widths[col] = max(2, max_width) - - # Generate delimiter - separator = "+" + "+".join(["-" * (col_widths[col] + 2) for col in columns]) + "+" - - # Print header - print(separator) - header = "|" + "|".join([f" {col:<{col_widths[col]}} " for col in columns]) + "|" - print(header) - print(separator) - - # Print data - for item in data: - row = "|" - for col in columns: - value = str(item.get(col, '')) - if len(value) > col_widths[col]: - value = value[:col_widths[col] - 3] + "..." - row += f" {value:<{col_widths[col]}} |" - print(row) - - print(separator) - - def run_interactive(self): - - self.is_interactive = True - print("RAGFlow Admin command line interface - Type '\\?' for help, '\\q' to quit") - - while True: - try: - command = input("admin> ").strip() - if not command: - continue - - print(f"command: {command}") - result = self.parser.parse_command(command) - self.execute_command(result) - - if isinstance(result, Tree): - continue - - if result.get('type') == 'meta' and result.get('command') in ['q', 'quit', 'exit']: - break - - except KeyboardInterrupt: - print("\nUse '\\q' to quit") - except EOFError: - print("\nGoodbye!") - break - - def run_single_command(self, args): - conn_info = self._parse_connection_args(args) - if 'error' in conn_info: - print(f"Error: {conn_info['error']}") - return - - def _parse_connection_args(self, args: List[str]) -> Dict[str, Any]: - parser = argparse.ArgumentParser(description='Admin CLI Client', add_help=False) - parser.add_argument('-h', '--host', default='localhost', help='Admin service host') - parser.add_argument('-p', '--port', type=int, default=8080, help='Admin service port') - - try: - parsed_args, remaining_args = parser.parse_known_args(args) - return { - 'host': parsed_args.host, - 'port': parsed_args.port, - } - except SystemExit: - return {'error': 'Invalid connection arguments'} - - def execute_command(self, parsed_command: Dict[str, Any]): - - command_dict: dict - if isinstance(parsed_command, Tree): - command_dict = parsed_command.children[0] - else: - if parsed_command['type'] == 'error': - print(f"Error: {parsed_command['message']}") - return - else: - command_dict = parsed_command - - # print(f"Parsed command: {command_dict}") - - command_type = command_dict['type'] - - match command_type: - case 'list_services': - self._handle_list_services(command_dict) - case 'show_service': - self._handle_show_service(command_dict) - case 'restart_service': - self._handle_restart_service(command_dict) - case 'shutdown_service': - self._handle_shutdown_service(command_dict) - case 'startup_service': - self._handle_startup_service(command_dict) - case 'list_users': - self._handle_list_users(command_dict) - case 'show_user': - self._handle_show_user(command_dict) - case 'drop_user': - self._handle_drop_user(command_dict) - case 'alter_user': - self._handle_alter_user(command_dict) - case 'create_user': - self._handle_create_user(command_dict) - case 'activate_user': - self._handle_activate_user(command_dict) - case 'list_datasets': - self._handle_list_datasets(command_dict) - case 'list_agents': - self._handle_list_agents(command_dict) - case 'meta': - self._handle_meta_command(command_dict) - case _: - print(f"Command '{command_type}' would be executed with API") - - def _handle_list_services(self, command): - print("Listing all services") - - url = f'http://{self.host}:{self.port}/api/v1/admin/services' - response = requests.get(url, auth=HTTPBasicAuth(self.admin_account, self.admin_password)) - res_json = response.json() - if response.status_code == 200: - self._print_table_simple(res_json['data']) - else: - print(f"Fail to get all users, code: {res_json['code']}, message: {res_json['message']}") - - def _handle_show_service(self, command): - service_id: int = command['number'] - print(f"Showing service: {service_id}") - - url = f'http://{self.host}:{self.port}/api/v1/admin/services/{service_id}' - response = requests.get(url, auth=HTTPBasicAuth(self.admin_account, self.admin_password)) - res_json = response.json() - if response.status_code == 200: - res_data = res_json['data'] - if res_data['alive']: - print(f"Service {res_data['service_name']} is alive. Detail:") - if isinstance(res_data['message'], str): - print(res_data['message']) - else: - self._print_table_simple(res_data['message']) - else: - print(f"Service {res_data['service_name']} is down. Detail: {res_data['message']}") - else: - print(f"Fail to show service, code: {res_json['code']}, message: {res_json['message']}") - - def _handle_restart_service(self, command): - service_id: int = command['number'] - print(f"Restart service {service_id}") - - def _handle_shutdown_service(self, command): - service_id: int = command['number'] - print(f"Shutdown service {service_id}") - - def _handle_startup_service(self, command): - service_id: int = command['number'] - print(f"Startup service {service_id}") - - def _handle_list_users(self, command): - print("Listing all users") - - url = f'http://{self.host}:{self.port}/api/v1/admin/users' - response = requests.get(url, auth=HTTPBasicAuth(self.admin_account, self.admin_password)) - res_json = response.json() - if response.status_code == 200: - self._print_table_simple(res_json['data']) - else: - print(f"Fail to get all users, code: {res_json['code']}, message: {res_json['message']}") - - def _handle_show_user(self, command): - username_tree: Tree = command['username'] - username: str = username_tree.children[0].strip("'\"") - print(f"Showing user: {username}") - url = f'http://{self.host}:{self.port}/api/v1/admin/users/{username}' - response = requests.get(url, auth=HTTPBasicAuth(self.admin_account, self.admin_password)) - res_json = response.json() - if response.status_code == 200: - self._print_table_simple(res_json['data']) - else: - print(f"Fail to get user {username}, code: {res_json['code']}, message: {res_json['message']}") - - def _handle_drop_user(self, command): - username_tree: Tree = command['username'] - username: str = username_tree.children[0].strip("'\"") - print(f"Drop user: {username}") - url = f'http://{self.host}:{self.port}/api/v1/admin/users/{username}' - response = requests.delete(url, auth=HTTPBasicAuth(self.admin_account, self.admin_password)) - res_json = response.json() - if response.status_code == 200: - print(res_json["message"]) - else: - print(f"Fail to drop user, code: {res_json['code']}, message: {res_json['message']}") - - def _handle_alter_user(self, command): - username_tree: Tree = command['username'] - username: str = username_tree.children[0].strip("'\"") - password_tree: Tree = command['password'] - password: str = password_tree.children[0].strip("'\"") - print(f"Alter user: {username}, password: {password}") - url = f'http://{self.host}:{self.port}/api/v1/admin/users/{username}/password' - response = requests.put(url, auth=HTTPBasicAuth(self.admin_account, self.admin_password), - json={'new_password': encrypt(password)}) - res_json = response.json() - if response.status_code == 200: - print(res_json["message"]) - else: - print(f"Fail to alter password, code: {res_json['code']}, message: {res_json['message']}") - - def _handle_create_user(self, command): - username_tree: Tree = command['username'] - username: str = username_tree.children[0].strip("'\"") - password_tree: Tree = command['password'] - password: str = password_tree.children[0].strip("'\"") - role: str = command['role'] - print(f"Create user: {username}, password: {password}, role: {role}") - url = f'http://{self.host}:{self.port}/api/v1/admin/users' - response = requests.post( - url, - auth=HTTPBasicAuth(self.admin_account, self.admin_password), - json={'username': username, 'password': encrypt(password), 'role': role} - ) - res_json = response.json() - if response.status_code == 200: - self._print_table_simple(res_json['data']) - else: - print(f"Fail to create user {username}, code: {res_json['code']}, message: {res_json['message']}") - - def _handle_activate_user(self, command): - username_tree: Tree = command['username'] - username: str = username_tree.children[0].strip("'\"") - activate_tree: Tree = command['activate_status'] - activate_status: str = activate_tree.children[0].strip("'\"") - if activate_status.lower() in ['on', 'off']: - print(f"Alter user {username} activate status, turn {activate_status.lower()}.") - url = f'http://{self.host}:{self.port}/api/v1/admin/users/{username}/activate' - response = requests.put(url, auth=HTTPBasicAuth(self.admin_account, self.admin_password), - json={'activate_status': activate_status}) - res_json = response.json() - if response.status_code == 200: - print(res_json["message"]) - else: - print(f"Fail to alter activate status, code: {res_json['code']}, message: {res_json['message']}") - else: - print(f"Unknown activate status: {activate_status}.") - - def _handle_list_datasets(self, command): - username_tree: Tree = command['username'] - username: str = username_tree.children[0].strip("'\"") - print(f"Listing all datasets of user: {username}") - url = f'http://{self.host}:{self.port}/api/v1/admin/users/{username}/datasets' - response = requests.get(url, auth=HTTPBasicAuth(self.admin_account, self.admin_password)) - res_json = response.json() - if response.status_code == 200: - self._print_table_simple(res_json['data']) - else: - print(f"Fail to get all datasets of {username}, code: {res_json['code']}, message: {res_json['message']}") - - def _handle_list_agents(self, command): - username_tree: Tree = command['username'] - username: str = username_tree.children[0].strip("'\"") - print(f"Listing all agents of user: {username}") - url = f'http://{self.host}:{self.port}/api/v1/admin/users/{username}/agents' - response = requests.get(url, auth=HTTPBasicAuth(self.admin_account, self.admin_password)) - res_json = response.json() - if response.status_code == 200: - self._print_table_simple(res_json['data']) - else: - print(f"Fail to get all agents of {username}, code: {res_json['code']}, message: {res_json['message']}") - - def _handle_meta_command(self, command): - meta_command = command['command'] - args = command.get('args', []) - - if meta_command in ['?', 'h', 'help']: - self.show_help() - elif meta_command in ['q', 'quit', 'exit']: - print("Goodbye!") - else: - print(f"Meta command '{meta_command}' with args {args}") - - def show_help(self): - """Help info""" - help_text = """ -Commands: - LIST SERVICES - SHOW SERVICE - STARTUP SERVICE - SHUTDOWN SERVICE - RESTART SERVICE - LIST USERS - SHOW USER - DROP USER - CREATE USER - ALTER USER PASSWORD - ALTER USER ACTIVE - LIST DATASETS OF - LIST AGENTS OF - -Meta Commands: - \\?, \\h, \\help Show this help - \\q, \\quit, \\exit Quit the CLI - """ - print(help_text) - - -def main(): - import sys - - cli = AdminCLI() - - if len(sys.argv) == 1 or (len(sys.argv) > 1 and sys.argv[1] == '-'): - print(r""" - ____ ___ ______________ ___ __ _ - / __ \/ | / ____/ ____/ /___ _ __ / | ____/ /___ ___ (_)___ - / /_/ / /| |/ / __/ /_ / / __ \ | /| / / / /| |/ __ / __ `__ \/ / __ \ - / _, _/ ___ / /_/ / __/ / / /_/ / |/ |/ / / ___ / /_/ / / / / / / / / / / - /_/ |_/_/ |_\____/_/ /_/\____/|__/|__/ /_/ |_\__,_/_/ /_/ /_/_/_/ /_/ - """) - if cli.verify_admin(sys.argv): - cli.run_interactive() - else: - if cli.verify_admin(sys.argv): - cli.run_interactive() - # cli.run_single_command(sys.argv[1:]) - - -if __name__ == '__main__': - main() diff --git a/admin/auth.py b/admin/auth.py deleted file mode 100644 index 001ba59..0000000 --- a/admin/auth.py +++ /dev/null @@ -1,57 +0,0 @@ -import logging -import uuid -from functools import wraps -from flask import request, jsonify - -from api.common.exceptions import AdminException -from api.db.init_data import encode_to_base64 -from api.db.services import UserService - - -def check_admin(username: str, password: str): - users = UserService.query(email=username) - if not users: - logging.info(f"Username: {username} is not registered!") - user_info = { - "id": uuid.uuid1().hex, - "password": encode_to_base64("admin"), - "nickname": "admin", - "is_superuser": True, - "email": "admin@ragflow.io", - "creator": "system", - "status": "1", - } - if not UserService.save(**user_info): - raise AdminException("Can't init admin.", 500) - - user = UserService.query_user(username, password) - if user: - return True - else: - return False - - -def login_verify(f): - @wraps(f) - def decorated(*args, **kwargs): - auth = request.authorization - if not auth or 'username' not in auth.parameters or 'password' not in auth.parameters: - return jsonify({ - "code": 401, - "message": "Authentication required", - "data": None - }), 200 - - username = auth.parameters['username'] - password = auth.parameters['password'] - # TODO: to check the username and password from DB - if check_admin(username, password) is False: - return jsonify({ - "code": 403, - "message": "Access denied", - "data": None - }), 200 - - return f(*args, **kwargs) - - return decorated diff --git a/admin/build_cli_release.sh b/admin/build_cli_release.sh new file mode 100644 index 0000000..c9fd6d9 --- /dev/null +++ b/admin/build_cli_release.sh @@ -0,0 +1,47 @@ +#!/bin/bash + +set -e + +echo "🚀 Start building..." +echo "================================" + +PROJECT_NAME="ragflow-cli" + +RELEASE_DIR="release" +BUILD_DIR="dist" +SOURCE_DIR="src" +PACKAGE_DIR="ragflow_cli" + +echo "🧹 Clean old build folder..." +rm -rf release/ + +echo "📁 Prepare source code..." +mkdir release/$PROJECT_NAME/$SOURCE_DIR -p +cp pyproject.toml release/$PROJECT_NAME/pyproject.toml +cp README.md release/$PROJECT_NAME/README.md + +mkdir release/$PROJECT_NAME/$SOURCE_DIR/$PACKAGE_DIR -p +cp admin_client.py release/$PROJECT_NAME/$SOURCE_DIR/$PACKAGE_DIR/admin_client.py + +if [ -d "release/$PROJECT_NAME/$SOURCE_DIR" ]; then + echo "✅ source dir: release/$PROJECT_NAME/$SOURCE_DIR" +else + echo "❌ source dir not exist: release/$PROJECT_NAME/$SOURCE_DIR" + exit 1 +fi + +echo "🔨 Make build file..." +cd release/$PROJECT_NAME +export PYTHONPATH=$(pwd) +python -m build + +echo "✅ check build result..." +if [ -d "$BUILD_DIR" ]; then + echo "📦 Package generated:" + ls -la $BUILD_DIR/ +else + echo "❌ Build Failed: $BUILD_DIR not exist." + exit 1 +fi + +echo "🎉 Build finished successfully!" \ No newline at end of file diff --git a/admin/README.md b/admin/client/README.md similarity index 79% rename from admin/README.md rename to admin/client/README.md index c5e1c0b..d13ff4d 100644 --- a/admin/README.md +++ b/admin/client/README.md @@ -15,22 +15,55 @@ It consists of a server-side Service and a command-line client (CLI), both imple - **Admin Service**: A backend service that interfaces with the RAGFlow system to execute administrative operations and monitor its status. - **Admin CLI**: A command-line interface that allows users to connect to the Admin Service and issue commands for system management. + + ### Starting the Admin Service -1. Before start Admin Service, please make sure RAGFlow system is already started. +#### Launching from source code + +1. Before start Admin Service, please make sure RAGFlow system is already started. + +2. Launch from source code: + + ```bash + python admin/server/admin_server.py + ``` + The service will start and listen for incoming connections from the CLI on the configured port. + +#### Using docker image + +1. Before startup, please configure the `docker_compose.yml` file to enable admin server: + + ```bash + command: + - --enable-adminserver + ``` + +2. Start the containers, the service will start and listen for incoming connections from the CLI on the configured port. + -2. Run the service script: - ```bash - python admin/admin_server.py - ``` - The service will start and listen for incoming connections from the CLI on the configured port. ### Using the Admin CLI 1. Ensure the Admin Service is running. -2. Launch the CLI client: +2. Install ragflow-cli. ```bash - python admin/admin_client.py -h 0.0.0.0 -p 9381 + pip install ragflow-cli==0.21.1 + ``` +3. Launch the CLI client: + ```bash + ragflow-cli -h 127.0.0.1 -p 9381 + ``` + You will be prompted to enter the superuser's password to log in. + The default password is admin. + + **Parameters:** + + - -h: RAGFlow admin server host address + + - -p: RAGFlow admin server port + + ## Supported Commands @@ -42,12 +75,7 @@ Commands are case-insensitive and must be terminated with a semicolon (`;`). - Lists all available services within the RAGFlow system. - `SHOW SERVICE ;` - Shows detailed status information for the service identified by ``. -- `STARTUP SERVICE ;` - - Attempts to start the service identified by ``. -- `SHUTDOWN SERVICE ;` - - Attempts to gracefully shut down the service identified by ``. -- `RESTART SERVICE ;` - - Attempts to restart the service identified by ``. + ### User Management Commands @@ -55,10 +83,17 @@ Commands are case-insensitive and must be terminated with a semicolon (`;`). - Lists all users known to the system. - `SHOW USER '';` - Shows details and permissions for the specified user. The username must be enclosed in single or double quotes. + +- `CREATE USER ;` + - Create user by username and password. The username and password must be enclosed in single or double quotes. + - `DROP USER '';` - Removes the specified user from the system. Use with caution. - `ALTER USER PASSWORD '' '';` - Changes the password for the specified user. +- `ALTER USER ACTIVE ;` + - Changes the user to active or inactive. + ### Data and Agent Commands diff --git a/admin/client/admin_client.py b/admin/client/admin_client.py new file mode 100644 index 0000000..79fea17 --- /dev/null +++ b/admin/client/admin_client.py @@ -0,0 +1,931 @@ +# +# Copyright 2025 The InfiniFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import argparse +import base64 +from cmd import Cmd + +from Cryptodome.PublicKey import RSA +from Cryptodome.Cipher import PKCS1_v1_5 as Cipher_pkcs1_v1_5 +from typing import Dict, List, Any +from lark import Lark, Transformer, Tree +import requests + +GRAMMAR = r""" +start: command + +command: sql_command | meta_command + +sql_command: list_services + | show_service + | startup_service + | shutdown_service + | restart_service + | list_users + | show_user + | drop_user + | alter_user + | create_user + | activate_user + | list_datasets + | list_agents + | create_role + | drop_role + | alter_role + | list_roles + | show_role + | grant_permission + | revoke_permission + | alter_user_role + | show_user_permission + +// meta command definition +meta_command: "\\" meta_command_name [meta_args] + +meta_command_name: /[a-zA-Z?]+/ +meta_args: (meta_arg)+ + +meta_arg: /[^\\s"']+/ | quoted_string + +// command definition + +LIST: "LIST"i +SERVICES: "SERVICES"i +SHOW: "SHOW"i +CREATE: "CREATE"i +SERVICE: "SERVICE"i +SHUTDOWN: "SHUTDOWN"i +STARTUP: "STARTUP"i +RESTART: "RESTART"i +USERS: "USERS"i +DROP: "DROP"i +USER: "USER"i +ALTER: "ALTER"i +ACTIVE: "ACTIVE"i +PASSWORD: "PASSWORD"i +DATASETS: "DATASETS"i +OF: "OF"i +AGENTS: "AGENTS"i +ROLE: "ROLE"i +ROLES: "ROLES"i +DESCRIPTION: "DESCRIPTION"i +GRANT: "GRANT"i +REVOKE: "REVOKE"i +ALL: "ALL"i +PERMISSION: "PERMISSION"i +TO: "TO"i +FROM: "FROM"i +FOR: "FOR"i +RESOURCES: "RESOURCES"i +ON: "ON"i +SET: "SET"i + +list_services: LIST SERVICES ";" +show_service: SHOW SERVICE NUMBER ";" +startup_service: STARTUP SERVICE NUMBER ";" +shutdown_service: SHUTDOWN SERVICE NUMBER ";" +restart_service: RESTART SERVICE NUMBER ";" + +list_users: LIST USERS ";" +drop_user: DROP USER quoted_string ";" +alter_user: ALTER USER PASSWORD quoted_string quoted_string ";" +show_user: SHOW USER quoted_string ";" +create_user: CREATE USER quoted_string quoted_string ";" +activate_user: ALTER USER ACTIVE quoted_string status ";" + +list_datasets: LIST DATASETS OF quoted_string ";" +list_agents: LIST AGENTS OF quoted_string ";" + +create_role: CREATE ROLE identifier [DESCRIPTION quoted_string] ";" +drop_role: DROP ROLE identifier ";" +alter_role: ALTER ROLE identifier SET DESCRIPTION quoted_string ";" +list_roles: LIST ROLES ";" +show_role: SHOW ROLE identifier ";" + +grant_permission: GRANT action_list ON identifier TO ROLE identifier ";" +revoke_permission: REVOKE action_list ON identifier FROM ROLE identifier ";" +alter_user_role: ALTER USER quoted_string SET ROLE identifier ";" +show_user_permission: SHOW USER PERMISSION quoted_string ";" + +action_list: identifier ("," identifier)* + +identifier: WORD +quoted_string: QUOTED_STRING +status: WORD + +QUOTED_STRING: /'[^']+'/ | /"[^"]+"/ +WORD: /[a-zA-Z0-9_\-\.]+/ +NUMBER: /[0-9]+/ + +%import common.WS +%ignore WS +""" + + +class AdminTransformer(Transformer): + + def start(self, items): + return items[0] + + def command(self, items): + return items[0] + + def list_services(self, items): + result = {'type': 'list_services'} + return result + + def show_service(self, items): + service_id = int(items[2]) + return {"type": "show_service", "number": service_id} + + def startup_service(self, items): + service_id = int(items[2]) + return {"type": "startup_service", "number": service_id} + + def shutdown_service(self, items): + service_id = int(items[2]) + return {"type": "shutdown_service", "number": service_id} + + def restart_service(self, items): + service_id = int(items[2]) + return {"type": "restart_service", "number": service_id} + + def list_users(self, items): + return {"type": "list_users"} + + def show_user(self, items): + user_name = items[2] + return {"type": "show_user", "user_name": user_name} + + def drop_user(self, items): + user_name = items[2] + return {"type": "drop_user", "user_name": user_name} + + def alter_user(self, items): + user_name = items[3] + new_password = items[4] + return {"type": "alter_user", "user_name": user_name, "password": new_password} + + def create_user(self, items): + user_name = items[2] + password = items[3] + return {"type": "create_user", "user_name": user_name, "password": password, "role": "user"} + + def activate_user(self, items): + user_name = items[3] + activate_status = items[4] + return {"type": "activate_user", "activate_status": activate_status, "user_name": user_name} + + def list_datasets(self, items): + user_name = items[3] + return {"type": "list_datasets", "user_name": user_name} + + def list_agents(self, items): + user_name = items[3] + return {"type": "list_agents", "user_name": user_name} + + def create_role(self, items): + role_name = items[2] + if len(items) > 4: + description = items[4] + return {"type": "create_role", "role_name": role_name, "description": description} + else: + return {"type": "create_role", "role_name": role_name} + + def drop_role(self, items): + role_name = items[2] + return {"type": "drop_role", "role_name": role_name} + + def alter_role(self, items): + role_name = items[2] + description = items[5] + return {"type": "alter_role", "role_name": role_name, "description": description} + + def list_roles(self, items): + return {"type": "list_roles"} + + def show_role(self, items): + role_name = items[2] + return {"type": "show_role", "role_name": role_name} + + def grant_permission(self, items): + action_list = items[1] + resource = items[3] + role_name = items[6] + return {"type": "grant_permission", "role_name": role_name, "resource": resource, "actions": action_list} + + def revoke_permission(self, items): + action_list = items[1] + resource = items[3] + role_name = items[6] + return { + "type": "revoke_permission", + "role_name": role_name, + "resource": resource, "actions": action_list + } + + def alter_user_role(self, items): + user_name = items[2] + role_name = items[5] + return {"type": "alter_user_role", "user_name": user_name, "role_name": role_name} + + def show_user_permission(self, items): + user_name = items[3] + return {"type": "show_user_permission", "user_name": user_name} + + def action_list(self, items): + return items + + def meta_command(self, items): + command_name = str(items[0]).lower() + args = items[1:] if len(items) > 1 else [] + + # handle quoted parameter + parsed_args = [] + for arg in args: + if hasattr(arg, 'value'): + parsed_args.append(arg.value) + else: + parsed_args.append(str(arg)) + + return {'type': 'meta', 'command': command_name, 'args': parsed_args} + + def meta_command_name(self, items): + return items[0] + + def meta_args(self, items): + return items + + +def encrypt(input_string): + pub = '-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArq9XTUSeYr2+N1h3Afl/z8Dse/2yD0ZGrKwx+EEEcdsBLca9Ynmx3nIB5obmLlSfmskLpBo0UACBmB5rEjBp2Q2f3AG3Hjd4B+gNCG6BDaawuDlgANIhGnaTLrIqWrrcm4EMzJOnAOI1fgzJRsOOUEfaS318Eq9OVO3apEyCCt0lOQK6PuksduOjVxtltDav+guVAA068NrPYmRNabVKRNLJpL8w4D44sfth5RvZ3q9t+6RTArpEtc5sh5ChzvqPOzKGMXW83C95TxmXqpbK6olN4RevSfVjEAgCydH6HN6OhtOQEcnrU97r9H0iZOWwbw3pVrZiUkuRD1R56Wzs2wIDAQAB\n-----END PUBLIC KEY-----' + pub_key = RSA.importKey(pub) + cipher = Cipher_pkcs1_v1_5.new(pub_key) + cipher_text = cipher.encrypt(base64.b64encode(input_string.encode('utf-8'))) + return base64.b64encode(cipher_text).decode("utf-8") + + +def encode_to_base64(input_string): + base64_encoded = base64.b64encode(input_string.encode('utf-8')) + return base64_encoded.decode('utf-8') + + +class AdminCLI(Cmd): + def __init__(self): + super().__init__() + self.parser = Lark(GRAMMAR, start='start', parser='lalr', transformer=AdminTransformer()) + self.command_history = [] + self.is_interactive = False + self.admin_account = "admin@ragflow.io" + self.admin_password: str = "admin" + self.session = requests.Session() + self.access_token: str = "" + self.host: str = "" + self.port: int = 0 + + intro = r"""Type "\h" for help.""" + prompt = "admin> " + + def onecmd(self, command: str) -> bool: + try: + result = self.parse_command(command) + + if isinstance(result, dict): + if 'type' in result and result.get('type') == 'empty': + return False + + self.execute_command(result) + + if isinstance(result, Tree): + return False + + if result.get('type') == 'meta' and result.get('command') in ['q', 'quit', 'exit']: + return True + + except KeyboardInterrupt: + print("\nUse '\\q' to quit") + except EOFError: + print("\nGoodbye!") + return True + return False + + def emptyline(self) -> bool: + return False + + def default(self, line: str) -> bool: + return self.onecmd(line) + + def parse_command(self, command_str: str) -> dict[str, str]: + if not command_str.strip(): + return {'type': 'empty'} + + self.command_history.append(command_str) + + try: + result = self.parser.parse(command_str) + return result + except Exception as e: + return {'type': 'error', 'message': f'Parse error: {str(e)}'} + + def verify_admin(self, arguments: dict, single_command: bool): + self.host = arguments['host'] + self.port = arguments['port'] + print(f"Attempt to access ip: {self.host}, port: {self.port}") + url = f"http://{self.host}:{self.port}/api/v1/admin/login" + + attempt_count = 3 + if single_command: + attempt_count = 1 + + try_count = 0 + while True: + try_count += 1 + if try_count > attempt_count: + return False + + if single_command: + admin_passwd = arguments['password'] + else: + admin_passwd = input(f"password for {self.admin_account}: ").strip() + try: + self.admin_password = encrypt(admin_passwd) + response = self.session.post(url, json={'email': self.admin_account, 'password': self.admin_password}) + if response.status_code == 200: + res_json = response.json() + error_code = res_json.get('code', -1) + if error_code == 0: + self.session.headers.update({ + 'Content-Type': 'application/json', + 'Authorization': response.headers['Authorization'], + 'User-Agent': 'RAGFlow-CLI/0.21.1' + }) + print("Authentication successful.") + return True + else: + error_message = res_json.get('message', 'Unknown error') + print(f"Authentication failed: {error_message}, try again") + continue + else: + print(f"Bad response,status: {response.status_code}, password is wrong") + except Exception as e: + print(str(e)) + print(f"Can't access {self.host}, port: {self.port}") + + def _print_table_simple(self, data): + if not data: + print("No data to print") + return + if isinstance(data, dict): + # handle single row data + data = [data] + + columns = list(data[0].keys()) + col_widths = {} + + def get_string_width(text): + half_width_chars = ( + " !\"#$%&'()*+,-./0123456789:;<=>?@" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`" + "abcdefghijklmnopqrstuvwxyz{|}~" + "\t\n\r" + ) + width = 0 + for char in text: + if char in half_width_chars: + width += 1 + else: + width += 2 + return width + + for col in columns: + max_width = get_string_width(str(col)) + for item in data: + value_len = get_string_width(str(item.get(col, ''))) + if value_len > max_width: + max_width = value_len + col_widths[col] = max(2, max_width) + + # Generate delimiter + separator = "+" + "+".join(["-" * (col_widths[col] + 2) for col in columns]) + "+" + + # Print header + print(separator) + header = "|" + "|".join([f" {col:<{col_widths[col]}} " for col in columns]) + "|" + print(header) + print(separator) + + # Print data + for item in data: + row = "|" + for col in columns: + value = str(item.get(col, '')) + if get_string_width(value) > col_widths[col]: + value = value[:col_widths[col] - 3] + "..." + row += f" {value:<{col_widths[col] - (get_string_width(value) - len(value))}} |" + print(row) + + print(separator) + + def run_interactive(self): + + self.is_interactive = True + print("RAGFlow Admin command line interface - Type '\\?' for help, '\\q' to quit") + + while True: + try: + command = input("admin> ").strip() + if not command: + continue + + print(f"command: {command}") + result = self.parse_command(command) + self.execute_command(result) + + if isinstance(result, Tree): + continue + + if result.get('type') == 'meta' and result.get('command') in ['q', 'quit', 'exit']: + break + + except KeyboardInterrupt: + print("\nUse '\\q' to quit") + except EOFError: + print("\nGoodbye!") + break + + def run_single_command(self, command: str): + result = self.parse_command(command) + self.execute_command(result) + + def parse_connection_args(self, args: List[str]) -> Dict[str, Any]: + parser = argparse.ArgumentParser(description='Admin CLI Client', add_help=False) + parser.add_argument('-h', '--host', default='localhost', help='Admin service host') + parser.add_argument('-p', '--port', type=int, default=8080, help='Admin service port') + parser.add_argument('-w', '--password', default='admin', type=str, help='Superuser password') + parser.add_argument('command', nargs='?', help='Single command') + try: + parsed_args, remaining_args = parser.parse_known_args(args) + if remaining_args: + command = remaining_args[0] + return { + 'host': parsed_args.host, + 'port': parsed_args.port, + 'password': parsed_args.password, + 'command': command + } + else: + return { + 'host': parsed_args.host, + 'port': parsed_args.port, + } + except SystemExit: + return {'error': 'Invalid connection arguments'} + + def execute_command(self, parsed_command: Dict[str, Any]): + + command_dict: dict + if isinstance(parsed_command, Tree): + command_dict = parsed_command.children[0] + else: + if parsed_command['type'] == 'error': + print(f"Error: {parsed_command['message']}") + return + else: + command_dict = parsed_command + + # print(f"Parsed command: {command_dict}") + + command_type = command_dict['type'] + + match command_type: + case 'list_services': + self._handle_list_services(command_dict) + case 'show_service': + self._handle_show_service(command_dict) + case 'restart_service': + self._handle_restart_service(command_dict) + case 'shutdown_service': + self._handle_shutdown_service(command_dict) + case 'startup_service': + self._handle_startup_service(command_dict) + case 'list_users': + self._handle_list_users(command_dict) + case 'show_user': + self._handle_show_user(command_dict) + case 'drop_user': + self._handle_drop_user(command_dict) + case 'alter_user': + self._handle_alter_user(command_dict) + case 'create_user': + self._handle_create_user(command_dict) + case 'activate_user': + self._handle_activate_user(command_dict) + case 'list_datasets': + self._handle_list_datasets(command_dict) + case 'list_agents': + self._handle_list_agents(command_dict) + case 'create_role': + self._create_role(command_dict) + case 'drop_role': + self._drop_role(command_dict) + case 'alter_role': + self._alter_role(command_dict) + case 'list_roles': + self._list_roles(command_dict) + case 'show_role': + self._show_role(command_dict) + case 'grant_permission': + self._grant_permission(command_dict) + case 'revoke_permission': + self._revoke_permission(command_dict) + case 'alter_user_role': + self._alter_user_role(command_dict) + case 'show_user_permission': + self._show_user_permission(command_dict) + case 'meta': + self._handle_meta_command(command_dict) + case _: + print(f"Command '{command_type}' would be executed with API") + + def _handle_list_services(self, command): + print("Listing all services") + + url = f'http://{self.host}:{self.port}/api/v1/admin/services' + response = self.session.get(url) + res_json = response.json() + if response.status_code == 200: + self._print_table_simple(res_json['data']) + else: + print(f"Fail to get all services, code: {res_json['code']}, message: {res_json['message']}") + + def _handle_show_service(self, command): + service_id: int = command['number'] + print(f"Showing service: {service_id}") + + url = f'http://{self.host}:{self.port}/api/v1/admin/services/{service_id}' + response = self.session.get(url) + res_json = response.json() + if response.status_code == 200: + res_data = res_json['data'] + if 'status' in res_data and res_data['status'] == 'alive': + print(f"Service {res_data['service_name']} is alive, ") + if isinstance(res_data['message'], str): + print(res_data['message']) + else: + self._print_table_simple(res_data['message']) + else: + print(f"Service {res_data['service_name']} is down, {res_data['message']}") + else: + print(f"Fail to show service, code: {res_json['code']}, message: {res_json['message']}") + + def _handle_restart_service(self, command): + service_id: int = command['number'] + print(f"Restart service {service_id}") + + def _handle_shutdown_service(self, command): + service_id: int = command['number'] + print(f"Shutdown service {service_id}") + + def _handle_startup_service(self, command): + service_id: int = command['number'] + print(f"Startup service {service_id}") + + def _handle_list_users(self, command): + print("Listing all users") + + url = f'http://{self.host}:{self.port}/api/v1/admin/users' + response = self.session.get(url) + res_json = response.json() + if response.status_code == 200: + self._print_table_simple(res_json['data']) + else: + print(f"Fail to get all users, code: {res_json['code']}, message: {res_json['message']}") + + def _handle_show_user(self, command): + username_tree: Tree = command['user_name'] + user_name: str = username_tree.children[0].strip("'\"") + print(f"Showing user: {user_name}") + url = f'http://{self.host}:{self.port}/api/v1/admin/users/{user_name}' + response = self.session.get(url) + res_json = response.json() + if response.status_code == 200: + self._print_table_simple(res_json['data']) + else: + print(f"Fail to get user {user_name}, code: {res_json['code']}, message: {res_json['message']}") + + def _handle_drop_user(self, command): + username_tree: Tree = command['user_name'] + user_name: str = username_tree.children[0].strip("'\"") + print(f"Drop user: {user_name}") + url = f'http://{self.host}:{self.port}/api/v1/admin/users/{user_name}' + response = self.session.delete(url) + res_json = response.json() + if response.status_code == 200: + print(res_json["message"]) + else: + print(f"Fail to drop user, code: {res_json['code']}, message: {res_json['message']}") + + def _handle_alter_user(self, command): + user_name_tree: Tree = command['user_name'] + user_name: str = user_name_tree.children[0].strip("'\"") + password_tree: Tree = command['password'] + password: str = password_tree.children[0].strip("'\"") + print(f"Alter user: {user_name}, password: {password}") + url = f'http://{self.host}:{self.port}/api/v1/admin/users/{user_name}/password' + response = self.session.put(url, json={'new_password': encrypt(password)}) + res_json = response.json() + if response.status_code == 200: + print(res_json["message"]) + else: + print(f"Fail to alter password, code: {res_json['code']}, message: {res_json['message']}") + + def _handle_create_user(self, command): + user_name_tree: Tree = command['user_name'] + user_name: str = user_name_tree.children[0].strip("'\"") + password_tree: Tree = command['password'] + password: str = password_tree.children[0].strip("'\"") + role: str = command['role'] + print(f"Create user: {user_name}, password: {password}, role: {role}") + url = f'http://{self.host}:{self.port}/api/v1/admin/users' + response = self.session.post( + url, + json={'user_name': user_name, 'password': encrypt(password), 'role': role} + ) + res_json = response.json() + if response.status_code == 200: + self._print_table_simple(res_json['data']) + else: + print(f"Fail to create user {user_name}, code: {res_json['code']}, message: {res_json['message']}") + + def _handle_activate_user(self, command): + user_name_tree: Tree = command['user_name'] + user_name: str = user_name_tree.children[0].strip("'\"") + activate_tree: Tree = command['activate_status'] + activate_status: str = activate_tree.children[0].strip("'\"") + if activate_status.lower() in ['on', 'off']: + print(f"Alter user {user_name} activate status, turn {activate_status.lower()}.") + url = f'http://{self.host}:{self.port}/api/v1/admin/users/{user_name}/activate' + response = self.session.put(url, json={'activate_status': activate_status}) + res_json = response.json() + if response.status_code == 200: + print(res_json["message"]) + else: + print(f"Fail to alter activate status, code: {res_json['code']}, message: {res_json['message']}") + else: + print(f"Unknown activate status: {activate_status}.") + + def _handle_list_datasets(self, command): + username_tree: Tree = command['user_name'] + user_name: str = username_tree.children[0].strip("'\"") + print(f"Listing all datasets of user: {user_name}") + url = f'http://{self.host}:{self.port}/api/v1/admin/users/{user_name}/datasets' + response = self.session.get(url) + res_json = response.json() + if response.status_code == 200: + self._print_table_simple(res_json['data']) + else: + print(f"Fail to get all datasets of {user_name}, code: {res_json['code']}, message: {res_json['message']}") + + def _handle_list_agents(self, command): + username_tree: Tree = command['user_name'] + user_name: str = username_tree.children[0].strip("'\"") + print(f"Listing all agents of user: {user_name}") + url = f'http://{self.host}:{self.port}/api/v1/admin/users/{user_name}/agents' + response = self.session.get(url) + res_json = response.json() + if response.status_code == 200: + self._print_table_simple(res_json['data']) + else: + print(f"Fail to get all agents of {user_name}, code: {res_json['code']}, message: {res_json['message']}") + + def _create_role(self, command): + role_name_tree: Tree = command['role_name'] + role_name: str = role_name_tree.children[0].strip("'\"") + desc_str: str = '' + if 'description' in command: + desc_tree: Tree = command['description'] + desc_str = desc_tree.children[0].strip("'\"") + + print(f"create role name: {role_name}, description: {desc_str}") + url = f'http://{self.host}:{self.port}/api/v1/admin/roles' + response = self.session.post( + url, + json={'role_name': role_name, 'description': desc_str} + ) + res_json = response.json() + if response.status_code == 200: + self._print_table_simple(res_json['data']) + else: + print(f"Fail to create role {role_name}, code: {res_json['code']}, message: {res_json['message']}") + + def _drop_role(self, command): + role_name_tree: Tree = command['role_name'] + role_name: str = role_name_tree.children[0].strip("'\"") + print(f"drop role name: {role_name}") + url = f'http://{self.host}:{self.port}/api/v1/admin/roles/{role_name}' + response = self.session.delete(url) + res_json = response.json() + if response.status_code == 200: + self._print_table_simple(res_json['data']) + else: + print(f"Fail to drop role {role_name}, code: {res_json['code']}, message: {res_json['message']}") + + def _alter_role(self, command): + role_name_tree: Tree = command['role_name'] + role_name: str = role_name_tree.children[0].strip("'\"") + desc_tree: Tree = command['description'] + desc_str: str = desc_tree.children[0].strip("'\"") + + print(f"alter role name: {role_name}, description: {desc_str}") + url = f'http://{self.host}:{self.port}/api/v1/admin/roles/{role_name}' + response = self.session.put( + url, + json={'description': desc_str} + ) + res_json = response.json() + if response.status_code == 200: + self._print_table_simple(res_json['data']) + else: + print( + f"Fail to update role {role_name} with description: {desc_str}, code: {res_json['code']}, message: {res_json['message']}") + + def _list_roles(self, command): + print("Listing all roles") + url = f'http://{self.host}:{self.port}/api/v1/admin/roles' + response = self.session.get(url) + res_json = response.json() + if response.status_code == 200: + self._print_table_simple(res_json['data']) + else: + print(f"Fail to list roles, code: {res_json['code']}, message: {res_json['message']}") + + def _show_role(self, command): + role_name_tree: Tree = command['role_name'] + role_name: str = role_name_tree.children[0].strip("'\"") + print(f"show role: {role_name}") + url = f'http://{self.host}:{self.port}/api/v1/admin/roles/{role_name}/permission' + response = self.session.get(url) + res_json = response.json() + if response.status_code == 200: + self._print_table_simple(res_json['data']) + else: + print(f"Fail to list roles, code: {res_json['code']}, message: {res_json['message']}") + + def _grant_permission(self, command): + role_name_tree: Tree = command['role_name'] + role_name_str: str = role_name_tree.children[0].strip("'\"") + resource_tree: Tree = command['resource'] + resource_str: str = resource_tree.children[0].strip("'\"") + action_tree_list: list = command['actions'] + actions: list = [] + for action_tree in action_tree_list: + action_str: str = action_tree.children[0].strip("'\"") + actions.append(action_str) + print(f"grant role_name: {role_name_str}, resource: {resource_str}, actions: {actions}") + url = f'http://{self.host}:{self.port}/api/v1/admin/roles/{role_name_str}/permission' + response = self.session.post( + url, + json={'actions': actions, 'resource': resource_str} + ) + res_json = response.json() + if response.status_code == 200: + self._print_table_simple(res_json['data']) + else: + print( + f"Fail to grant role {role_name_str} with {actions} on {resource_str}, code: {res_json['code']}, message: {res_json['message']}") + + def _revoke_permission(self, command): + role_name_tree: Tree = command['role_name'] + role_name_str: str = role_name_tree.children[0].strip("'\"") + resource_tree: Tree = command['resource'] + resource_str: str = resource_tree.children[0].strip("'\"") + action_tree_list: list = command['actions'] + actions: list = [] + for action_tree in action_tree_list: + action_str: str = action_tree.children[0].strip("'\"") + actions.append(action_str) + print(f"revoke role_name: {role_name_str}, resource: {resource_str}, actions: {actions}") + url = f'http://{self.host}:{self.port}/api/v1/admin/roles/{role_name_str}/permission' + response = self.session.delete( + url, + json={'actions': actions, 'resource': resource_str} + ) + res_json = response.json() + if response.status_code == 200: + self._print_table_simple(res_json['data']) + else: + print( + f"Fail to revoke role {role_name_str} with {actions} on {resource_str}, code: {res_json['code']}, message: {res_json['message']}") + + def _alter_user_role(self, command): + role_name_tree: Tree = command['role_name'] + role_name_str: str = role_name_tree.children[0].strip("'\"") + user_name_tree: Tree = command['user_name'] + user_name_str: str = user_name_tree.children[0].strip("'\"") + print(f"alter_user_role user_name: {user_name_str}, role_name: {role_name_str}") + url = f'http://{self.host}:{self.port}/api/v1/admin/users/{user_name_str}/role' + response = self.session.put( + url, + json={'role_name': role_name_str} + ) + res_json = response.json() + if response.status_code == 200: + self._print_table_simple(res_json['data']) + else: + print( + f"Fail to alter user: {user_name_str} to role {role_name_str}, code: {res_json['code']}, message: {res_json['message']}") + + def _show_user_permission(self, command): + user_name_tree: Tree = command['user_name'] + user_name_str: str = user_name_tree.children[0].strip("'\"") + print(f"show_user_permission user_name: {user_name_str}") + url = f'http://{self.host}:{self.port}/api/v1/admin/users/{user_name_str}/permission' + response = self.session.get(url) + res_json = response.json() + if response.status_code == 200: + self._print_table_simple(res_json['data']) + else: + print( + f"Fail to show user: {user_name_str} permission, code: {res_json['code']}, message: {res_json['message']}") + + def _handle_meta_command(self, command): + meta_command = command['command'] + args = command.get('args', []) + + if meta_command in ['?', 'h', 'help']: + self.show_help() + elif meta_command in ['q', 'quit', 'exit']: + print("Goodbye!") + else: + print(f"Meta command '{meta_command}' with args {args}") + + def show_help(self): + """Help info""" + help_text = """ +Commands: + LIST SERVICES + SHOW SERVICE + STARTUP SERVICE + SHUTDOWN SERVICE + RESTART SERVICE + LIST USERS + SHOW USER + DROP USER + CREATE USER + ALTER USER PASSWORD + ALTER USER ACTIVE + LIST DATASETS OF + LIST AGENTS OF + +Meta Commands: + \\?, \\h, \\help Show this help + \\q, \\quit, \\exit Quit the CLI + """ + print(help_text) + + +def main(): + import sys + + cli = AdminCLI() + + args = cli.parse_connection_args(sys.argv) + if 'error' in args: + print(f"Error: {args['error']}") + return + + if 'command' in args: + if 'password' not in args: + print("Error: password is missing") + return + if cli.verify_admin(args, single_command=True): + command: str = args['command'] + print(f"Run single command: {command}") + cli.run_single_command(command) + else: + if cli.verify_admin(args, single_command=False): + print(r""" + ____ ___ ______________ ___ __ _ + / __ \/ | / ____/ ____/ /___ _ __ / | ____/ /___ ___ (_)___ + / /_/ / /| |/ / __/ /_ / / __ \ | /| / / / /| |/ __ / __ `__ \/ / __ \ + / _, _/ ___ / /_/ / __/ / / /_/ / |/ |/ / / ___ / /_/ / / / / / / / / / / + /_/ |_/_/ |_\____/_/ /_/\____/|__/|__/ /_/ |_\__,_/_/ /_/ /_/_/_/ /_/ + """) + cli.cmdloop() + + +if __name__ == '__main__': + main() diff --git a/admin/client/pyproject.toml b/admin/client/pyproject.toml new file mode 100644 index 0000000..f967cd5 --- /dev/null +++ b/admin/client/pyproject.toml @@ -0,0 +1,24 @@ +[project] +name = "ragflow-cli" +version = "0.21.1" +description = "Admin Service's client of [RAGFlow](https://github.com/infiniflow/ragflow). The Admin Service provides user management and system monitoring. " +authors = [{ name = "Lynn", email = "lynn_inf@hotmail.com" }] +license = { text = "Apache License, Version 2.0" } +readme = "README.md" +requires-python = ">=3.10,<3.13" +dependencies = [ + "requests>=2.30.0,<3.0.0", + "beartype>=0.18.5,<0.19.0", + "pycryptodomex>=3.10.0", + "lark>=1.1.0", +] + +[dependency-groups] +test = [ + "pytest>=8.3.5", + "requests>=2.32.3", + "requests-toolbelt>=1.0.0", +] + +[project.scripts] +ragflow-cli = "admin_client:main" diff --git a/admin/models.py b/admin/models.py deleted file mode 100644 index e69de29..0000000 diff --git a/admin/responses.py b/admin/responses.py deleted file mode 100644 index 00cee70..0000000 --- a/admin/responses.py +++ /dev/null @@ -1,15 +0,0 @@ -from flask import jsonify - -def success_response(data=None, message="Success", code = 0): - return jsonify({ - "code": code, - "message": message, - "data": data - }), 200 - -def error_response(message="Error", code=-1, data=None): - return jsonify({ - "code": code, - "message": message, - "data": data - }), 400 \ No newline at end of file diff --git a/admin/routes.py b/admin/routes.py deleted file mode 100644 index a737305..0000000 --- a/admin/routes.py +++ /dev/null @@ -1,190 +0,0 @@ -from flask import Blueprint, request - -from auth import login_verify -from responses import success_response, error_response -from services import UserMgr, ServiceMgr, UserServiceMgr -from api.common.exceptions import AdminException - -admin_bp = Blueprint('admin', __name__, url_prefix='/api/v1/admin') - - -@admin_bp.route('/auth', methods=['GET']) -@login_verify -def auth_admin(): - try: - return success_response(None, "Admin is authorized", 0) - except Exception as e: - return error_response(str(e), 500) - - -@admin_bp.route('/users', methods=['GET']) -@login_verify -def list_users(): - try: - users = UserMgr.get_all_users() - return success_response(users, "Get all users", 0) - except Exception as e: - return error_response(str(e), 500) - - -@admin_bp.route('/users', methods=['POST']) -@login_verify -def create_user(): - try: - data = request.get_json() - if not data or 'username' not in data or 'password' not in data: - return error_response("Username and password are required", 400) - - username = data['username'] - password = data['password'] - role = data.get('role', 'user') - - res = UserMgr.create_user(username, password, role) - if res["success"]: - user_info = res["user_info"] - user_info.pop("password") # do not return password - return success_response(user_info, "User created successfully") - else: - return error_response("create user failed") - - except AdminException as e: - return error_response(e.message, e.code) - except Exception as e: - return error_response(str(e)) - - -@admin_bp.route('/users/', methods=['DELETE']) -@login_verify -def delete_user(username): - try: - res = UserMgr.delete_user(username) - if res["success"]: - return success_response(None, res["message"]) - else: - return error_response(res["message"]) - - except AdminException as e: - return error_response(e.message, e.code) - except Exception as e: - return error_response(str(e), 500) - - -@admin_bp.route('/users//password', methods=['PUT']) -@login_verify -def change_password(username): - try: - data = request.get_json() - if not data or 'new_password' not in data: - return error_response("New password is required", 400) - - new_password = data['new_password'] - msg = UserMgr.update_user_password(username, new_password) - return success_response(None, msg) - - except AdminException as e: - return error_response(e.message, e.code) - except Exception as e: - return error_response(str(e), 500) - - -@admin_bp.route('/users//activate', methods=['PUT']) -@login_verify -def alter_user_activate_status(username): - try: - data = request.get_json() - if not data or 'activate_status' not in data: - return error_response("Activation status is required", 400) - activate_status = data['activate_status'] - msg = UserMgr.update_user_activate_status(username, activate_status) - return success_response(None, msg) - except AdminException as e: - return error_response(e.message, e.code) - except Exception as e: - return error_response(str(e), 500) - -@admin_bp.route('/users/', methods=['GET']) -@login_verify -def get_user_details(username): - try: - user_details = UserMgr.get_user_details(username) - return success_response(user_details) - - except AdminException as e: - return error_response(e.message, e.code) - except Exception as e: - return error_response(str(e), 500) - -@admin_bp.route('/users//datasets', methods=['GET']) -@login_verify -def get_user_datasets(username): - try: - datasets_list = UserServiceMgr.get_user_datasets(username) - return success_response(datasets_list) - - except AdminException as e: - return error_response(e.message, e.code) - except Exception as e: - return error_response(str(e), 500) - - -@admin_bp.route('/users//agents', methods=['GET']) -@login_verify -def get_user_agents(username): - try: - agents_list = UserServiceMgr.get_user_agents(username) - return success_response(agents_list) - - except AdminException as e: - return error_response(e.message, e.code) - except Exception as e: - return error_response(str(e), 500) - - -@admin_bp.route('/services', methods=['GET']) -@login_verify -def get_services(): - try: - services = ServiceMgr.get_all_services() - return success_response(services, "Get all services", 0) - except Exception as e: - return error_response(str(e), 500) - - -@admin_bp.route('/service_types/', methods=['GET']) -@login_verify -def get_services_by_type(service_type_str): - try: - services = ServiceMgr.get_services_by_type(service_type_str) - return success_response(services) - except Exception as e: - return error_response(str(e), 500) - - -@admin_bp.route('/services/', methods=['GET']) -@login_verify -def get_service(service_id): - try: - services = ServiceMgr.get_service_details(service_id) - return success_response(services) - except Exception as e: - return error_response(str(e), 500) - - -@admin_bp.route('/services/', methods=['DELETE']) -@login_verify -def shutdown_service(service_id): - try: - services = ServiceMgr.shutdown_service(service_id) - return success_response(services) - except Exception as e: - return error_response(str(e), 500) - - -@admin_bp.route('/services/', methods=['PUT']) -@login_verify -def restart_service(service_id): - try: - services = ServiceMgr.restart_service(service_id) - return success_response(services) - except Exception as e: - return error_response(str(e), 500) diff --git a/admin/admin_server.py b/admin/server/admin_server.py similarity index 57% rename from admin/admin_server.py rename to admin/server/admin_server.py index 27ee0c7..ddfffe0 100644 --- a/admin/admin_server.py +++ b/admin/server/admin_server.py @@ -1,3 +1,18 @@ +# +# Copyright 2025 The InfiniFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# import os import signal @@ -12,6 +27,9 @@ from api.utils.log_utils import init_root_logger from api.constants import SERVICE_CONF from api import settings from config import load_configurations, SERVICE_CONFIGS +from auth import init_default_admin, setup_auth +from flask_session import Session +from flask_login import LoginManager stop_event = threading.Event() @@ -27,7 +45,17 @@ if __name__ == '__main__': app = Flask(__name__) app.register_blueprint(admin_bp) + app.config["SESSION_PERMANENT"] = False + app.config["SESSION_TYPE"] = "filesystem" + app.config["MAX_CONTENT_LENGTH"] = int( + os.environ.get("MAX_CONTENT_LENGTH", 1024 * 1024 * 1024) + ) + Session(app) + login_manager = LoginManager() + login_manager.init_app(app) settings.init_settings() + setup_auth(login_manager) + init_default_admin() SERVICE_CONFIGS.configs = load_configurations(SERVICE_CONF) try: diff --git a/admin/server/auth.py b/admin/server/auth.py new file mode 100644 index 0000000..05fb620 --- /dev/null +++ b/admin/server/auth.py @@ -0,0 +1,193 @@ +# +# Copyright 2025 The InfiniFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +import logging +import uuid +from functools import wraps +from datetime import datetime +from flask import request, jsonify +from flask_login import current_user, login_user +from itsdangerous.url_safe import URLSafeTimedSerializer as Serializer + +from api import settings +from api.common.exceptions import AdminException, UserNotFoundError +from api.db.init_data import encode_to_base64 +from api.db.services import UserService +from api.db import ActiveEnum, StatusEnum +from api.utils.crypt import decrypt +from api.utils import ( + current_timestamp, + datetime_format, + get_format_time, + get_uuid, +) +from api.utils.api_utils import ( + construct_response, +) + + +def setup_auth(login_manager): + @login_manager.request_loader + def load_user(web_request): + jwt = Serializer(secret_key=settings.SECRET_KEY) + authorization = web_request.headers.get("Authorization") + if authorization: + try: + access_token = str(jwt.loads(authorization)) + + if not access_token or not access_token.strip(): + logging.warning("Authentication attempt with empty access token") + return None + + # Access tokens should be UUIDs (32 hex characters) + if len(access_token.strip()) < 32: + logging.warning(f"Authentication attempt with invalid token format: {len(access_token)} chars") + return None + + user = UserService.query( + access_token=access_token, status=StatusEnum.VALID.value + ) + if user: + if not user[0].access_token or not user[0].access_token.strip(): + logging.warning(f"User {user[0].email} has empty access_token in database") + return None + return user[0] + else: + return None + except Exception as e: + logging.warning(f"load_user got exception {e}") + return None + else: + return None + + +def init_default_admin(): + # Verify that at least one active admin user exists. If not, create a default one. + users = UserService.query(is_superuser=True) + if not users: + default_admin = { + "id": uuid.uuid1().hex, + "password": encode_to_base64("admin"), + "nickname": "admin", + "is_superuser": True, + "email": "admin@ragflow.io", + "creator": "system", + "status": "1", + } + if not UserService.save(**default_admin): + raise AdminException("Can't init admin.", 500) + elif not any([u.is_active == ActiveEnum.ACTIVE.value for u in users]): + raise AdminException("No active admin. Please update 'is_active' in db manually.", 500) + + +def check_admin_auth(func): + @wraps(func) + def wrapper(*args, **kwargs): + user = UserService.filter_by_id(current_user.id) + if not user: + raise UserNotFoundError(current_user.email) + if not user.is_superuser: + raise AdminException("Not admin", 403) + if user.is_active == ActiveEnum.INACTIVE.value: + raise AdminException(f"User {current_user.email} inactive", 403) + + return func(*args, **kwargs) + + return wrapper + + +def login_admin(email: str, password: str): + """ + :param email: admin email + :param password: string before decrypt + """ + users = UserService.query(email=email) + if not users: + raise UserNotFoundError(email) + psw = decrypt(password) + user = UserService.query_user(email, psw) + if not user: + raise AdminException("Email and password do not match!") + if not user.is_superuser: + raise AdminException("Not admin", 403) + if user.is_active == ActiveEnum.INACTIVE.value: + raise AdminException(f"User {email} inactive", 403) + + resp = user.to_json() + user.access_token = get_uuid() + login_user(user) + user.update_time = (current_timestamp(),) + user.update_date = (datetime_format(datetime.now()),) + user.last_login_time = get_format_time() + user.save() + msg = "Welcome back!" + return construct_response(data=resp, auth=user.get_id(), message=msg) + + +def check_admin(username: str, password: str): + users = UserService.query(email=username) + if not users: + logging.info(f"Username: {username} is not registered!") + user_info = { + "id": uuid.uuid1().hex, + "password": encode_to_base64("admin"), + "nickname": "admin", + "is_superuser": True, + "email": "admin@ragflow.io", + "creator": "system", + "status": "1", + } + if not UserService.save(**user_info): + raise AdminException("Can't init admin.", 500) + + user = UserService.query_user(username, password) + if user: + return True + else: + return False + + +def login_verify(f): + @wraps(f) + def decorated(*args, **kwargs): + auth = request.authorization + if not auth or 'username' not in auth.parameters or 'password' not in auth.parameters: + return jsonify({ + "code": 401, + "message": "Authentication required", + "data": None + }), 200 + + username = auth.parameters['username'] + password = auth.parameters['password'] + try: + if check_admin(username, password) is False: + return jsonify({ + "code": 500, + "message": "Access denied", + "data": None + }), 200 + except Exception as e: + error_msg = str(e) + return jsonify({ + "code": 500, + "message": error_msg + }), 200 + + return f(*args, **kwargs) + + return decorated diff --git a/admin/config.py b/admin/server/config.py similarity index 89% rename from admin/config.py rename to admin/server/config.py index 94147de..a14d95b 100644 --- a/admin/config.py +++ b/admin/server/config.py @@ -1,3 +1,20 @@ +# +# Copyright 2025 The InfiniFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + import logging import threading from enum import Enum @@ -9,6 +26,8 @@ from urllib.parse import urlparse class ServiceConfigs: + configs = dict + def __init__(self): self.configs = [] self.lock = threading.Lock() @@ -35,7 +54,8 @@ class BaseConfig(BaseModel): detail_func_name: str def to_dict(self) -> dict[str, Any]: - return {'id': self.id, 'name': self.name, 'host': self.host, 'port': self.port, 'service_type': self.service_type} + return {'id': self.id, 'name': self.name, 'host': self.host, 'port': self.port, + 'service_type': self.service_type} class MetaConfig(BaseConfig): @@ -211,7 +231,8 @@ def load_configurations(config_path: str) -> list[BaseConfig]: host: str = v['host'] http_port: int = v['http_port'] config = RAGFlowServerConfig(id=id_count, name=name, host=host, port=http_port, - service_type="ragflow_server", detail_func_name="check_ragflow_server_alive") + service_type="ragflow_server", + detail_func_name="check_ragflow_server_alive") configurations.append(config) id_count += 1 case "es": @@ -236,7 +257,8 @@ def load_configurations(config_path: str) -> list[BaseConfig]: host = parts[0] port = int(parts[1]) database: str = v.get('db_name', 'default_db') - config = InfinityConfig(id=id_count, name=name, host=host, port=port, service_type="retrieval", retrieval_type="infinity", + config = InfinityConfig(id=id_count, name=name, host=host, port=port, service_type="retrieval", + retrieval_type="infinity", db_name=database, detail_func_name="get_infinity_status") configurations.append(config) id_count += 1 @@ -248,7 +270,8 @@ def load_configurations(config_path: str) -> list[BaseConfig]: port = int(parts[1]) user = v.get('user') password = v.get('password') - config = MinioConfig(id=id_count, name=name, host=host, port=port, user=user, password=password, service_type="file_store", + config = MinioConfig(id=id_count, name=name, host=host, port=port, user=user, password=password, + service_type="file_store", store_type="minio", detail_func_name="check_minio_alive") configurations.append(config) id_count += 1 diff --git a/admin/exceptions.py b/admin/server/exceptions.py similarity index 100% rename from admin/exceptions.py rename to admin/server/exceptions.py diff --git a/deepdoc/parser/resume/entities/__init__.py b/admin/server/models.py similarity index 99% rename from deepdoc/parser/resume/entities/__init__.py rename to admin/server/models.py index e156bc9..177b91d 100644 --- a/deepdoc/parser/resume/entities/__init__.py +++ b/admin/server/models.py @@ -12,4 +12,4 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -# \ No newline at end of file +# diff --git a/deepdoc/parser/pdf_parser.py b/admin/server/responses.py similarity index 63% rename from deepdoc/parser/pdf_parser.py rename to admin/server/responses.py index 5a8ea14..54f841a 100644 --- a/deepdoc/parser/pdf_parser.py +++ b/admin/server/responses.py @@ -1,24 +1,34 @@ -# -# Copyright 2025 The InfiniFlow Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -import logging -import os -import random -import re -import sys -import threading - - +# +# Copyright 2025 The InfiniFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +from flask import jsonify + + +def success_response(data=None, message="Success", code=0): + return jsonify({ + "code": code, + "message": message, + "data": data + }), 200 + + +def error_response(message="Error", code=-1, data=None): + return jsonify({ + "code": code, + "message": message, + "data": data + }), 400 diff --git a/admin/server/roles.py b/admin/server/roles.py new file mode 100644 index 0000000..ac04179 --- /dev/null +++ b/admin/server/roles.py @@ -0,0 +1,76 @@ +# +# Copyright 2025 The InfiniFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +import logging + +from typing import Dict, Any + +from api.common.exceptions import AdminException + + +class RoleMgr: + @staticmethod + def create_role(role_name: str, description: str): + error_msg = f"not implement: create role: {role_name}, description: {description}" + logging.error(error_msg) + raise AdminException(error_msg) + + @staticmethod + def update_role_description(role_name: str, description: str) -> Dict[str, Any]: + error_msg = f"not implement: update role: {role_name} with description: {description}" + logging.error(error_msg) + raise AdminException(error_msg) + + @staticmethod + def delete_role(role_name: str) -> Dict[str, Any]: + error_msg = f"not implement: drop role: {role_name}" + logging.error(error_msg) + raise AdminException(error_msg) + + @staticmethod + def list_roles() -> Dict[str, Any]: + error_msg = "not implement: list roles" + logging.error(error_msg) + raise AdminException(error_msg) + + @staticmethod + def get_role_permission(role_name: str) -> Dict[str, Any]: + error_msg = f"not implement: show role {role_name}" + logging.error(error_msg) + raise AdminException(error_msg) + + @staticmethod + def grant_role_permission(role_name: str, actions: list, resource: str) -> Dict[str, Any]: + error_msg = f"not implement: grant role {role_name} actions: {actions} on {resource}" + logging.error(error_msg) + raise AdminException(error_msg) + + @staticmethod + def revoke_role_permission(role_name: str, actions: list, resource: str) -> Dict[str, Any]: + error_msg = f"not implement: revoke role {role_name} actions: {actions} on {resource}" + logging.error(error_msg) + raise AdminException(error_msg) + + @staticmethod + def update_user_role(user_name: str, role_name: str) -> Dict[str, Any]: + error_msg = f"not implement: update user role: {user_name} to role {role_name}" + logging.error(error_msg) + raise AdminException(error_msg) + + @staticmethod + def get_user_permission(user_name: str) -> Dict[str, Any]: + error_msg = f"not implement: get user permission: {user_name}" + logging.error(error_msg) + raise AdminException(error_msg) diff --git a/admin/server/routes.py b/admin/server/routes.py new file mode 100644 index 0000000..7235695 --- /dev/null +++ b/admin/server/routes.py @@ -0,0 +1,371 @@ +# +# Copyright 2025 The InfiniFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import secrets + +from flask import Blueprint, request +from flask_login import current_user, logout_user, login_required + +from auth import login_verify, login_admin, check_admin_auth +from responses import success_response, error_response +from services import UserMgr, ServiceMgr, UserServiceMgr +from roles import RoleMgr +from api.common.exceptions import AdminException + +admin_bp = Blueprint('admin', __name__, url_prefix='/api/v1/admin') + + +@admin_bp.route('/login', methods=['POST']) +def login(): + if not request.json: + return error_response('Authorize admin failed.' ,400) + try: + email = request.json.get("email", "") + password = request.json.get("password", "") + return login_admin(email, password) + except Exception as e: + return error_response(str(e), 500) + + +@admin_bp.route('/logout', methods=['GET']) +@login_required +def logout(): + try: + current_user.access_token = f"INVALID_{secrets.token_hex(16)}" + current_user.save() + logout_user() + return success_response(True) + except Exception as e: + return error_response(str(e), 500) + + +@admin_bp.route('/auth', methods=['GET']) +@login_verify +def auth_admin(): + try: + return success_response(None, "Admin is authorized", 0) + except Exception as e: + return error_response(str(e), 500) + + +@admin_bp.route('/users', methods=['GET']) +@login_required +@check_admin_auth +def list_users(): + try: + users = UserMgr.get_all_users() + return success_response(users, "Get all users", 0) + except Exception as e: + return error_response(str(e), 500) + + +@admin_bp.route('/users', methods=['POST']) +@login_required +@check_admin_auth +def create_user(): + try: + data = request.get_json() + if not data or 'username' not in data or 'password' not in data: + return error_response("Username and password are required", 400) + + username = data['username'] + password = data['password'] + role = data.get('role', 'user') + + res = UserMgr.create_user(username, password, role) + if res["success"]: + user_info = res["user_info"] + user_info.pop("password") # do not return password + return success_response(user_info, "User created successfully") + else: + return error_response("create user failed") + + except AdminException as e: + return error_response(e.message, e.code) + except Exception as e: + return error_response(str(e)) + + +@admin_bp.route('/users/', methods=['DELETE']) +@login_required +@check_admin_auth +def delete_user(username): + try: + res = UserMgr.delete_user(username) + if res["success"]: + return success_response(None, res["message"]) + else: + return error_response(res["message"]) + + except AdminException as e: + return error_response(e.message, e.code) + except Exception as e: + return error_response(str(e), 500) + + +@admin_bp.route('/users//password', methods=['PUT']) +@login_required +@check_admin_auth +def change_password(username): + try: + data = request.get_json() + if not data or 'new_password' not in data: + return error_response("New password is required", 400) + + new_password = data['new_password'] + msg = UserMgr.update_user_password(username, new_password) + return success_response(None, msg) + + except AdminException as e: + return error_response(e.message, e.code) + except Exception as e: + return error_response(str(e), 500) + + +@admin_bp.route('/users//activate', methods=['PUT']) +@login_required +@check_admin_auth +def alter_user_activate_status(username): + try: + data = request.get_json() + if not data or 'activate_status' not in data: + return error_response("Activation status is required", 400) + activate_status = data['activate_status'] + msg = UserMgr.update_user_activate_status(username, activate_status) + return success_response(None, msg) + except AdminException as e: + return error_response(e.message, e.code) + except Exception as e: + return error_response(str(e), 500) + + +@admin_bp.route('/users/', methods=['GET']) +@login_required +@check_admin_auth +def get_user_details(username): + try: + user_details = UserMgr.get_user_details(username) + return success_response(user_details) + + except AdminException as e: + return error_response(e.message, e.code) + except Exception as e: + return error_response(str(e), 500) + + +@admin_bp.route('/users//datasets', methods=['GET']) +@login_required +@check_admin_auth +def get_user_datasets(username): + try: + datasets_list = UserServiceMgr.get_user_datasets(username) + return success_response(datasets_list) + + except AdminException as e: + return error_response(e.message, e.code) + except Exception as e: + return error_response(str(e), 500) + + +@admin_bp.route('/users//agents', methods=['GET']) +@login_required +@check_admin_auth +def get_user_agents(username): + try: + agents_list = UserServiceMgr.get_user_agents(username) + return success_response(agents_list) + + except AdminException as e: + return error_response(e.message, e.code) + except Exception as e: + return error_response(str(e), 500) + + +@admin_bp.route('/services', methods=['GET']) +@login_required +@check_admin_auth +def get_services(): + try: + services = ServiceMgr.get_all_services() + return success_response(services, "Get all services", 0) + except Exception as e: + return error_response(str(e), 500) + + +@admin_bp.route('/service_types/', methods=['GET']) +@login_required +@check_admin_auth +def get_services_by_type(service_type_str): + try: + services = ServiceMgr.get_services_by_type(service_type_str) + return success_response(services) + except Exception as e: + return error_response(str(e), 500) + + +@admin_bp.route('/services/', methods=['GET']) +@login_required +@check_admin_auth +def get_service(service_id): + try: + services = ServiceMgr.get_service_details(service_id) + return success_response(services) + except Exception as e: + return error_response(str(e), 500) + + +@admin_bp.route('/services/', methods=['DELETE']) +@login_required +@check_admin_auth +def shutdown_service(service_id): + try: + services = ServiceMgr.shutdown_service(service_id) + return success_response(services) + except Exception as e: + return error_response(str(e), 500) + + +@admin_bp.route('/services/', methods=['PUT']) +@login_required +@check_admin_auth +def restart_service(service_id): + try: + services = ServiceMgr.restart_service(service_id) + return success_response(services) + except Exception as e: + return error_response(str(e), 500) + + +@admin_bp.route('/roles', methods=['POST']) +@login_required +@check_admin_auth +def create_role(): + try: + data = request.get_json() + if not data or 'role_name' not in data: + return error_response("Role name is required", 400) + role_name: str = data['role_name'] + description: str = data['description'] + res = RoleMgr.create_role(role_name, description) + return success_response(res) + except Exception as e: + return error_response(str(e), 500) + + +@admin_bp.route('/roles/', methods=['PUT']) +@login_required +@check_admin_auth +def update_role(role_name: str): + try: + data = request.get_json() + if not data or 'description' not in data: + return error_response("Role description is required", 400) + description: str = data['description'] + res = RoleMgr.update_role_description(role_name, description) + return success_response(res) + except Exception as e: + return error_response(str(e), 500) + + +@admin_bp.route('/roles/', methods=['DELETE']) +@login_required +@check_admin_auth +def delete_role(role_name: str): + try: + res = RoleMgr.delete_role(role_name) + return success_response(res) + except Exception as e: + return error_response(str(e), 500) + + +@admin_bp.route('/roles', methods=['GET']) +@login_required +@check_admin_auth +def list_roles(): + try: + res = RoleMgr.list_roles() + return success_response(res) + except Exception as e: + return error_response(str(e), 500) + + +@admin_bp.route('/roles//permission', methods=['GET']) +@login_required +@check_admin_auth +def get_role_permission(role_name: str): + try: + res = RoleMgr.get_role_permission(role_name) + return success_response(res) + except Exception as e: + return error_response(str(e), 500) + + +@admin_bp.route('/roles//permission', methods=['POST']) +@login_required +@check_admin_auth +def grant_role_permission(role_name: str): + try: + data = request.get_json() + if not data or 'actions' not in data or 'resource' not in data: + return error_response("Permission is required", 400) + actions: list = data['actions'] + resource: str = data['resource'] + res = RoleMgr.grant_role_permission(role_name, actions, resource) + return success_response(res) + except Exception as e: + return error_response(str(e), 500) + + +@admin_bp.route('/roles//permission', methods=['DELETE']) +@login_required +@check_admin_auth +def revoke_role_permission(role_name: str): + try: + data = request.get_json() + if not data or 'actions' not in data or 'resource' not in data: + return error_response("Permission is required", 400) + actions: list = data['actions'] + resource: str = data['resource'] + res = RoleMgr.revoke_role_permission(role_name, actions, resource) + return success_response(res) + except Exception as e: + return error_response(str(e), 500) + + +@admin_bp.route('/users//role', methods=['PUT']) +@login_required +@check_admin_auth +def update_user_role(user_name: str): + try: + data = request.get_json() + if not data or 'role_name' not in data: + return error_response("Role name is required", 400) + role_name: str = data['role_name'] + res = RoleMgr.update_user_role(user_name, role_name) + return success_response(res) + except Exception as e: + return error_response(str(e), 500) + + +@admin_bp.route('/users//permission', methods=['GET']) +@login_required +@check_admin_auth +def get_user_permission(user_name: str): + try: + res = RoleMgr.get_user_permission(user_name) + return success_response(res) + except Exception as e: + return error_response(str(e), 500) diff --git a/admin/services.py b/admin/server/services.py similarity index 82% rename from admin/services.py rename to admin/server/services.py index 2c8eaaf..f5c6ce3 100644 --- a/admin/services.py +++ b/admin/server/services.py @@ -1,3 +1,20 @@ +# +# Copyright 2025 The InfiniFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + import re from werkzeug.security import check_password_hash from api.db import ActiveEnum @@ -12,13 +29,20 @@ from api.utils import health_utils from api.common.exceptions import AdminException, UserAlreadyExistsError, UserNotFoundError from config import SERVICE_CONFIGS + class UserMgr: @staticmethod def get_all_users(): users = UserService.get_all_users() result = [] for user in users: - result.append({'email': user.email, 'nickname': user.nickname, 'create_date': user.create_date, 'is_active': user.is_active}) + result.append({ + 'email': user.email, + 'nickname': user.nickname, + 'create_date': user.create_date, + 'is_active': user.is_active, + 'is_superuser': user.is_superuser, + }) return result @staticmethod @@ -31,7 +55,6 @@ class UserMgr: 'email': user.email, 'language': user.language, 'last_login_time': user.last_login_time, - 'is_authenticated': user.is_authenticated, 'is_active': user.is_active, 'is_anonymous': user.is_anonymous, 'login_channel': user.login_channel, @@ -112,6 +135,7 @@ class UserMgr: UserService.update_user(usr.id, {"is_active": target_status}) return f"Turn {_activate_status} user activate status successfully!" + class UserServiceMgr: @staticmethod @@ -146,18 +170,27 @@ class UserServiceMgr: return [{ 'title': r['title'], 'permission': r['permission'], - 'canvas_type': r['canvas_type'], - 'canvas_category': r['canvas_category'] + 'canvas_category': r['canvas_category'].split('_')[0] } for r in res] + class ServiceMgr: @staticmethod def get_all_services(): result = [] configs = SERVICE_CONFIGS.configs - for config in configs: - result.append(config.to_dict()) + for service_id, config in enumerate(configs): + config_dict = config.to_dict() + try: + service_detail = ServiceMgr.get_service_details(service_id) + if "status" in service_detail: + config_dict['status'] = service_detail['status'] + else: + config_dict['status'] = 'timeout' + except Exception: + config_dict['status'] = 'timeout' + result.append(config_dict) return result @staticmethod @@ -176,7 +209,7 @@ class ServiceMgr: } service_info = service_config_mapping.get(service_id, {}) if not service_info: - raise AdminException(f"Invalid service_id: {service_id}") + raise AdminException(f"invalid service_id: {service_id}") detail_func = getattr(health_utils, service_info.get('detail_func_name')) res = detail_func() diff --git a/agent/canvas.py b/agent/canvas.py index a22391d..d1cbc48 100644 --- a/agent/canvas.py +++ b/agent/canvas.py @@ -203,7 +203,6 @@ class Canvas(Graph): self.history = [] self.retrieval = [] self.memory = [] - for k in self.globals.keys(): if isinstance(self.globals[k], str): self.globals[k] = "" @@ -292,7 +291,6 @@ class Canvas(Graph): "thoughts": self.get_component_thoughts(self.path[i]) }) _run_batch(idx, to) - # post processing of components invocation for i in range(idx, to): cpn = self.get_component(self.path[i]) @@ -393,7 +391,6 @@ class Canvas(Graph): self.path = path yield decorate("user_inputs", {"inputs": another_inputs, "tips": tips}) return - self.path = self.path[:idx] if not self.error: yield decorate("workflow_finished", diff --git a/agent/component/agent_with_tools.py b/agent/component/agent_with_tools.py index a85df40..32458fc 100644 --- a/agent/component/agent_with_tools.py +++ b/agent/component/agent_with_tools.py @@ -346,7 +346,11 @@ Respond immediately with your final comprehensive answer. return "Error occurred." - def reset(self): + def reset(self, temp=False): + """ + Reset all tools if they have a reset method. This avoids errors for tools like MCPToolCallSession. + """ for k, cpn in self.tools.items(): - cpn.reset() + if hasattr(cpn, "reset") and callable(cpn.reset): + cpn.reset() diff --git a/agent/templates/advanced_ingestion_pipeline.json b/agent/templates/advanced_ingestion_pipeline.json new file mode 100644 index 0000000..5765155 --- /dev/null +++ b/agent/templates/advanced_ingestion_pipeline.json @@ -0,0 +1,726 @@ +{ + "id": 23, + "title": { + "en": "Advanced Ingestion Pipeline", + "zh": "编排复杂的 Ingestion Pipeline" + }, + "description": { + "en": "This template demonstrates how to use an LLM to generate summaries, keywords, Q&A, and metadata for each chunk to support diverse retrieval needs.", + "zh": "此模板演示如何利用大模型为切片生成摘要、关键词、问答及元数据,以满足多样化的召回需求。" + }, + "canvas_type": "Ingestion Pipeline", + "canvas_category": "dataflow_canvas", + "dsl": { + "components": { + "File": { + "obj": { + "component_name": "File", + "params": {} + }, + "downstream": [ + "Parser:HipSignsRhyme" + ], + "upstream": [] + }, + "Parser:HipSignsRhyme": { + "obj": { + "component_name": "Parser", + "params": { + "outputs": { + "html": { + "type": "string", + "value": "" + }, + "json": { + "type": "Array", + "value": [] + }, + "markdown": { + "type": "string", + "value": "" + }, + "text": { + "type": "string", + "value": "" + } + }, + "setups": { + "pdf": { + "output_format": "markdown", + "suffix": [ + "pdf" + ], + "parse_method": "DeepDOC" + }, + "spreadsheet": { + "output_format": "html", + "suffix": [ + "xls", + "xlsx", + "csv" + ] + }, + "image": { + "output_format": "text", + "suffix": [ + "jpg", + "jpeg", + "png", + "gif" + ], + "parse_method": "ocr" + }, + "email": { + "output_format": "text", + "suffix": [ + "eml", + "msg" + ], + "fields": [ + "from", + "to", + "cc", + "bcc", + "date", + "subject", + "body", + "attachments" + ] + }, + "text&markdown": { + "output_format": "text", + "suffix": [ + "md", + "markdown", + "mdx", + "txt" + ] + }, + "word": { + "output_format": "json", + "suffix": [ + "doc", + "docx" + ] + }, + "slides": { + "output_format": "json", + "suffix": [ + "pptx" + ] + } + } + } + }, + "downstream": [ + "Splitter:KindDingosJam" + ], + "upstream": [ + "File" + ] + }, + "Splitter:KindDingosJam": { + "obj": { + "component_name": "Splitter", + "params": { + "chunk_token_size": 512, + "delimiters": [ + "\n" + ], + "outputs": { + "chunks": { + "type": "Array", + "value": [] + } + }, + "overlapped_percent": 0.002 + } + }, + "downstream": [ + "Extractor:NineTiesSin" + ], + "upstream": [ + "Parser:HipSignsRhyme" + ] + }, + "Extractor:NineTiesSin": { + "obj": { + "component_name": "Extractor", + "params": { + "field_name": "summary", + "frequencyPenaltyEnabled": false, + "frequency_penalty": 0.7, + "llm_id": "deepseek-chat@DeepSeek", + "maxTokensEnabled": false, + "max_tokens": 256, + "outputs": {}, + "presencePenaltyEnabled": false, + "presence_penalty": 0.4, + "prompts": [ + { + "content": "Text to Summarize:\n{Splitter:KindDingosJam@chunks}", + "role": "user" + } + ], + "sys_prompt": "Act as a precise summarizer. Your task is to create a summary of the provided content that is both concise and faithful to the original.\n\nKey Instructions:\n1. Accuracy: Strictly base the summary on the information given. Do not introduce any new facts, conclusions, or interpretations that are not explicitly stated.\n2. Language: Write the summary in the same language as the source text.\n3. Objectivity: Present the key points without bias, preserving the original intent and tone of the content. Do not editorialize.\n4. Conciseness: Focus on the most important ideas, omitting minor details and fluff.", + "temperature": 0.1, + "temperatureEnabled": false, + "topPEnabled": false, + "top_p": 0.3 + } + }, + "downstream": [ + "Extractor:TastyPointsLay" + ], + "upstream": [ + "Splitter:KindDingosJam" + ] + }, + "Extractor:TastyPointsLay": { + "obj": { + "component_name": "Extractor", + "params": { + "field_name": "keywords", + "frequencyPenaltyEnabled": false, + "frequency_penalty": 0.7, + "llm_id": "deepseek-chat@DeepSeek", + "maxTokensEnabled": false, + "max_tokens": 256, + "outputs": {}, + "presencePenaltyEnabled": false, + "presence_penalty": 0.4, + "prompts": [ + { + "content": "Text Content:\n{Splitter:KindDingosJam@chunks}\n", + "role": "user" + } + ], + "sys_prompt": "Role\nYou are a text analyzer.\n\nTask\nExtract the most important keywords/phrases of a given piece of text content.\n\nRequirements\n- Summarize the text content, and give the top 5 important keywords/phrases.\n- The keywords MUST be in the same language as the given piece of text content.\n- The keywords are delimited by ENGLISH COMMA.\n- Output keywords ONLY.", + "temperature": 0.1, + "temperatureEnabled": false, + "topPEnabled": false, + "top_p": 0.3 + } + }, + "downstream": [ + "Extractor:BlueResultsWink" + ], + "upstream": [ + "Extractor:NineTiesSin" + ] + }, + "Extractor:BlueResultsWink": { + "obj": { + "component_name": "Extractor", + "params": { + "field_name": "questions", + "frequencyPenaltyEnabled": false, + "frequency_penalty": 0.7, + "llm_id": "deepseek-chat@DeepSeek", + "maxTokensEnabled": false, + "max_tokens": 256, + "outputs": {}, + "presencePenaltyEnabled": false, + "presence_penalty": 0.4, + "prompts": [ + { + "content": "Text Content:\n\n{Splitter:KindDingosJam@chunks}\n", + "role": "user" + } + ], + "sys_prompt": "Role\nYou are a text analyzer.\n\nTask\nPropose 3 questions about a given piece of text content.\n\nRequirements\n- Understand and summarize the text content, and propose the top 3 important questions.\n- The questions SHOULD NOT have overlapping meanings.\n- The questions SHOULD cover the main content of the text as much as possible.\n- The questions MUST be in the same language as the given piece of text content.\n- One question per line.\n- Output questions ONLY.", + "temperature": 0.1, + "temperatureEnabled": false, + "topPEnabled": false, + "top_p": 0.3 + } + }, + "downstream": [ + "Extractor:CuteBusesBet" + ], + "upstream": [ + "Extractor:TastyPointsLay" + ] + }, + "Extractor:CuteBusesBet": { + "obj": { + "component_name": "Extractor", + "params": { + "field_name": "metadata", + "frequencyPenaltyEnabled": false, + "frequency_penalty": 0.7, + "llm_id": "deepseek-chat@DeepSeek", + "maxTokensEnabled": false, + "max_tokens": 256, + "outputs": {}, + "presencePenaltyEnabled": false, + "presence_penalty": 0.4, + "prompts": [ + { + "content": "Content: \n\n{Splitter:KindDingosJam@chunks}", + "role": "user" + } + ], + "sys_prompt": "Extract important structured information from the given content. Output ONLY a valid JSON string with no additional text. If no important structured information is found, output an empty JSON object: {}.\n\nImportant structured information may include: names, dates, locations, events, key facts, numerical data, or other extractable entities.", + "temperature": 0.1, + "temperatureEnabled": false, + "topPEnabled": false, + "top_p": 0.3 + } + }, + "downstream": [ + "Tokenizer:LegalHorsesCheer" + ], + "upstream": [ + "Extractor:BlueResultsWink" + ] + }, + "Tokenizer:LegalHorsesCheer": { + "obj": { + "component_name": "Tokenizer", + "params": { + "fields": "text", + "filename_embd_weight": 0.1, + "outputs": {}, + "search_method": [ + "embedding", + "full_text" + ] + } + }, + "downstream": [], + "upstream": [ + "Extractor:CuteBusesBet" + ] + } + }, + "globals": {}, + "graph": { + "nodes": [ + { + "data": { + "label": "File", + "name": "File" + }, + "dragging": false, + "id": "File", + "measured": { + "height": 48, + "width": 200 + }, + "position": { + "x": -301.4128436198721, + "y": 375.86728431988394 + }, + "selected": false, + "sourcePosition": "left", + "targetPosition": "right", + "type": "beginNode" + }, + { + "data": { + "form": { + "outputs": { + "html": { + "type": "string", + "value": "" + }, + "json": { + "type": "Array", + "value": [] + }, + "markdown": { + "type": "string", + "value": "" + }, + "text": { + "type": "string", + "value": "" + } + }, + "setups": [ + { + "fileFormat": "pdf", + "output_format": "markdown", + "parse_method": "DeepDOC" + }, + { + "fileFormat": "spreadsheet", + "output_format": "html" + }, + { + "fileFormat": "image", + "output_format": "text", + "parse_method": "ocr" + }, + { + "fields": [ + "from", + "to", + "cc", + "bcc", + "date", + "subject", + "body", + "attachments" + ], + "fileFormat": "email", + "output_format": "text" + }, + { + "fileFormat": "text&markdown", + "output_format": "text" + }, + { + "fileFormat": "word", + "output_format": "json" + }, + { + "fileFormat": "slides", + "output_format": "json" + } + ] + }, + "label": "Parser", + "name": "Parser" + }, + "dragging": false, + "id": "Parser:HipSignsRhyme", + "measured": { + "height": 56, + "width": 200 + }, + "position": { + "x": -297.12089864837964, + "y": 532.2084591689336 + }, + "selected": false, + "sourcePosition": "right", + "targetPosition": "left", + "type": "parserNode" + }, + { + "data": { + "form": { + "chunk_token_size": 512, + "delimiters": [ + { + "value": "\n" + } + ], + "outputs": { + "chunks": { + "type": "Array", + "value": [] + } + }, + "overlapped_percent": 0.2 + }, + "label": "Splitter", + "name": "Token Chunker" + }, + "dragging": false, + "id": "Splitter:KindDingosJam", + "measured": { + "height": 80, + "width": 200 + }, + "position": { + "x": 7.288275851418206, + "y": 371.19722568785704 + }, + "selected": false, + "sourcePosition": "right", + "targetPosition": "left", + "type": "splitterNode" + }, + { + "data": { + "form": { + "field_name": "summary", + "frequencyPenaltyEnabled": false, + "frequency_penalty": 0.7, + "llm_id": "deepseek-chat@DeepSeek", + "maxTokensEnabled": false, + "max_tokens": 256, + "outputs": {}, + "presencePenaltyEnabled": false, + "presence_penalty": 0.4, + "prompts": "Text to Summarize:\n{Splitter:KindDingosJam@chunks}", + "sys_prompt": "Act as a precise summarizer. Your task is to create a summary of the provided content that is both concise and faithful to the original.\n\nKey Instructions:\n1. Accuracy: Strictly base the summary on the information given. Do not introduce any new facts, conclusions, or interpretations that are not explicitly stated.\n2. Language: Write the summary in the same language as the source text.\n3. Objectivity: Present the key points without bias, preserving the original intent and tone of the content. Do not editorialize.\n4. Conciseness: Focus on the most important ideas, omitting minor details and fluff.", + "temperature": 0.1, + "temperatureEnabled": false, + "topPEnabled": false, + "top_p": 0.3 + }, + "label": "Extractor", + "name": "Summarization" + }, + "dragging": false, + "id": "Extractor:NineTiesSin", + "measured": { + "height": 84, + "width": 200 + }, + "position": { + "x": 9.537168313582939, + "y": 461.26662127765564 + }, + "selected": false, + "sourcePosition": "right", + "targetPosition": "left", + "type": "contextNode" + }, + { + "data": { + "form": { + "field_name": "keywords", + "frequencyPenaltyEnabled": false, + "frequency_penalty": 0.7, + "llm_id": "deepseek-chat@DeepSeek", + "maxTokensEnabled": false, + "max_tokens": 256, + "outputs": {}, + "presencePenaltyEnabled": false, + "presence_penalty": 0.4, + "prompts": "Text Content:\n{Splitter:KindDingosJam@chunks}\n", + "sys_prompt": "Role\nYou are a text analyzer.\n\nTask\nExtract the most important keywords/phrases of a given piece of text content.\n\nRequirements\n- Summarize the text content, and give the top 5 important keywords/phrases.\n- The keywords MUST be in the same language as the given piece of text content.\n- The keywords are delimited by ENGLISH COMMA.\n- Output keywords ONLY.", + "temperature": 0.1, + "temperatureEnabled": false, + "topPEnabled": false, + "top_p": 0.3 + }, + "label": "Extractor", + "name": "Auto Keywords" + }, + "dragging": false, + "id": "Extractor:TastyPointsLay", + "measured": { + "height": 84, + "width": 200 + }, + "position": { + "x": 7.473032067783009, + "y": 533.0519245332371 + }, + "selected": false, + "sourcePosition": "right", + "targetPosition": "left", + "type": "contextNode" + }, + { + "data": { + "form": { + "field_name": "questions", + "frequencyPenaltyEnabled": false, + "frequency_penalty": 0.7, + "llm_id": "deepseek-chat@DeepSeek", + "maxTokensEnabled": false, + "max_tokens": 256, + "outputs": {}, + "presencePenaltyEnabled": false, + "presence_penalty": 0.4, + "prompts": "Text Content:\n\n{Splitter:KindDingosJam@chunks}\n", + "sys_prompt": "Role\nYou are a text analyzer.\n\nTask\nPropose 3 questions about a given piece of text content.\n\nRequirements\n- Understand and summarize the text content, and propose the top 3 important questions.\n- The questions SHOULD NOT have overlapping meanings.\n- The questions SHOULD cover the main content of the text as much as possible.\n- The questions MUST be in the same language as the given piece of text content.\n- One question per line.\n- Output questions ONLY.", + "temperature": 0.1, + "temperatureEnabled": false, + "topPEnabled": false, + "top_p": 0.3 + }, + "label": "Extractor", + "name": "Auto Questions" + }, + "dragging": false, + "id": "Extractor:BlueResultsWink", + "measured": { + "height": 84, + "width": 200 + }, + "position": { + "x": 2.905601749296892, + "y": 617.0420857433816 + }, + "selected": false, + "sourcePosition": "right", + "targetPosition": "left", + "type": "contextNode" + }, + { + "data": { + "form": { + "field_name": "metadata", + "frequencyPenaltyEnabled": false, + "frequency_penalty": 0.7, + "llm_id": "deepseek-chat@DeepSeek", + "maxTokensEnabled": false, + "max_tokens": 256, + "outputs": {}, + "presencePenaltyEnabled": false, + "presence_penalty": 0.4, + "prompts": "Content: \n\n{Splitter:KindDingosJam@chunks}", + "sys_prompt": "Extract important structured information from the given content. Output ONLY a valid JSON string with no additional text. If no important structured information is found, output an empty JSON object: {}.\n\nImportant structured information may include: names, dates, locations, events, key facts, numerical data, or other extractable entities.", + "temperature": 0.1, + "temperatureEnabled": false, + "topPEnabled": false, + "top_p": 0.3 + }, + "label": "Extractor", + "name": "Generate Metadata" + }, + "dragging": false, + "id": "Extractor:CuteBusesBet", + "measured": { + "height": 84, + "width": 200 + }, + "position": { + "x": 327.16477358029204, + "y": 374.11630810111944 + }, + "selected": false, + "sourcePosition": "right", + "targetPosition": "left", + "type": "contextNode" + }, + { + "data": { + "form": { + "fields": "text", + "filename_embd_weight": 0.1, + "outputs": {}, + "search_method": [ + "embedding", + "full_text" + ] + }, + "label": "Tokenizer", + "name": "Indexer" + }, + "dragging": false, + "id": "Tokenizer:LegalHorsesCheer", + "measured": { + "height": 120, + "width": 200 + }, + "position": { + "x": 345.50155210663667, + "y": 533.0511852267863 + }, + "selected": false, + "sourcePosition": "right", + "targetPosition": "left", + "type": "tokenizerNode" + }, + { + "id": "Note:CruelSidesStick", + "type": "noteNode", + "position": { + "x": -29, + "y": 765 + }, + "data": { + "label": "Note", + "name": "Add more attributes", + "form": { + "text": "Using LLM to generate summaries, keywords, Q&A, and metadata." + } + }, + "sourcePosition": "right", + "targetPosition": "left", + "dragHandle": ".note-drag-handle", + "measured": { + "width": 281, + "height": 130 + }, + "width": 281, + "height": 130, + "resizing": false + } + ], + "edges": [ + { + "data": { + "isHovered": false + }, + "id": "xy-edge__Filestart-Parser:HipSignsRhymeend", + "source": "File", + "sourceHandle": "start", + "target": "Parser:HipSignsRhyme", + "targetHandle": "end" + }, + { + "data": { + "isHovered": false + }, + "id": "xy-edge__Splitter:KindDingosJamstart-Extractor:NineTiesSinend", + "source": "Splitter:KindDingosJam", + "sourceHandle": "start", + "target": "Extractor:NineTiesSin", + "targetHandle": "end" + }, + { + "data": { + "isHovered": false + }, + "id": "xy-edge__Extractor:NineTiesSinstart-Extractor:TastyPointsLayend", + "source": "Extractor:NineTiesSin", + "sourceHandle": "start", + "target": "Extractor:TastyPointsLay", + "targetHandle": "end" + }, + { + "data": { + "isHovered": false + }, + "id": "xy-edge__Extractor:TastyPointsLaystart-Extractor:BlueResultsWinkend", + "source": "Extractor:TastyPointsLay", + "sourceHandle": "start", + "target": "Extractor:BlueResultsWink", + "targetHandle": "end" + }, + { + "data": { + "isHovered": false + }, + "id": "xy-edge__Extractor:BlueResultsWinkstart-Extractor:CuteBusesBetend", + "source": "Extractor:BlueResultsWink", + "sourceHandle": "start", + "target": "Extractor:CuteBusesBet", + "targetHandle": "end" + }, + { + "data": { + "isHovered": false + }, + "id": "xy-edge__Extractor:CuteBusesBetstart-Tokenizer:LegalHorsesCheerend", + "source": "Extractor:CuteBusesBet", + "sourceHandle": "start", + "target": "Tokenizer:LegalHorsesCheer", + "targetHandle": "end" + }, + { + "data": { + "isHovered": false + }, + "id": "xy-edge__Parser:HipSignsRhymestart-Splitter:KindDingosJamend", + "markerEnd": "logo", + "source": "Parser:HipSignsRhyme", + "sourceHandle": "start", + "style": { + "stroke": "rgba(91, 93, 106, 1)", + "strokeWidth": 1 + }, + "target": "Splitter:KindDingosJam", + "targetHandle": "end", + "type": "buttonEdge", + "zIndex": 1001 + } + ] + }, + "history": [], + "messages": [], + "path": [], + "retrieval": [] + }, + "avatar": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAABpQSURBVHgBbXoJfFTluf5zzpw5syaZZLKRjZCEBEIgLAFBLYIW0apsUnFprUu1t3q1WqWl2Nvi36X6v7aIqNV760WttVotFVBZROAiEowIYQ1bQvY9mcxk9jlzzn2+E7Cx7eSX32xneb93ed7nfb6R8A+PVWvfm3ek4exih01ZAkjFsiShu7cf40uKcM9ty5DuSUEyqSMS03i0AYfNCsOA+fofH+LceEJDXNNhtylQZRmlbh1/6gHur3Mi3KgAHkCxAZqb1/PzvYXniasN6TCcwPIpftzvTdaHNLneZZEfnz8tvXn0PaSLL9as/Zvnk7qDv7Zb5YfSUlPMzwZ8fqS4nXjgrpsxc1oJurqDpkHCVJtVMc+OxTU+SVBVC3R9ZBESDReL1HQdikWGhf/FqoavYjpu2ZuKzrMKUjOA66/fi+BgLjbvKQdSdUg0XgrJ0LmWmpIgXqwII0WXYbVznW4rBjULWgLS82FH4vGl49KHvl7AvCUPeeJWdXd2tneqsC4cjWB4OIw7br4BixZeytcxRKJxGmTASmNketIw/m6seB1PJGFVaCy/i3KR4lkcN0bVEVcN3LLPir1nHYAziTG5Cn660MDjoWswxTseLetfRDeNT9Ka7JQoXpgSwFyngR4u5C/tKXir2YGOuASFp8u8nmzI9WEd83GnNGQu4OoHXlzrjvQ95EgOYyiiY/Ilc3D/HUu4FhlDgTCN12iMBKfd+rWXv/mQ+D3M45L0usOhIoXvc91JrGxQ8OIBJ6QgF58rIz+X6ZGWQCEXMS/Hj81bHDhySoVaoOGXU/24JU2HapWx/pQdvz3kRtZkA3fNiGKsR0ePy4FlvNFW3lEJRZ5/xO18WLJv8BVrcJ7XArxjMIiaTA1/+q4XhRjCSZ8ECz2sWkV6gLksPCsxCnw/KgIa00XnAVbFAoXflzo0vDkAPPhpKsLDMiR6zghrcOQr8DCvbTQy7Ad6j/KeKQZuqvbjiaI4OkMKitN1fHtzOhodFjywPI4qScNZw4pImhV3PPU31Gyvg89hx2sbfoLTuZ750sy1H21w2K13LJxbDU9RJnY06di0XcFUFtVHy8MwkqwFhk++mNs0VNNGvCQ8H9eSNJqv+V/m1HEiYWD5p3a09togpzMHBhTcXJlATZ4VH7bo2NXBnGa6JMIGqseG8PKEEJS4bKbIVSVcwB/S4Jss4en5UVgGk+gc48Llnx7Hktt/g2S/D4mqCTDOdSAlNIiz9928Tspoe+zwOE/hVCYqdqYvw2c+F1rtUTx9xInOLTL+dmsYc3M1tIe4CAlmwXIdCMXiJvA4bCqyrXxh0/H9Qyp2HHBAUnRY0mTYmW4nFvtQVOBBPCmDdY6T24GZ53X8flYfZtPLp8My0l0WXF4kYewrbrSWyXhkbgzpEQNX5Ngx+81dkO97BeHSPCAQgTKzDIna02bdWQP+ZnlI8k09G2xCn94Mb9dDOIZe9PpUrCECFN+rY+k7TmxsVpDnNEyPJ+hxUbAOVYWXuV7miuO5Lub766nYsceBBTURTBmTgOYz8Na1YYQkG+IGC5uRDEQNVC4EGs61IyvDik5DQVmOFVVZEu7fy6jZZMy+VMMsJYGITcKvB5KQHqbxHhcwFGTYElAWTIGcYodMoNBUe7H8Yfb9KLB60BruY157sCH5Borp1c6EgsecEaT90MA9G5046Seq6LwAkUbk+oQUHV9GdWS/n4r/3JUCKTWJVfcCp664CY7vPQIXDYi9k2QNKYi2EHyGgba6YWy4cRP0tx5DTmEG8jwKEqytYWbay/tUpN5g4AZZx511brz4Y8Lq1A5I6Q6inx3JuBVJxYXg6vdopw0J2KA7PLDsedS7JkDDch1p0JjK3dIJxNvnIZLuQr7LwLdsCWz3WvHOByp+My+BFKbPkEXHPHr71d1OMOWRyfCXVQCdxMHFhJnI8bk41piFnlWbMGNgEh5bvxq3PrwA7d1NaNryEfKWXIn078xCIhBDPov6V1/ZcchtQaJUwqGfJHDL6q+w4FiUKepDjfskOqMWdIfjCDpt6A0m0D8UhjyvEtq0cbAEV1yyJk9OQ2NvADkJD7xyDiJtDeieXY03vHZIBVZUlmg40WRB0JfEH70qHnzbCR8RQ04H0hgJm1uG0yrBn4jj021lOHogB94ZSVzPIiyak45ZN3rQuG8Q3YeAaMUw7vx4PSw9cdGqkWFJ4L5tLoRvl7BwcRcWbT2HbiLg5wTLNuUI5iQTrD8N5U8th1xTita6VoQdDsTzvLCW5kDyfPRzwykxH7uCSEt1wGF3YOn8ctz46BwceCWKTUU2fLl2LKQZdMjrOlauOYxX/rsKId1GSAWsRCVnKptfREZ4iJ1DMfBQtQ/3ZybRTIiMhqMo92ZDdzONND+amwYxFPegcmwGAsGYCbuTd6ZiWkM3rvttJ7bImxHVh6HAhWtvm457ssKo3XQKfd1DKCr2oul0P/sM6QuRMBqLQrEft2DlNVcyZRJ4/WgtBvQQDv3gHLb+tQ7lSiUq20qx5EYDv24uhDbHgq7ibDzy4+N4/L1q6F0MO+F0gKhiGMTvmgB+VxRjsSpoVW0YbO6Bt0DFF0fPIcTWyazG9o1voLLIiyn//lOkZ6o4OSiZaPad33bjbcvrSEtmwIlUsO8jSb610e+AlcFqjBL9+D/gYFNk3QRZxIXeDEhF33/MsFmt8BY5cOpgHyZPz0Lw/xH/mefWpIsXiiLPUoopzho8Wz8BEx5owdiPP8CpFfPRevUUGGxYxUVhvFoVRB7RppmYnpvGhkXy9qvCKCY5PVjNJjkYjrHhGTh1thHDwTDSvUlseZpXrynHBl82ap5+DD6oGKFyIyRHMzTE+GxnylxT6kSsL8jGGMOgnkRVSQYOikabaVyxJnYuAa4BatgC92YZMWKeypgnmYsJXjZuWCDH+6H4y9BcxoLddwbNegqMRWPxuyl9eLKIeRogJ7JbUEyvOtIlHHghgjMth9HRH0ORJQvFC9jBeZPSwmx4Ur2oGJePk5t0dMlWHPBKyPnkA/rc8002yz9WGoo9Ko62RxCmA149+RBO1XVje/0QnIK5FmWlID/HiZtn1eDfLr8UA4NhhojECXb63ocgS4pNHg1oQkFtN8IZNvi5rJv+fwWOVvZjgqygWVNQUeBAVqqKYIT4HDfIThV4b2nHwgdd6Not6tUwO3g4rsPFBf7urtM49VYBblu3HfrsFMSduZDkf+ZZ4pMgOdbciR4U5KXiscV/Ri9Z8fRxbuTSFjl+mkStXcKZXV04Wd8Fm2I1Twyjj/4ooVfCXMZZpEte1LsPIYdN5Y3+H2BFiR1nhi1sSAoK2JSS7LqC6NlpeJzEbepPZfQ9uwgnD7ehYEM7GaRi8idFFD69/smeZuRW7EV1/A+oWDmMw2+tJHU+byLT6AedjPPDUaSRTlfl2ZGd60ax14bLJzOFWvyQU6fZ4Kl2oMfhR2NLDxuG/nUADTaLbFQjKg/DafSi64dzUbPTh0/PB9nQgLJcG/m+4EPfJHZiDpAJs98/SFq8qBoWLYS/7T0Kj0NBU6cPv9+wA6+fnI87Dl2OprW/QM0Xh1H5bgx733seDr0XVsZ99ELSGeW36zqwaEk5Vqd2476bxqHuBLODjECed1UZ8qalIPNKF/oOhtk55Qs1JJlRUNkXZuhX48Tcy2Fz5eLKQ61YXJMNN9v+cDhhelW+cLPRw06UYS/JzkRH5SmMzy9FiTsTqdIGwm8vfrBiATraEwgO6Ch44DtocJ3H8ne7seRXPtR+/CLOLfs2VKKDHf1cRwK8CSJkwm8f6IO1bheije147VSfuTApU3vEuNVyGU5UnUbfSTJA48LKJUHadLj1NhxfvBShe6/HXdftRt5VY7BgVRHSqgmLMdIT5rUwXFBsk3aPGi8FSw0Sfc4NdbPGKhFiQ/JHIugNMJWUkftk5jqw9nsH8Mn7TaiUilCt52HjwjQcW5KDaFcL0vbVIauhEakDPvTFA7hh9mTsZjNzWKKIkD1IM431RtVfM/D58mNwy5zdiOdWI2SGsXN8FZrW3I7yWhWLX6wl9FWjgxeyEutXtZShuzlqzgEq8ymRTJp2jx4tTQOzHfj9bUew//QRyGQHcy6dittemIBYIGlGWbBKEcBrx76BchSQqqSC4wMuMUpQ6+lEYOpUtE60wUeAiitJJDkZJsmzxFEKGbJ0HVYbHYRLq+pgi07BUEkheucQ36uqUHxWxtWvNmBMzIZuwuaA0YMMHuctt+LOI2Mw3Jo0511hhMh/kU6JC/OBIH1iIYY9iUidDR8938aRUMbilUWwTYoh5E+aBTox34rjtRrWL2xDZtwN0e6OkxPzTKTQzBKpAL0Gew9yze/6yZbHgwiII3yeBCnnl1EjwanFzYxTWTsZrSFUfOVDdiuRg3KBx12ChqE2/PzDcTj+YQh2zor7/urDI+fGYqAt8U+wJ+pBzM6xWMKcn+NkiFGOlenVRCEW+9CxJGyTE5iSLuMo8eKe92KwPOXD4s4YfBYbmycHd/51op3mGkhnX7bT2yKmCvNC4V8cMbY8FSFmibL4yb0MnVjRUZRiItq4wgA7QEraRGJ6GAeHGvHS0SlYf3szrr03C80HImaOs3xN2eTiaGkaL1CINSGmNjdnhQEySG2QPaGIlDnA/sBDx5TyPH5/w5c27KrnkLR3APptaXCeSWDapmZ8yX4jC5VD0GVzCRYzGuI1k4emjzw0WiDmRHmIB+xj0KJcZx1D18nG1YsOdr0o2uJ+/PvaEjx73XmcqA/RbgkOj0gZySz2KCWVv6OQYaoRuIBCYlkC0VQy1bhFo9/Y/jnkvxxg09vqwd5aO6oK6QDmkdKroXaGG/VpjbAzClHT0ISpaoRptmSKJ9IFbJRGvafTwgwDfQM2aq7Hwj/FDBUbJ8aVOdCwPYTGthApggPj5zux+91BOEU3oreFWCUsDQpP0/MONjFxU7Eokf8p/N5iY1NLZbpoEopqXVj3uZNDvYxXfgH88DYV117lghZiLvUm0VGQj5ARwKKnZ2Lidfno1wPkQpwDpJhp2T8+xGirSJDwrx6m1jNInJ5iw8tPVCKFMshrS9vRTaqweEcuwj1itBxhoS6miyheIXJZhfe5KGISShw6WuwSLvncgTOH6ZhsGSXTOD/k0WHEcC+P6ajI5BjHmdSvY/ZcD258exlpdhKli/Ix9cdlOPzCOZzZ3wl/MMQ6tV2Igrg+0c/CPnA9NhkXFyFWJLKu1xjEPMcVGIqGGBkDk9noPjzUi6t/lo0Vz+TAoPGBoGYqExdFLhMSzTSicEWYczok3H6ESsRBzq9chKpoSC9QwKEKzowkjyPVOCBhiJmZVqnj0axuVNoVtPclUZ5vQ3GWii6fhpCdvYRjXx+7f6A5QqeyWTIY6cVOeCvckObjXUOsShhup/eyyzyoeqIQyivZOLmzWWQisq9zYMWfc0EGjH5OUjamhlDhkqPw3uzAdEBJmoEnTgNP1nNWjDHVqEaIhidIvI1YnpnD+aGfw0gfCSMHoQerfPiRN4oGvwrO/KguspOecHJLGOawYyqYrKVwVDNLN4MKhlPIG5ooa6bQt56dYobdzVV7Sh3IyLIhjfJf58c6jhVacPO2fEwe74CvLQYhRrpcKuJMlRhvoCojApcwfgKnr61UImZudNFTMmxMlxhh58qxUTwzmwoGi/n1Yxqe2MiDOfteM2MYz+SH2ZnJj+J25PN4j1OB0Iy1xIhjBC1TSKHzHVGcDFqwpd2NfZ2Umig4iGwZY+eIdKyT1FEgCQ9WWRE6T0gytweZv1lkfUY3wxhLmjThog5qNi1CYYzHlXHK72ESLieqHKdEaEljGtJbBnP84aoYnq1JwEh1E7Fgpk/b/xr4jCPjJG8YXUkVOakKHSYLh5p1JKIqHCPR++XuBN46o+CRfS4MM5UmjU9i0hgdXgpoYL/oZPpJx1oCxr9sRppmGijkc/F+ND0QofNaDAgR+646Ge+fcZAuizpiuognRkgllvU9GEDvgA1jGdWk8Cb5vtQq4fsfb8Uvr/42LGrMLPbR1xb3kpMaXKQNc9/zoJmOu3dxHDO9GvqpXCsJlZRHxnnlPB7ISGHHp5pwMWQXPSzQRKjLLtLf6IXXF3Oeh6OMUvjzbTJW/8WFhJ+5nMlruBj7OMycB8+z+UP4U4UfNx/Ox6dPtcOVA+x4fytecX6K6t9/GwX0YFsS/6Ryq9TWA5TUJ77sgftaA+unh6EFmZIh6kPsUHusn+GAvh9+TojP9boJENm6iUFi5QkiiMBzq3IBz/WRwhbfiw2N8S4dZMEYs9mFlW+loCpfxrXTOWnRcGmY1SZykp6WeUyYw/qfz32G8DoZ3/vNtzD3nkKo5D3xD/14UrsaLbGQyWJHlO2RexucdQ3ee8Z/pcK+Avjt5Ch6KaA7dRXjMo7gV/pqbA9vhT8axFrPXVTqXVA8Fhf6SVOFsaL9C4cYo3ZbNH5QSHoYYAQu+0zBoWa7qVDPuwm4ezoEY0fXm0B9gJ2XvCdJplE0No71i8JoMi6B9CMdO+dvw+ZX92HH9l14+Z3bYItnQFFls5uHo4kRR9lVjKeE7n2eMuL1wC/zoxhmPY7h+CkA44qu9XBxtr4urRLjLBl4L3QM2bZUyL3BYXqZ0MQLCCogeIyZ8zQ+lXlelpYkcaNq/GoKDp9yIsPLIbuGfYD6D/VXxHqBoy30JKVDGwXe564YwI5Jg7D20aD7ktjbcI4ClQPfeeYW7Gx/E2cpMyoWMlECBhVUjpcy8rLTkGON4e49doTzJCybGoeXmyJvDjnx6Os2vLKqj2GPI6xH0WMJ4LNEE/bHz6KHEpCcGFBGOhhG5llhuMa8r6TWudknI+PtFGxg+1c8pAY5TC86SKXuE2K3fJieX7WOZ2YpuGuWD4em92MyC7U1acfYigQO/9yJaYW5rCUdZ042o7ahH++8/gK6mo/CGiIz/UxFSUkaXn3rA+6TqXj7ICncVUBrixUPrpZQPO0MfnHfOUzs2wmpIw/VkQLsOd6GhrZeKM0uxBq5hWVIf0+XBEM5gchykJJ3wRYVPQHCouCGYsuMXTcRIUUmnW5vpcOJCMLrc+aE8dxYdghi8/mEHTkZXDR3Yg48qeLIuz5om3PxH8FUaBMNKhIJfLBlC8mTFfeWNuNSDuk/3fI7Ktyp+MvVN7ILMqKUhrxzmvBMj4H98nnsJa1umd6CiS2pONLUSXt0HPjuo7j5i//BALfBFJMCM8/zGLKYVceCOjv2HaBs6L3AOTJYkUNWEx416jq+RjJQyihjJibwwmV+yioGWmi4h1he6BQbCEQMbkXt23kEamEAp9qA5p1lyLmSewbkLiX5OZDZSGvWbcD/7KpD585z+PPuP2LpLjpyqYSfzehEb28r3pe/ouTfhstnVeHNX6zEmAdXIjs7w9xI+4/tW6B1C4mf7lWIueVMlzVNlEdeS8MXX9oxs5jNjNK5JISyAGUWAZGi0XBbSM2y4Kn5/dhbMQQHi3aQQ09JNpU9t2Jyd4EsQvMsySvGtHUBVJRmITZ0oX8YukkTztS24j/XrYOzK4ocdxzlkypxltPsQvaNjo79qMVmRPQgJ7IMHKfUs9S7DvPHjIdxkvXJ/9NHu9C+w4/gnjjkfWKHhHj+0ud2uFhA/02au/xOjn4kWGK7U3RoKT7CZ26dHkDdrE7MYkdtpkZflM1OShlRNCnNbEaGOQvEWTtX/UHBl1QX7HcdRtEy7o/R8jSStf1H+/GjSw/hBstP0HRiK55b/xKG+ukcyjNFGw/jS8s+uIx0XCSYGvlEZIhOWR3EtPQsyMcJAueJmA0k/sf4+u63U5qt+VJxxVReJCuBADfUCnji2SG6ilOULnB5SgTrCqgYk0G2ay54WRMuu2ymSyJpjOLnI3tmoiGl6k7cevY8yjOrcYm6ET/7YxlSCmUTScIM5xVZK/DSsXvgirhxXecPIMXeQKO+By49/Rswbi5E9BaPBf0vhzDzllxMKivEno5TcFvVesVRkNzkyVV+IkhUJrvjs7U6Al9Q80lIyGJRra3oxTQCVSOrKyvFwp12i8lbBHbHEyO7loK7CHlFYyMa2Ue2jOhCtgJ8susM6mI3mtKiws9bOwewcg+lwstySBDjGFIG8Xn7u6QEb1B55qRHbDb0b1Ibk534NRa3E9GjGq65uwrbXjrGvTjUc5vVmOcwtN0qJcIhzhUxcjvVbeDn1PhvdUVNpuimipqVajEvlfwH3iI6t6AbKhudoNnJURERFEQcv7/+FL63aDKvP4wz56MoLM9GhPsGpnzL1v1vPXciev9rOLYsgJmrHkdQLoa5rzvqId65OE/k3ZiK3tNUClNYoBZlnBy9U9ozzAzpOUJ2SUK+rGYYX13Ri/lEpTbJhdx0i0mFdWO08SM7lYJWmx3cqTKb5Au/n8DX+SuaYkGREzvuDmKy9Q8oz3sHNksKIThi2id2Z8Ult036K6bU7Yc7twgNt9xBMa2ZbFb+xgLE2O1rj0LmODrru2Ox8EfV67a983DzyJ3WGp4pxbHd60p93B6W0UMmmUua66aOw5T+ht4jSF3swigpNvsukrCLNZC4MNgLddrQRwZ9uTEFDccHMGacE1nc8FDGJhALj6gXIv3yily4e9w2TGhOwTu/uQztjnbMeGQtWSmdKmVAzHsmxxELtmlYEJ5Z/xw2zIf0wdDIpLz98ei2VXe/2685HKpLnV1Itdlikb5OB7FKcSONixBeFosQ84HxL351oFgsI9HgABClgYl+FTEjgcwyG7jjijANj3Hj0MUFWq0jURO/kehv82M3N1iu3TmAzNY01K5dgfayfDhaW5Hq76RLIxQamK5aYl16S8qd9Uuf/fuPPUY/Gs77ijlGriHAVPPrqRc/F+giclrkemykii8M8MY3yk2jxDgi8BI1hknUQkIHtZh7ZyBNFlGh0g7VzlqjRxUzikRAwuVNZX/BGEqLU6XJXHwz9IKpqFuYi/PjYs26v2dT+rFzH7RvW7BntL3/B41Ezp+M4ooqAAAAAElFTkSuQmCC" +} \ No newline at end of file diff --git a/agent/templates/chunk_summary.json b/agent/templates/chunk_summary.json new file mode 100644 index 0000000..c18a68a --- /dev/null +++ b/agent/templates/chunk_summary.json @@ -0,0 +1,493 @@ +{ + "id": 24, + "title": { + "en": "Chunk Summary", + "zh": "总结切片" + }, + "description": { + "en": "This template uses an LLM to generate chunk summaries for building text and vector indexes. During retrieval, summaries enhance matching, and the original chunks are returned as results.", + "zh": "此模板利用大模型生成切片摘要,并据此建立全文索引与向量。检索时以摘要提升匹配效果,最终召回对应的原文切片。" + }, + "canvas_type": "Ingestion Pipeline", + "canvas_category": "dataflow_canvas", + "dsl": { + "components": { + "File": { + "obj": { + "component_name": "File", + "params": {} + }, + "downstream": [ + "Parser:HipSignsRhyme" + ], + "upstream": [] + }, + "Parser:HipSignsRhyme": { + "obj": { + "component_name": "Parser", + "params": { + "outputs": { + "html": { + "type": "string", + "value": "" + }, + "json": { + "type": "Array", + "value": [] + }, + "markdown": { + "type": "string", + "value": "" + }, + "text": { + "type": "string", + "value": "" + } + }, + "setups": { + "pdf": { + "output_format": "json", + "suffix": [ + "pdf" + ], + "parse_method": "DeepDOC" + }, + "spreadsheet": { + "output_format": "html", + "suffix": [ + "xls", + "xlsx", + "csv" + ] + }, + "image": { + "output_format": "text", + "suffix": [ + "jpg", + "jpeg", + "png", + "gif" + ], + "parse_method": "ocr" + }, + "email": { + "output_format": "text", + "suffix": [ + "eml", + "msg" + ], + "fields": [ + "from", + "to", + "cc", + "bcc", + "date", + "subject", + "body", + "attachments" + ] + }, + "text&markdown": { + "output_format": "text", + "suffix": [ + "md", + "markdown", + "mdx", + "txt" + ] + }, + "word": { + "output_format": "json", + "suffix": [ + "doc", + "docx" + ] + }, + "slides": { + "output_format": "json", + "suffix": [ + "pptx" + ] + } + } + } + }, + "downstream": [ + "Splitter:LateExpertsFeel" + ], + "upstream": [ + "File" + ] + }, + "Splitter:LateExpertsFeel": { + "obj": { + "component_name": "Splitter", + "params": { + "chunk_token_size": 512, + "delimiters": [ + "\n" + ], + "outputs": { + "chunks": { + "type": "Array", + "value": [] + } + }, + "overlapped_percent": 0 + } + }, + "downstream": [ + "Extractor:YummyGhostsType" + ], + "upstream": [ + "Parser:HipSignsRhyme" + ] + }, + "Tokenizer:EightRocketsAppear": { + "obj": { + "component_name": "Tokenizer", + "params": { + "fields": "summary", + "filename_embd_weight": 0.1, + "outputs": {}, + "search_method": [ + "embedding", + "full_text" + ] + } + }, + "downstream": [], + "upstream": [ + "Extractor:YummyGhostsType" + ] + }, + "Extractor:YummyGhostsType": { + "obj": { + "component_name": "Extractor", + "params": { + "field_name": "summary", + "frequencyPenaltyEnabled": false, + "frequency_penalty": 0.7, + "llm_id": "deepseek-chat@DeepSeek", + "maxTokensEnabled": false, + "max_tokens": 256, + "outputs": { + "chunks": { + "type": "Array", + "value": [] + } + }, + "presencePenaltyEnabled": false, + "presence_penalty": 0.4, + "prompts": [ + { + "content": "Text to Summarize:\n\n\n{Splitter:LateExpertsFeel@chunks}", + "role": "user" + } + ], + "sys_prompt": "Act as a precise summarizer. Your task is to create a summary of the provided content that is both concise and faithful to the original.\n\nKey Instructions:\n1. Accuracy: Strictly base the summary on the information given. Do not introduce any new facts, conclusions, or interpretations that are not explicitly stated.\n2. Language: Write the summary in the same language as the source text.\n3. Objectivity: Present the key points without bias, preserving the original intent and tone of the content. Do not editorialize.\n4. Conciseness: Focus on the most important ideas, omitting minor details and fluff.", + "temperature": 0.1, + "temperatureEnabled": false, + "topPEnabled": false, + "top_p": 0.3 + } + }, + "downstream": [ + "Tokenizer:EightRocketsAppear" + ], + "upstream": [ + "Splitter:LateExpertsFeel" + ] + } + }, + "globals": {}, + "graph": { + "nodes": [ + { + "data": { + "label": "File", + "name": "File" + }, + "id": "File", + "measured": { + "height": 48, + "width": 200 + }, + "position": { + "x": 50, + "y": 200 + }, + "sourcePosition": "left", + "targetPosition": "right", + "type": "beginNode" + }, + { + "data": { + "form": { + "outputs": { + "html": { + "type": "string", + "value": "" + }, + "json": { + "type": "Array", + "value": [] + }, + "markdown": { + "type": "string", + "value": "" + }, + "text": { + "type": "string", + "value": "" + } + }, + "setups": [ + { + "fileFormat": "pdf", + "output_format": "json", + "parse_method": "DeepDOC" + }, + { + "fileFormat": "spreadsheet", + "output_format": "html" + }, + { + "fileFormat": "image", + "output_format": "text", + "parse_method": "ocr" + }, + { + "fields": [ + "from", + "to", + "cc", + "bcc", + "date", + "subject", + "body", + "attachments" + ], + "fileFormat": "email", + "output_format": "text" + }, + { + "fileFormat": "text&markdown", + "output_format": "text" + }, + { + "fileFormat": "word", + "output_format": "json" + }, + { + "fileFormat": "slides", + "output_format": "json" + } + ] + }, + "label": "Parser", + "name": "Parser" + }, + "dragging": false, + "id": "Parser:HipSignsRhyme", + "measured": { + "height": 412, + "width": 200 + }, + "position": { + "x": 316.99524094206413, + "y": 195.39629819663406 + }, + "selected": false, + "sourcePosition": "right", + "targetPosition": "left", + "type": "parserNode" + }, + { + "data": { + "form": { + "chunk_token_size": 512, + "delimiters": [ + { + "value": "\n" + } + ], + "outputs": { + "chunks": { + "type": "Array", + "value": [] + } + }, + "overlapped_percent": 0 + }, + "label": "Splitter", + "name": "Token Splitter" + }, + "dragging": false, + "id": "Splitter:LateExpertsFeel", + "measured": { + "height": 80, + "width": 200 + }, + "position": { + "x": 600.5891036507014, + "y": 197.6804920892271 + }, + "selected": false, + "sourcePosition": "right", + "targetPosition": "left", + "type": "splitterNode" + }, + { + "data": { + "form": { + "fields": "summary", + "filename_embd_weight": 0.1, + "outputs": {}, + "search_method": [ + "embedding", + "full_text" + ] + }, + "label": "Tokenizer", + "name": "Indexer" + }, + "dragging": false, + "id": "Tokenizer:EightRocketsAppear", + "measured": { + "height": 120, + "width": 200 + }, + "position": { + "x": 1136.0745258879847, + "y": 202.22674640530906 + }, + "selected": false, + "sourcePosition": "right", + "targetPosition": "left", + "type": "tokenizerNode" + }, + { + "data": { + "form": { + "field_name": "summary", + "frequencyPenaltyEnabled": false, + "frequency_penalty": 0.7, + "llm_id": "deepseek-chat@DeepSeek", + "maxTokensEnabled": false, + "max_tokens": 256, + "outputs": { + "chunks": { + "type": "Array", + "value": [] + } + }, + "presencePenaltyEnabled": false, + "presence_penalty": 0.4, + "prompts": "Text to Summarize:\n\n\n{Splitter:LateExpertsFeel@chunks}", + "sys_prompt": "Act as a precise summarizer. Your task is to create a summary of the provided content that is both concise and faithful to the original.\n\nKey Instructions:\n1. Accuracy: Strictly base the summary on the information given. Do not introduce any new facts, conclusions, or interpretations that are not explicitly stated.\n2. Language: Write the summary in the same language as the source text.\n3. Objectivity: Present the key points without bias, preserving the original intent and tone of the content. Do not editorialize.\n4. Conciseness: Focus on the most important ideas, omitting minor details and fluff.", + "temperature": 0.1, + "temperatureEnabled": false, + "topPEnabled": false, + "top_p": 0.3 + }, + "label": "Extractor", + "name": "Transformer" + }, + "dragging": false, + "id": "Extractor:YummyGhostsType", + "measured": { + "height": 84, + "width": 200 + }, + "position": { + "x": 870.1728208672672, + "y": 201.4516837225608 + }, + "selected": false, + "sourcePosition": "right", + "targetPosition": "left", + "type": "contextNode" + }, + { + "id": "Note:MightyPandasWatch", + "type": "noteNode", + "position": { + "x": 1128.1996486833773, + "y": 342.4601052720091 + }, + "data": { + "label": "Note", + "name": "Index summary", + "form": { + "text": "Using summary to build both text and vector indexes." + } + }, + "sourcePosition": "right", + "targetPosition": "left", + "dragHandle": ".note-drag-handle", + "measured": { + "width": 249, + "height": 128 + }, + "selected": false, + "dragging": false + } + ], + "edges": [ + { + "data": { + "isHovered": false + }, + "id": "xy-edge__Filestart-Parser:HipSignsRhymeend", + "source": "File", + "sourceHandle": "start", + "target": "Parser:HipSignsRhyme", + "targetHandle": "end" + }, + { + "data": { + "isHovered": false + }, + "id": "xy-edge__Parser:HipSignsRhymestart-Splitter:LateExpertsFeelend", + "source": "Parser:HipSignsRhyme", + "sourceHandle": "start", + "target": "Splitter:LateExpertsFeel", + "targetHandle": "end" + }, + { + "data": { + "isHovered": false + }, + "id": "xy-edge__Splitter:LateExpertsFeelstart-Extractor:YummyGhostsTypeend", + "source": "Splitter:LateExpertsFeel", + "sourceHandle": "start", + "target": "Extractor:YummyGhostsType", + "targetHandle": "end" + }, + { + "data": { + "isHovered": false + }, + "id": "xy-edge__Extractor:YummyGhostsTypestart-Tokenizer:EightRocketsAppearend", + "markerEnd": "logo", + "source": "Extractor:YummyGhostsType", + "sourceHandle": "start", + "style": { + "stroke": "rgba(91, 93, 106, 1)", + "strokeWidth": 1 + }, + "target": "Tokenizer:EightRocketsAppear", + "targetHandle": "end", + "type": "buttonEdge", + "zIndex": 1001 + } + ] + }, + "history": [], + "messages": [], + "path": [], + "retrieval": [] + }, + "avatar": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAA7ESURBVHgBvVpLrF1lFV7/v/c+j3t729tSClZCigXFF/JQE21CaKITHcAEica5UwcmDsW5A4mJTIwzB5iQiMTIwAhGQzQSrKJIomADWkpLuX3cx3ns/f9+31rrP4/b2xqNsJtzzzn7/I/1r/Wtbz12g+y6Nq7kB1KQB0OSh3KQY/IeXV0QyXn5Xox4JZEQ5BR+OhUr+fbBYTi9OCaUDxs5r8umfAvjvy7v8pX9lfRvuP5g/Fznpe/fjVMc5GC4KGU2hU9b8izWu1vepUsFVi1nCD4XOha5YPLFw+S88Bkfe9nWcKFPhVZO8hA2H5p/t4RvQ5YJ3qfYfdpl6Vz9lQsfY1aoVPhWQdJQXrgfgonMt2mYmwGf7k4NZOZvGzv5WGrlH/J/uqjlhB06mWOaugSOZ9qm0LxbJ8LILvqAyohBwXCv62Q4QU42M0L0uGAlDDtZp85O8r9cXIp7Usu0c2vAVqEqOuCS0PauQuOUHDNeBEV2APl8npFTGgg/4RedE0wZZU2Rh+r/FjozocWwnJLdzS4kNVfPhM66ZcX9k1mHcGqp2jAXRaG0hy9z6YkeIsjU77X8PhfmwXDhym7yuvbVFngk8yjOrKqwDA+auUiTjGlaP2hKjulg88QhwbkhOjtxUVi0wEatgVfNw7uV6B88FMfX1xO44Jnazp1tqJMqM3mRMwY6XFDOznhvk2mc83QRh0oVDehVsIl1DDNqad25GzEnnkB0Qo2bFAYjE9EiAV86P3x9TaFJd50oBExos9lM08BF5OBg9yn0GBNVyXl2MmcZ+175IdxlwEpirOTxgHPH2SxF8fqYMB4lxeYA0jcucKuWCdIrFpgJzYmFOajRmSMGv5cxwTWSDZedUmPRpMPJfUEVTh/wzx1kmXTcJ8FJo2qRXpI786cpBnBfKivAnBvjIOcuiawNsxwAfvfvMxKgEinrlMp4Ez7Qdtmpzk0splmFqdj37EFzUhwyzSNqED9oMCwbv5vGqWGu35JFqCAeBHDsctZgRX+qYKVhL8qwMcEmrej4BlZeGwR55WySmw9EaXF/fdXSCyqP64Uzl7N6ZMGllM2doAn9tgjtjut0rRYhPConlMoRxbHTZBrvDFMQymiQkyk89xjA/isQsAWwt6dZrVpR+zkBMkEPOMXmB/cF+dvZVo6s1Wq1tZ6xhir+LVhAFU8BHD6dzCGVHB5pJrQf0IWecb36gEVbHjLhz1QZK+piWcwCBODKIOohtzCAAtKzAjRGeDYYvn8l43BRxnCsGPieZf8wyN/fyrK+EmTYFxlWJqPSaHQtU/DWBciO9Rk8qmWoRJMZZnXtQmsJuMZffI8KxZD8EHj1ENkq3NuBSSdtmlmRizbQCK3Zg1aGyNzWIewTL07lCx9vIHSS22+MOjbjkJvbQf3q0JpRaz0N5ojZo2NwKFWqbjhtMFyQ3wMpjfQILE5oHTofnZH01kXzF2iMTjrGepzbRIvQV8aGfVULxkQIUymMIiABrQNKPz7Vyi0Hgvz0pSSPP9LI9389kc1JlHtviXJxBMvhoJex9irGXt6GVWCNOHWHyTErLGoKDS1gXekVGqSFIPgOKG0TPDfiAfDaxOm326xQ6/C+Bc+6MqElgzrwCL9f3AFUxjZeU2PgftAkObwSsb7IX8508qMXkzz6zES+fF+Uc5tBHr6vkm8+PZGvneiZM6s8QXbAIO87FOXtzaQ+SxoO/7oECMU5vosvkFrbzpyxhTdmwiOb8MRe7TzO74wZg8ZwrTRJ1oG26T4NhcaC+oJW1gdZXt/I8uQfsjxyT5ZfvRp1j2+cFPnOLzusGeTkHUHuOhrV/146k+SeW4OMp2J0jc37sNiFTdDrwJ24MshajpONPaaOU0bD7Za0lzRokBm2W7NI7XzPg6gfuNNTIfztMCjvZVDg8SMRB8ny8psQ/I+dfOneCrSZ5Zk/Z/nM8SB3Hgny+9eT3H+8lgNDUQ1fHInccRj+0mlKKk2TFYK09hBauQQk7Ou5EzPkt0p55OVkQQpCjTtSpGl/GxQz4UIVgw1wPqUf2EGUofBnMAAt4pSn32nl+OFavvdcJ1/5dCUHEYieBExuWs9y4rZKD0EK/eJHKnl7K8udNxnUpmQprH3DEEFsC76B9wGEpUWraIqhr049dqyCTsMbF3NOHr5png4LVDgQnW6nNUgQf8UPxq79XmW/E8cNPlMbv3illRMfrORpCPj5D1WyD3T3879iLfjO7UdQ+W1GOXulk4fvqbBmVM4/OEhyaWRr0KEHdVKokhTqaL5I4Sk0uWjcJSWKXmMyRI2oyQISMbqGTQErOKOnD/i3A23TpJt4TaeEW5bHf9NCU9A4TYv5F7aYo1Tywmut3H97lB/8tlPtnTgGwXeyvH9/LZ+7MwD3lfpQhMnWeknxf8MKIiys11fhbde+sxSVtQViGIGNyHyHVk14yaZcdWLNF8k+oKERoPLaBct+Jh5NYwkIGHMzaO4izP4aHPED6wFMZEU3zUtn/uHvRD55q6gVD8IHju4PeIE5trMqatgPEDwr1GgB8nIuuVYMnucUMoBFIOwQSqIfMq4wcvdqMpwF3VppUgwipLoLW3AQaGzQD1ra0bRqKqw8BPU8+rOJfPVTNWjQNEPqHSUmeVExedfRTj57rDIYtBauzm1mzfNvQDI2AButNpaY55IAegozcXqO0QIX/Y1WzErLIIhoySJnU9kMospCvLU98fQWwrz6TlDWWEVE7DrDPVOEd7aSvHIOGoQAq4Dah4HrTXDzPqQGhNwAsIg134N2GTYn5hs0eZ9FCBSxqgLNk0diewxNEwfRqznmpoR0v7YYwFgwmVoKvor1eI/s2JZoTmpidOWiByD0QfAr04pLgMpl4h4njZhNS3wUjPH86SQfOwptwoOfeDHIC/9kbeAlJg46gWM1cDQ0oaSPoNWIpQn7Gk31VPxRSyrke+UpylSjfQU/GEIh+wCVqjZmZPQnjRrsklVtXguoBUYTOhPZB45TWyHx/GnRJGp9aFYZtRY5iatDQ4T2cVJTHx6SsbI6OrVNndRaxFgE56H7zDobsySxramEBp7O0xbs2US1UMhWsDBDoJxkoaaxd0tWli87ALxmMuUilqjxhOe3wefnLUkb9ixl6HtlRV9IweJBla0HUkdbmokZWyHcsIZCmrrS36ggK947nUu/6yGvYDwoiSHxT1ailAo7T2lK8a81BKuyMK9F+Fk5OcXCBpaFruOX1T5xbKXeIEaLgtgwQaDR2MpKmrUO3udhskc/8Hq5Bga0WM8UxKqgCG5cqRvVNq9xZ8lhciZb7RncyIrZy0zN6MmGwQorjQmV5VvhzJWUmTWOJ97PsXRDg08PePzT2VqmgBgZJCEyM73vBbeA5u8mfPQqnywxxMGowdJXUCjBEk1txQ+zA8KzbS0BIy0OqG2vu5OIt2vs8FY2m2IZGzguutK0IgteZe8A1wxmXOfyyALUDjR05lJE8gTsr4imsBq6xTJVa51YK3CtD+1WSe+TRusqanSNtWGbTDL29gq1TMg2XvSr0F4IKaSy5UC+lflCtFqZjFV6p15SWnlIjTC7JMan2WBCLW/AGm9dtiKc3EvaLO3BFZicGSb5nRilX/Qb1rPWfbDE0DoWTAnUqRurLcjtTL1zMkuVJpl17uxgjacq4jnZ7i5WeGPD0ungNe32xIJOD0dmXLCSLyg1nt+MyiRUEdMIpgIkT0KiD4ekcApBKZHUDqB5TmN1MAOQp10e5oMmaFrGeuOrroOTQNY0IKUou6/sVgpvMJVIppHoPLU1TnoAb/nA9Eyu6EmtLsy4q/UtoNFnzq+sJNpHmpQEkGZvsrHMQt+wYDvxA6yl1SBhUxmZKJWWdku+Sm47fLBUhaShNMpNK2cF1q1TBJkdUKvi14v3WZhXdgE9At8hF/orbROvuGp7N3osjQHzC7p7l60c5Td17Eo08nMf7cZdJTTGBzMt02iSju6t2aqnrKygqsoaVnSuDhuxqGFgI9zryoOJrh8N297vYUrATLLBAGK2OGSbC6azYpQsxnqD1Nproq9nvSIVPO+CSJo/xWnckSmAte/5YzQLaCLXWYJFrq1cCaOJZaRNLF0E6zp0zF10QUuqepW3Bl1wyd7ZyFb0U4NkLiqjV3vwCdZI2I2S4guWG9EXSztx7/G1ndYgwbqTuFYqIz8jNYidWad04gLpr2f4Y2HdLbXZDc9aP8/KSz9kP+jx1RKq7WWotLlQpzk6g6MGrmQN3flTgeVrlo3ytJ22+mTeTpH584Au2XMsNpo0Z59p23GarPXVOpOQMpva1o3XwrZr2xBmlKkw0edoMiturnUZU5XFskdJb8gWK+SFwdl0aB3l4Bsl8a5bVGyTAntaY2T1YuX0XXZvHV6aHmDTQaFfZsX5aussabz0YLOlF8vtdYb8yHTYqKfNngGGmbx6aeuwk9nTRgrL4EUGC154e2zaU9sxWN6kTJVZhwdL4iRcnW7u0jgX0v2jDa8h3Gnse0w3ScbZtQcjzd7zvOViEHUiFndiL/ipDuaYeUHogjCmJNnX7lU2j1rpsiy05pevsr+25sXK2tYF96Yh752KiDVPLU7M2R5vUpuWf2Q1m2nfn3kh8Rr0LM+3p4lBhV+8Zm31TtTZ2W1mJquMkgqMrhbas2lVYs8F1w65a1wfkiQpD0lOhfNX8gNoKz6b93AyfUiXwsyRzdt9s3S11mba9uc6zBybELz+Ddd9Mk/6pHC1zIkj+XMz1/bV8rVyW7xxLTxXpfBYWTcsPFBOnqN0IdtiIc8ebuzWXOnoaS2rPZ6gwrcOE5uyNxPxGrjw3Id9Yr4rhNLewuPWY3hSf1pX3NjI67mRZ6f+yLUcIl+HDWxzOxApsCpPZoLxf8rlicJe8+ZPcbxKVaFdMBU4XMeZcbJTcbrwXw34ATX1SYj72NK4kJcssiS4czSDDjVeOzW2M9qM19rcfMsFTQvCc6uC72td1HwR3pdbvt7cyceqLj2Knz6Br2qRRUvMtBesn6S4l3xd7i6CK6MkrVPcYa3bEfN/mJrlNPTyFIb9hJBf/O3fQ3B6D7564aoAAAAASUVORK5CYII=" +} \ No newline at end of file diff --git a/agent/templates/image_lingo.json b/agent/templates/image_lingo.json index 0f874d7..88dfc6c 100644 --- a/agent/templates/image_lingo.json +++ b/agent/templates/image_lingo.json @@ -7,261 +7,262 @@ "en": "ImageLingo lets you snap any photo containing text—menus, signs, or documents—and instantly recognize and translate it into your language of choice using advanced AI-powered translation technology.", "zh": "多模态大模型允许您拍摄任何包含文本的照片——菜单、标志或文档——立即识别并转换成您选择的语言。"}, "canvas_type": "Consumer App", - "dsl": { - "components": { - "Agent:CoolPandasCrash": { - "downstream": [ - "Message:CurlyApplesRelate" - ], - "obj": { - "component_name": "Agent", - "params": { - "delay_after_error": 1, - "description": "", - "exception_comment": "", - "exception_goto": [], - "exception_method": null, - "frequencyPenaltyEnabled": false, - "frequency_penalty": 0.7, - "llm_filter": "image2text", - "llm_id": "qwen-vl-plus@Tongyi-Qianwen", - "maxTokensEnabled": false, - "max_retries": 3, - "max_rounds": 5, - "max_tokens": 256, - "mcp": [], - "message_history_window_size": 12, - "outputs": { - "content": { - "type": "string", - "value": "" - }, - "structured_output": {} - }, - "presencePenaltyEnabled": false, - "presence_penalty": 0.4, - "prompts": [ - { - "content": "The user query is {sys.query}\n\n\n\nThe input files are {sys.files}\n\n", - "role": "user" - } - ], - "sys_prompt": "You are a multilingual translation assistant that works from images. When given a photo of any text or scene, you should:\n\n\n\n1. Detect and extract all written text in the image, regardless of font, orientation, or style. \n\n2. Identify the source language of the extracted text. \n\n3. Determine the target language:\n\n - If the user explicitly specifies a language, use that.\n\n - If no language is specified, automatically detect the user\u2019s spoken language and use that as the target. \n\n4. Translate the content accurately into the target language, preserving meaning, tone, and formatting (e.g., line breaks, punctuation). \n\n5. If the image contains signage, menus, labels, or other contextual text, adapt the translation to be natural and context-appropriate for daily use. \n\n6. Return the translated text in plain, well-formatted paragraphs. If the user asks, also provide transliteration for non-Latin scripts. \n\n7. If the image is unclear or the target language cannot be determined, ask a clarifying follow-up question.\n\n\nExample:\n\nUser: \u201cTranslate this photo for me.\u201d\n\nAgent Input: [Image of a Japanese train schedule]\n\nAgent Output:\n\n\u201c7:30 AM \u2013 \u6771\u4eac\u99c5 (Tokyo Station) \n\n8:15 AM \u2013 \u65b0\u5927\u962a (Shin-Osaka)\u201d \n\n(Detected user language: English)```\n\n", - "temperature": 0.1, - "temperatureEnabled": true, - "tools": [], - "topPEnabled": false, - "top_p": 0.3, - "user_prompt": "", - "visual_files_var": "sys.files" - } - }, - "upstream": [ - "begin" - ] - }, - "Message:CurlyApplesRelate": { - "downstream": [], - "obj": { - "component_name": "Message", - "params": { - "content": [ - "{Agent:CoolPandasCrash@content}" - ] - } - }, - "upstream": [ - "Agent:CoolPandasCrash" - ] - }, - "begin": { - "downstream": [ - "Agent:CoolPandasCrash" - ], - "obj": { - "component_name": "Begin", - "params": { - "enablePrologue": true, - "inputs": {}, - "mode": "task", - "prologue": "Hi there! I\u2019m ImageLingo, your on-the-go image translation assistant\u2014just snap a photo, and I\u2019ll instantly translate and adapt it into your language." - } - }, - "upstream": [] - } - }, - "globals": { - "sys.conversation_turns": 0, - "sys.files": [], - "sys.query": "", - "sys.user_id": "" - }, - "graph": { - "edges": [ - { - "data": { - "isHovered": false - }, - "id": "xy-edge__beginstart-Agent:CoolPandasCrashend", - "source": "begin", - "sourceHandle": "start", - "target": "Agent:CoolPandasCrash", - "targetHandle": "end" - }, - { - "data": { - "isHovered": false - }, - "id": "xy-edge__Agent:CoolPandasCrashstart-Message:CurlyApplesRelateend", - "source": "Agent:CoolPandasCrash", - "sourceHandle": "start", - "target": "Message:CurlyApplesRelate", - "targetHandle": "end" - } - ], - "nodes": [ - { - "data": { - "form": { - "enablePrologue": true, - "inputs": {}, - "mode": "task", - "prologue": "Hi there! I\u2019m ImageLingo, your on-the-go image translation assistant\u2014just snap a photo, and I\u2019ll instantly translate and adapt it into your language." - }, - "label": "Begin", - "name": "begin" - }, - "id": "begin", - "measured": { - "height": 48, - "width": 200 - }, - "position": { - "x": 50, - "y": 200 - }, - "selected": false, - "sourcePosition": "left", - "targetPosition": "right", - "type": "beginNode" - }, - { - "data": { - "form": { - "delay_after_error": 1, - "description": "", - "exception_comment": "", - "exception_goto": "", - "exception_method": null, - "frequencyPenaltyEnabled": false, - "frequency_penalty": 0.7, - "llm_filter": "image2text", - "llm_id": "qwen-vl-plus@Tongyi-Qianwen", - "maxTokensEnabled": false, - "max_retries": 3, - "max_rounds": 5, - "max_tokens": 256, - "mcp": [], - "message_history_window_size": 12, - "outputs": { - "content": { - "type": "string", - "value": "" - }, - "structured_output": {} - }, - "presencePenaltyEnabled": false, - "presence_penalty": 0.4, - "prompts": [ - { - "content": "The user query is {sys.query}\n\n\n\nThe input files are {sys.files}\n\n", - "role": "user" - } - ], - "sys_prompt": "You are a multilingual translation assistant that works from images. When given a photo of any text or scene, you should:\n\n\n\n1. Detect and extract all written text in the image, regardless of font, orientation, or style. \n\n2. Identify the source language of the extracted text. \n\n3. Determine the target language:\n\n - If the user explicitly specifies a language, use that.\n\n - If no language is specified, automatically detect the user\u2019s spoken language and use that as the target. \n\n4. Translate the content accurately into the target language, preserving meaning, tone, and formatting (e.g., line breaks, punctuation). \n\n5. If the image contains signage, menus, labels, or other contextual text, adapt the translation to be natural and context-appropriate for daily use. \n\n6. Return the translated text in plain, well-formatted paragraphs. If the user asks, also provide transliteration for non-Latin scripts. \n\n7. If the image is unclear or the target language cannot be determined, ask a clarifying follow-up question.\n\n\nExample:\n\nUser: \u201cTranslate this photo for me.\u201d\n\nAgent Input: [Image of a Japanese train schedule]\n\nAgent Output:\n\n\u201c7:30 AM \u2013 \u6771\u4eac\u99c5 (Tokyo Station) \n\n8:15 AM \u2013 \u65b0\u5927\u962a (Shin-Osaka)\u201d \n\n(Detected user language: English)```\n\n", - "temperature": 0.1, - "temperatureEnabled": true, - "tools": [], - "topPEnabled": false, - "top_p": 0.3, - "user_prompt": "", - "visual_files_var": "sys.files" - }, - "label": "Agent", - "name": "Translation Agent With Vision" - }, - "dragging": false, - "id": "Agent:CoolPandasCrash", - "measured": { - "height": 87, - "width": 200 - }, - "position": { - "x": 350.5, - "y": 200 - }, - "selected": true, - "sourcePosition": "right", - "targetPosition": "left", - "type": "agentNode" - }, - { - "data": { - "form": { - "content": [ - "{Agent:CoolPandasCrash@content}" - ] - }, - "label": "Message", - "name": "Message" - }, - "id": "Message:CurlyApplesRelate", - "measured": { - "height": 56, - "width": 200 - }, - "position": { - "x": 650, - "y": 200 - }, - "selected": false, - "sourcePosition": "right", - "targetPosition": "left", - "type": "messageNode" - }, - { - "data": { - "form": { - "text": "ImageLingo lets you snap any photo containing text\u2014menus, signs, or documents\u2014and instantly recognize and translate it into your language of choice using advanced OCR and AI-powered translation technology. With automatic source-language detection and context-aware adaptations, translations preserve formatting, tone, and intent. Your on-the-go language assistant. " - }, - "label": "Note", - "name": "Translation Agent" - }, - "dragHandle": ".note-drag-handle", - "dragging": false, - "height": 190, - "id": "Note:OpenCobrasMarry", - "measured": { - "height": 190, - "width": 376 - }, - "position": { - "x": 385.5, - "y": -42 - }, - "resizing": false, - "selected": false, - "sourcePosition": "right", - "targetPosition": "left", - "type": "noteNode", - "width": 376 - } - ] - }, - "history": [], - "messages": [], - "path": [], - "retrieval": [] - }, + "dsl": { + "components": { + "Agent:CoolPandasCrash": { + "downstream": [ + "Message:CurlyApplesRelate" + ], + "obj": { + "component_name": "Agent", + "params": { + "delay_after_error": 1, + "description": "", + "exception_comment": "", + "exception_goto": [], + "exception_method": null, + "frequency_penalty": 0.7, + "frequencyPenaltyEnabled": false, + "llm_filter": "image2text", + "llm_id": "qwen-vl-plus@Tongyi-Qianwen", + "max_retries": 3, + "max_rounds": 5, + "max_tokens": 256, + "maxTokensEnabled": false, + "mcp": [], + "message_history_window_size": 12, + "outputs": { + "content": { + "type": "string", + "value": "" + }, + "structured_output": {} + }, + "presence_penalty": 0.4, + "presencePenaltyEnabled": false, + "prompts": [ + { + "content": "The user query is {sys.query}\n\n\n", + "role": "user" + } + ], + "sys_prompt": "You are a multilingual translation assistant that works from images. When given a photo of any text or scene, you should:\n\n\n\n1. Detect and extract all written text in the image, regardless of font, orientation, or style. \n\n2. Identify the source language of the extracted text. \n\n3. Determine the target language:\n\n - If the user explicitly specifies a language, use that.\n\n - If no language is specified, automatically detect the user’s spoken language and use that as the target. \n\n4. Translate the content accurately into the target language, preserving meaning, tone, and formatting (e.g., line breaks, punctuation). \n\n5. If the image contains signage, menus, labels, or other contextual text, adapt the translation to be natural and context-appropriate for daily use. \n\n6. Return the translated text in plain, well-formatted paragraphs. If the user asks, also provide transliteration for non-Latin scripts. \n\n7. If the image is unclear or the target language cannot be determined, ask a clarifying follow-up question.\n\n\nExample:\n\nUser: “Translate this photo for me.”\n\nAgent Input: [Image of a Japanese train schedule]\n\nAgent Output:\n\n“7:30 AM – 東京駅 (Tokyo Station) \n\n8:15 AM – 新大阪 (Shin-Osaka)” \n\n(Detected user language: English)```\n\n", + "temperature": 0.1, + "temperatureEnabled": true, + "tools": [], + "top_p": 0.3, + "topPEnabled": false, + "user_prompt": "", + "visual_files_var": "sys.files" + } + }, + "upstream": [ + "begin" + ] + }, + "begin": { + "downstream": [ + "Agent:CoolPandasCrash" + ], + "obj": { + "component_name": "Begin", + "params": { + "enablePrologue": true, + "inputs": {}, + "mode": "conversational", + "prologue": "Hi there! I’m ImageLingo, your on-the-go image translation assistant—just snap a photo, and I’ll instantly translate and adapt it into your language." + } + }, + "upstream": [] + }, + "Message:CurlyApplesRelate": { + "downstream": [], + "obj": { + "component_name": "Message", + "params": { + "content": [ + "{Agent:CoolPandasCrash@content}" + ] + } + }, + "upstream": [ + "Agent:CoolPandasCrash" + ] + } + }, + "globals": { + "sys.conversation_turns": 0, + "sys.files": [], + "sys.query": "", + "sys.user_id": "" + }, + "graph": { + "edges": [ + { + "data": { + "isHovered": false + }, + "id": "xy-edge__beginstart-Agent:CoolPandasCrashend", + "source": "begin", + "sourceHandle": "start", + "target": "Agent:CoolPandasCrash", + "targetHandle": "end" + }, + { + "data": { + "isHovered": false + }, + "id": "xy-edge__Agent:CoolPandasCrashstart-Message:CurlyApplesRelateend", + "source": "Agent:CoolPandasCrash", + "sourceHandle": "start", + "target": "Message:CurlyApplesRelate", + "targetHandle": "end" + } + ], + "nodes": [ + { + "data": { + "form": { + "enablePrologue": true, + "inputs": {}, + "mode": "conversational", + "prologue": "Hi there! I’m ImageLingo, your on-the-go image translation assistant—just snap a photo, and I’ll instantly translate and adapt it into your language." + }, + "label": "Begin", + "name": "begin" + }, + "id": "begin", + "measured": { + "height": 48, + "width": 200 + }, + "position": { + "x": 50, + "y": 200 + }, + "selected": false, + "sourcePosition": "left", + "targetPosition": "right", + "type": "beginNode" + }, + { + "data": { + "form": { + "delay_after_error": 1, + "description": "", + "exception_comment": "", + "exception_goto": "", + "exception_method": null, + "frequency_penalty": 0.7, + "frequencyPenaltyEnabled": false, + "llm_filter": "image2text", + "llm_id": "qwen-vl-plus@Tongyi-Qianwen", + "max_retries": 3, + "max_rounds": 5, + "max_tokens": 256, + "maxTokensEnabled": false, + "mcp": [], + "message_history_window_size": 12, + "outputs": { + "content": { + "type": "string", + "value": "" + }, + "structured_output": {} + }, + "presence_penalty": 0.4, + "presencePenaltyEnabled": false, + "prompts": [ + { + "content": "The user query is {sys.query}\n\n\n", + "role": "user" + } + ], + "sys_prompt": "You are a multilingual translation assistant that works from images. When given a photo of any text or scene, you should:\n\n\n\n1. Detect and extract all written text in the image, regardless of font, orientation, or style. \n\n2. Identify the source language of the extracted text. \n\n3. Determine the target language:\n\n - If the user explicitly specifies a language, use that.\n\n - If no language is specified, automatically detect the user’s spoken language and use that as the target. \n\n4. Translate the content accurately into the target language, preserving meaning, tone, and formatting (e.g., line breaks, punctuation). \n\n5. If the image contains signage, menus, labels, or other contextual text, adapt the translation to be natural and context-appropriate for daily use. \n\n6. Return the translated text in plain, well-formatted paragraphs. If the user asks, also provide transliteration for non-Latin scripts. \n\n7. If the image is unclear or the target language cannot be determined, ask a clarifying follow-up question.\n\n\nExample:\n\nUser: “Translate this photo for me.”\n\nAgent Input: [Image of a Japanese train schedule]\n\nAgent Output:\n\n“7:30 AM – 東京駅 (Tokyo Station) \n\n8:15 AM – 新大阪 (Shin-Osaka)” \n\n(Detected user language: English)```\n\n", + "temperature": 0.1, + "temperatureEnabled": true, + "tools": [], + "top_p": 0.3, + "topPEnabled": false, + "user_prompt": "", + "visual_files_var": "sys.files" + }, + "label": "Agent", + "name": "Translation Agent With Vision" + }, + "dragging": false, + "id": "Agent:CoolPandasCrash", + "measured": { + "height": 85, + "width": 200 + }, + "position": { + "x": 350.5, + "y": 200 + }, + "selected": true, + "sourcePosition": "right", + "targetPosition": "left", + "type": "agentNode" + }, + { + "data": { + "form": { + "content": [ + "{Agent:CoolPandasCrash@content}" + ] + }, + "label": "Message", + "name": "Message" + }, + "id": "Message:CurlyApplesRelate", + "measured": { + "height": 56, + "width": 200 + }, + "position": { + "x": 650, + "y": 200 + }, + "selected": false, + "sourcePosition": "right", + "targetPosition": "left", + "type": "messageNode" + }, + { + "data": { + "form": { + "text": "ImageLingo lets you snap any photo containing text—menus, signs, or documents—and instantly recognize and translate it into your language of choice using advanced OCR and AI-powered translation technology. With automatic source-language detection and context-aware adaptations, translations preserve formatting, tone, and intent. Your on-the-go language assistant. " + }, + "label": "Note", + "name": "Translation Agent" + }, + "dragging": false, + "dragHandle": ".note-drag-handle", + "height": 190, + "id": "Note:OpenCobrasMarry", + "measured": { + "height": 190, + "width": 376 + }, + "position": { + "x": 385.5, + "y": -42 + }, + "resizing": false, + "selected": false, + "sourcePosition": "right", + "targetPosition": "left", + "type": "noteNode", + "width": 376 + } + ] + }, + "history": [], + "messages": [], + "path": [], + "retrieval": [] + }, "avatar": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAABfmSURBVHgBdVoJeFTluX7PbJmZJLNlD9kXwhKQRTbDrhRc0VqLxbrUi7cVKWqr7VO1Sq331qtWBBUUKgJWLi6tuOCCCztY1kBYExISIGRPJsvsy7nv/89MMsHeeThM5uTMOd//Le/3vt8fBVe8unp7Zx5p7Z5v0mlu9YbUgjyTDilmI/p6nQgoeiSaTNDwOo1WiwvNl9HU2Y6pw4bj3l0n8EBhBjSKihJ7MkwJCQiHVQSDIWj4BZ1WB1UN81B5aKAiLD+LlzgHcT5qg/wc966oaiUUbaVZr/1TVlZWfby9mtgPH23fblv50XvLNeHw9jDCj5g0SkGI3++mAQadDlarDampKUi322CxWhCmVeVDy5CVlg0fL7zOqkN7bw804QC0igJFGhCGTqfhoZPGSXMUPlIJg5fwUAYsEd+JflaiP8cOemCMoqj3+cM4/+3BPcsLCgps/V8T/50/f95WffnC9iHp6WOGFw1FEx3T6A3gUlsndC0XcbHuHGrOVqP67BnU155Dc2Mj+jq7YE1NxYkTVQAXYNAqCIWFX8VNNdL4QCgsHyBslufl70UEMNj7ce+4IgryZ7F0/tNrtLjkbMIdc2+onHPdlFnr129x6sQFFzqdzzS1do3Z9u0uHD9WiToa23b5Mvz0qFajwGgyI4EpkWAyytSxJhiRlJ4OV28fnnjiMby+4k308mfhdy0ixmpotZ4fwmrEBEUNRw1R462MC4DSb/igxagDnlZpizXJyugrY0I+zTM89ahiBAq0JuN5kaOJZjP0RqO8mfheKEwv+gNwu90I+rz8vQmJSYnIys5CQUkpzHYHdn39JSNTi/b2DsSymKHmA5mIUWPCNF6v1Ud8qUYjEPXqQObj39ZB/0Jpk5YRCGnCuOXGOXA7XdDo9bN0abm5z+j1OrS1d6LD2U3PdcOSnITsITSyqBgZ+flIS0tFel4hvH4fJlfMQAJde+FSI4qKCvHu2+vg9Xrh97sQ6umAlpFqrTuNzy714t45P0KQTgiFgtAZdby3Ig1Wop6VP4tzsTT6d8ZHz4vvSEdodLA77Ohu6YU25L9VV1Y2dEw+DSkuK0NeURGycnKRbLczVXTS+36PB34vD7cL2bl5NNaHUEBFYUYasm0WFA8rR239OaTY7FBFYQb8aOh24WJXDzo9Llj1JgSly2ViMZWUfq/3Gz/Y3kGLQtyiIucBR3oqaqvO80LdfN3Tr742xu/x0ii/hCSFuetlPuuJMhoeSQl6JNkt0OgM8HP1Pf4ONFy4gJNVx1F/7hx6nF3Y//33uO2WWxHwuuDTGeGx5WCULhVVdXUYnpPHKISQbDBJOA3F1UEUPSPejVvFoAK+YiHistSMDARDRDu9tkCn0sNWhl1nTYIhwQANMbqzuwen+PAAf2fJHoJ9VXtRc7wSBz7YhJDfz0JVkVtcjFFjxyHocmLXrl1Y+NO70EVvqzTWpnpgTtIjw2KDlX1DOEUiUdRYmRKxFIpLkStMjUQDAxFTeQOVEJyRkSUdrhBUdKdqarHn4L9gdwZRXX8ZDRoPNJZELLh+Hm6aPRNvPvYgDrS5YQz6YGJDC+oNGFE+AscOH8WYceNY1FYcYAREgVvT86Wxk7OHSZ+KRhbigqSHWQtRHIoYL1NisPEK+us1knHKFUWuRL4nIhAIBuQ9dUt+9lP8fO7NyOFD87PKUa2vR35ZIXavW4EbHV7UE/sDPW50G5Nx/x0/hsfnx+p1f0decRG+/nYHPM4OngvAJZAqGGa6BPkkX79B4mVg6g0YH32PNrsQrqyByGclamwsAvEwm04ID7H3CAcpEyquUe8cPhk2exoeWvE0Jk6/Dvt2fourS7IwPCmA7y770Vw4FuYT/4LJmowUqxXl5eUoGz4CpcOGYeLEybjx5vlY/9YbKM4rhZ+5KR8al7s6jUbaKDx2ZW5HOnR/kvC8EsuvgXNxCxZuOdN4Dj+ZNRdWmwO68WMmoLqyEp+ersQIawaamhtJD+yAPRM9RWVYfGc5hpPrDCkugZW4r9CbfvYE4XEPa8RosePmn9yBr77YikeX/h5Bd1A+RgCAdJoSLdJoV1Z/kOMDhazGqMUP+1j/z+xlsCfboDXoSABU6I4dO4B7Ft6D+8qXoQNB5Gbnwkwvh/hAH/E94BO5b5Yd2NPnQqfLheOkD0UpDoxgFJbccT12HD2L6dOnIDHRxHRy01rBXwYeHZL5rAyQNmWwUf0LuxJxZAePdOD4JafQkQmJRlw/Ihe6Ne9uhtftka2+hIYaEhPR5XSit8uJ7rZWKCzaz9//Ozp7XajXmnBg0zvIspjRxX5wz6J78Nzy1/Ho3Qtw+NBR6R6xcC0X2+9MNcIYBU+KeVlBXHHGGOcgb0eWJSOnxCGWJHdAkjkZVjrwhtFF0HU5e9Bw6TLaAyFs37Mbuza/izumjmOu26CaLKg9dhSVM36MSy88hbLps1CUnYarsjPQGlRw8MBhLD33S2xe9SoyKubhZH0dTEwxqzlJFlnMUFFsGvw/MBmNTMzkCMVQIlGKFXMUbiPppkqoT7RZCfsm6P7wxyfR6LyATJ0NUxctBRkbTlQdwfFP3sP4MaNhKxuFa9vO4ctMVn7zJXSzEK+99hY8uvwFedvnfnUvXOwNiUyZmjNnMHpEufSq5On8L+APsXsTho1mWczAD/NaLkTarQ5AVzQsatx1IlhaJYJkjrQ0tHQ6oWnzt7Ao0tDo6kCWGoC5pxPjczNQ2dhCiqzFwc8+Qu3h73GxoQF11WdAQYFNO7fh/vvvxfLlf0XhzOsRKByH/3xqGb7b9iW7u27QU0X66nX6fuGhxnleagA1jm7GX/BvGWkkUmFyq9SUdLT2kAGPLS9RvYIaN3fg4pkaJBgTaGQm7v3lQ9j50WaUj78aQ0qGYiy7bh4JXXKyRdJlHxHITX6UxHRx08MNpBR/+MXPsffgUTLFXtHG+q3qL9RwOG5tA/gfSyFxUVgdAM8rO7QSTTMj6fxzr70I7cHt0BlSsuBgAyqYUoFxv30chSR2ufmFkvXd94v74errk2QugcWssED7up1SyOzddxjppNar3t+EG+bOxan31qKOtWQyGNAnkkujjZgh8j8uz2Pmx1OE2CtmfDhqaAyxroRfAcmZmRk42dkN3WdfbkMnUUdQ4lAoBLPRRDptQVtLK3p6mpjDXnmHfYcOwZqZheq689h67DiOrn8LW7/6Ao0nq6BeOxsBopiXgNDa0SZJT7w8vFKkRHB/wFh5Li6F+ssgri0o0XvFCjyLtuykczVJJHKdlIdVNGrrP97HY0uWYHhRPr7a8j5a2lvQwVQ5Wl1DreBECenDVaNH4zS5T9GEiVi44A7UX7xISA1g6Py7MZLcSEhMPaMwYKw6QHBin69Ii9jvYv/HGqBGiFNFkepOyFTxLjQ29TFys7IltOuGUKx4+3owMsOMNIsF1uJcTK24Cnv3H4CloAQXuvoQ5nk9ydNZSs0UMsEUdw8yFRdKJ01Ckz0L733wIW4ucOCqjER89fU3uGbCFPS53AOFGCE2A16Me6nRIxwzX16k/iB9lOiqxF0M+gRoiGpOLxlp9pBMNSU5AcPS2BxMBmw5dB4GtmmzIw1j87ORNr4CvvYmplYizIlmhChSqqgDfNTL3kAA86ZOQ9bQoSgsLEbIoMdvHrgfx46dQlNbW38K6KCJrWEgbaJpFVuAFAuCM/HCIFEmQBbrZ7P0kgV7qDM6iY7C/I7ONnS2t2Lzug3ou9QCiXkXWp0Ik1+PZ3pkpnZSoFN9GQKYM+kadukefNZwAWkcp9wydzaKJs9EmL0iKTmZIkeL3u5eSTmE6rKkOnCupk6apKEGVsORQvXRQiFqQkE/vCE/fJSfbl8fXB52/L4utHe0ouliM5o4SOhsaZFQ3N7WAmtGLryMZGP1WeqQECyOVHgInX3MCr8vTEfTMaW5GWpORiqQaIfT5cWwkcMxmpA5fOQoFJPA6fR6TgKS4WUtuMiDEGWUGqKMEC8hss8Q00vkZWpKGkaQ+K3/5E00tzfIYVhvXzc5VA/RTBy98Lg8VHwevnMxHjY5F4cGPXz3BHmfyO2F9pVDMUpa8dKKuZIq0IfaQihFrdSOpBn8PK1ikvryxs3Swxp2HWGknw8RnMbK3E+k7nV2dOLs6VPUDDmQxJhP6WFIdRT3OjY7Y4IZT730FFYuW45Fi5ey2HpxoaGWV+rpOTWiE3iEeQhOJPuBqkRnRkrkUCLQqSiDITOeUw3uywrcTGFNQ10DSjiB8FIXt3e7mRIuSZc1PS1Y8/ZbKM/PI/8fgUV3LYRZrJxeF+vXEWkMosOyXwQDXvQEODYhc502fRpaLnZxGNYNZ5sHvT2+qHejoxHeQ89aMSRooeeh02t4TonQb018wUZhNqz2qzdZxFFQEw4RE0BtKBBY5iT2154+jj1rliHB0wT70MmcqjGeZw/gw9MNsBB5ikddhevz0qDJyCG1DInhUgQTeBOrLQVb3ngBjW1OzJv/Y/zvhnXw8ylJpNfthGhxTYJBjytfwqggSWQwFJk/+cRBJ/io+oRkFHJUp49EyiWzgs9V6UAu2Gik5s5MgS7IZecYA3DAww7qxphrroWz9Sz8zS0YVVqAEKm2dvg4XCbP3/PpPzErKQsuLljT3gxvVip0vHtrezv7w2TMnjUbBaWlHEl2Yefv/gMVCxchwK694D8ewHc7vif86SLaOHoIjxcPLWTt2GEju3Q4HHBwpJPCuY+dnN/KGaydqqutrRn7Dx7g7xywWKgKeS4pORHJSTYomWlpqiEjBWXpDthLyhD8/iPMuHsJHMMmYWTd15j61HrOhlxQEpNROHoiXn/6T0ijsNFYmT75BXIBIsRGolJfby8HYpmYMW0axhlCeO7nt3MiZ8A/XGG8vHo1huRm0ygrI2Zlt09GTkEh5s64loYk9wt2MTMVaSneBZyKiaHUzvS+UIFhDpv9RDOLNZ2Q2gwl3W5Xc+ffjsP6VJy8aTTyJlagh3znzIlKqM0XsDuYiFLq3kmc0olG19rRIQvRQPf5eKhGg0hIhEnmvM1taGFNLX3xOeiJPiuml6OEnKVx4UNIovcFpRaTbmGtnsUv6sHLZhTiIMDPgjQwzQTS1NTVyGfkkC588vmnqKutw4hJUzCfEe6gyApyAXqjBV0dXEBKqkMNEhF2bPscRm8fQLhL4Kr1mdnQUXuCqXKE+a4hmSs0p8CUbGWKdKCaN51h8MNdfR6J9LLa1Y69hw/h8X17iXschrHprZ1QjMI/v8rE5GSC8FhVfYo10YFhpcNw5NghzpP24Oqrx7MPdGDXzl2kCQZkceq2d+/3GJKTReLYi5bG1kgUWEJ/XbMWo/Ny4KSzxD6Ejh1Z+d1LL6pHNm/EWy89j14qnXBjLUxD8hG6WAt9ajZH4hrJ6ROIOp+9sxYfNDhxoaMd3UMn4oXZE2EdMgQHd3yH1W+shi0tBQHeVKBEIpvcX97+G35243zmtwWLljyIb7/4At/v3I/ikaWYNXM2TnISXnnwGJ2jweNP/h7v/X0TWi63cvqRhMUPPoCVL78KR6qd079e9hAP7DnpeJfXKOzMfooo0by1L/71lWUTHHwou2uIXmT/hoGw2Fh9DqELNWg/eRRdZ09h99avoB0zkjnvRHU2J3LN53Hg1Dmsfvl5tHAU+dGrK3Fnug1VNTVoYOFfbu3AbcNz8cQjv4GZUVu3cSOWPrwUu7bvgJ114CZUT548EVVVJ/CjObM5KDgBS1IS9KYELPjJrVi16m+yxxRxAmh3WNl32AS7unH28kXceuNNZAgUM4oO2lnjy5blMNxB5qDKtq4XDYVdtsvjw6effIaDLd3YX1+DQyOm4/z+j1GZWIo+tw9BNqpcsxadbHJi0cUUNc9/tw99LPZJc2+Ck0OBYGM9bivJx9x5c/DN8RM4ymlen6sPv/v949i/bw8neodlN//1bx7Bd19tw+mTNXB2d2PejfM4rm+laPKRXjQjNy8btefq5cD59JGjKBg9EuUlJYyCD0rt1vfUS+QfgrYaAm4kk28kJ1qw8pvdeHH3KTydFsTGPj16aUyQBC7IPbEQcTjBbMXYyVMwlNTh6vHjYRmSg/GjRsBC6iGK8w9/XIZDn7yP9xfOQza3oZ5z67Dqv5+XXXvS9ImYOHkSdn7zHY4cOEYoLcDU2TOhJ/K8ve5d5BXkIIeT8KEleVj9+noKrGy0NLWzRrQSqfykg19/+y3CXm5pNRLD2RplPoWJBtpkYjHF1Krj9bjhxh/h2a07kUqtPILFc9+iX2LNls+w+0QNjtTWYsO7G/HQrxdjwsTxGJaTQVLnw58fuA8n6K2pM6bjVAO3oogUm5qc2Ld3F9ZufAvdPd04tO8QVq18A79euoSEzoUzp2qw5vU1uGZqheQ5XrcXR48c4UahGckc4bh63TBzLquXztHCxvHP088+QyhNhbL/g3Wkjnp4G2pg40xIy2LtRAK2aNJQYTHgqqlTYRZTMMJbgNLyZN1FQqCCnDSH5OZ1ze3ItlslBRccR8/ohTy9UEi/pw8fisfuvgP/9e4/oSGmf/DxB1j8q8Uoo8bu7OrAFEbhG0bhR9fP5agkhC0fbeVWlh7DODAbzvnsX/7yCtJS0yS5E+RRqEYf09XKcb+HLHXFW29yAR9uUDXEcT3H5AkMg5Hh7knJhjm3UNLkjh6X5CUW0gKRZs0sJLHZlmJLlt1Uz7CGo/pVUIZYl83Oy8PoolJuimTg4YeX4Ott2/Dlp19KbX3dDXNQUlKEZU8+i/S0dC46Aa+ufAkLFtzNz2ncUVWwmrOmjRs34PCBShaxDU6OUG65fT5V4AKkc6ilpa0tTU10DLmH0ueUm3NasYFGXuJmWgmm52fXs3CEZ2KzCnCRQsBkEBLTaLycU/ImYuYvbtbW0QujmftnOTnycBHb9cRfEwnf2rVrMYu6ORgIyo3CHTu4n7DwLkyZNgkWW5LcUGlqaUN19WlK2yOoOnoYhXTAs398Ci2sz05SE0EG31m/iXsWbkzjNteUa6ahgoNoHcl6vaujpUBlEzGISUKKGcb0TKgiKtSfwehQNhRVUH6mwqmay6iYMCqye8nvtF5uROelWmzdvB779+yjIdUSTewOh1zoySPHMX3dVAqSZKo6ExlvD9555x3yHTvqzpznHlw6Xnt1FSYTDKZfU8FxjRd9fW4UlZVi0aJ7sW7DJo7UU+Ew27B9/z7cvvCnTDUjcnNzKnWqXv+xwWJ/2MA2zymuNNjg7WXrzOG0oktWvtjYNpoS2cK5d1xfj0DbRfzPk5txlJOKOu7kiC1WQa8TCcdiZmNiLRlZaOIl9pS9hN0N69Yjn9yn6lAlNwv9HIqtxOo3X8POnXthUWxwMLInuBdx250LkJmRLmdTmZmZyKBIWrz4ISlq9ITRnp4e+XOIDmaGVCq71y6fGfL0bU9m7hvF1DfJAj+Vk4WstKryCHPwIKoOH5Fds57Gu1kXBhprFoNgvg8W6YokYV3c7QzQSFE74ysmIoVGjB4zChPp4UbmbV5uDtJSUmHmYlNSUiKTB14tvif4kKTSPMSfKYTjh2Hh8KDxTFivLZRP37fh9VdUj/vhxNQM2LOGYPvXX+D+Z1+R8ltscBu5dyz+XCAydVZ+yOujKdbS2ooKcptf3fUzXDdnFk5SH48iNIbEbicX5A/45C6lFO00TlVDEVEfjkj7cHSCLXdz2OCgDh7DxAbEkrWG1RWTJ097RO7U+8L+ZUooMIPwOEY1JHAz2QAdDU9hWAfEhxIRFILHCxYqHqhGNsP7SHPLcrKxZ9NbsJEtOvnwhvWvodBmw1Hu6tgYVbFXHNszE0wzlZQ6glgx81T5WSOFUkR5hft3BSOLCEXlGJ9fmZAQXCbOyQXM+sWjzu3Ll8+iBlsW6ut+GMJTcYOooBDYfPcSsYYNLYWnrYneDMJEiuzgBvbc667G/OlTUf/hZjR3tPDogL/lMmY+8RzrgbzH4+7ftUtgJC+2NhHJLEyRYFQ2Dmw9Cd0c/+pfg7hGbpQrK0yGwLKxY2c5Y9Ef9Dq//YuCFWveXLbu8x1XWRNNY5IMWiSxkB1sVBVsLufYuDpZtMXp3NgmrNpIvsryWfBEDYg5Ko0yczQ56sHfIkRYDgnSBbV/g0MkSBeFj41bU0lmkkhGVdKAK/7kJjaSjAr9eqbVx4qq2TJlyrQd8fb+H90isRVH8mIAAAAAAElFTkSuQmCC" -} \ No newline at end of file +} + diff --git a/agent/templates/stock_research_report.json b/agent/templates/stock_research_report.json new file mode 100644 index 0000000..1e7585e --- /dev/null +++ b/agent/templates/stock_research_report.json @@ -0,0 +1,1172 @@ +{ + "id": 26, + "title": { + "en": "Stock Research Report Agent", + "zh": "股票研究报告智能体" + }, + "description": { + "en": "This template helps financial analysts quickly organize information — it can automatically retrieve company data, consolidate financial metrics, and integrate research report insights.", + "zh": "这个模板可以帮助金融分析师快速整理信息——它能够自动获取公司数据、整合财务指标,并汇总研报观点。" + }, + "canvas_type": "Recommended", + "dsl": { + "components": { + "Agent:ManyToesBrush": { + "downstream": [ + "Switch:FluffyCoinsSell" + ], + "obj": { + "component_name": "Agent", + "params": { + "cite": true, + "delay_after_error": 1, + "description": "", + "exception_default_value": "", + "exception_goto": [], + "exception_method": "", + "frequencyPenaltyEnabled": false, + "frequency_penalty": 0.7, + "llm_id": "qwen-turbo-latest@Tongyi-Qianwen", + "maxTokensEnabled": false, + "max_retries": 3, + "max_rounds": 1, + "max_tokens": 256, + "mcp": [], + "message_history_window_size": 12, + "outputs": { + "content": { + "type": "string", + "value": "" + } + }, + "presencePenaltyEnabled": false, + "presence_penalty": 0.4, + "prompts": [ + { + "content": "{sys.query}", + "role": "user" + } + ], + "sys_prompt": " \n\nYour responsibility is to identify and extract the stock name or abbreviation from the user's natural language query and return the corresponding unique stock code.\n\n \n\n\n\n \n\n1. Only one result is allowed: - If a stock is identified \u2192 only return the corresponding stock code; - If no stock is identified \u2192 only return \u201cNot Found\u201d. 2. **Do not** output any additional text, punctuation, explanation, prefixes, or line breaks. 3. The output must strictly adhere to the . \n\n\n\n\n\nOnly output the stock code (e.g., AAPL or 600519)\nOr only output \u201cNot Found\u201d\n\n\n\n\nUser input: \u201cHelp me check the research report of Apple\u201d \u2192 Output: AAPL\nUser input: \u201cHow is Maotai\u2019s financial performance\u201d \u2192 Output: 600519\nUser input: \u201cHow is the Shanghai Composite Index doing today\u201d \u2192 Output: Not Found\n\n\n\n - Tavily Search: Use this tool when you are unsure of the stock code. - If you are confident, you do not need to use the tool. \n\n\n\n\n\nOnly output the result, no explanations, hints, or notes allowed.\nThe output can only be the stock code or \u201cNot Found\u201d, otherwise, it is considered an incorrect answer.\n", + "temperature": 0.1, + "temperatureEnabled": false, + "tools": [ + { + "component_name": "TavilySearch", + "name": "TavilySearch", + "params": { + "api_key": "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", + "days": 7, + "exclude_domains": [], + "include_answer": false, + "include_domains": [], + "include_image_descriptions": false, + "include_images": false, + "include_raw_content": true, + "max_results": 5, + "outputs": { + "formalized_content": { + "type": "string", + "value": "" + }, + "json": { + "type": "Array", + "value": [] + } + }, + "query": "sys.query", + "search_depth": "basic", + "topic": "general" + } + } + ], + "topPEnabled": false, + "top_p": 0.3, + "user_prompt": "", + "visual_files_var": "" + } + }, + "upstream": [ + "begin" + ] + }, + "Agent:SadDodosRescue": { + "downstream": [ + "Agent:SharpSlothsSlide" + ], + "obj": { + "component_name": "Agent", + "params": { + "cite": true, + "delay_after_error": 1, + "description": "", + "exception_default_value": "", + "exception_goto": [], + "exception_method": "", + "frequencyPenaltyEnabled": false, + "frequency_penalty": 0.7, + "llm_id": "kimi-k2-turbo-preview@Moonshot", + "maxTokensEnabled": false, + "max_retries": 3, + "max_rounds": 1, + "max_tokens": 256, + "mcp": [ + { + "mcp_id": "30d6ef8ea8d511f0828382e3548809fa", + "tools": {} + } + ], + "message_history_window_size": 12, + "outputs": { + "content": { + "type": "string", + "value": "" + } + }, + "presencePenaltyEnabled": false, + "presence_penalty": 0.4, + "prompts": [ + { + "content": "user's query is {sys.query}\n\n\n{Agent:ManyToesBrush@content}\n", + "role": "user" + } + ], + "sys_prompt": " \n\nYou are the information extraction agent. You understand the user\u2019s query and delegate tasks to investoday and the internal research report retrieval agent. \n\n \n\n\n\n 1. Based on the stock code output by the \"Extract Stock Code\" agent, call investoday's list_news to retrieve the latest authoritative research reports and views, and save all publicly available key information. \n\n2. Call the \"Internal Research Report Retrieval Agent\" and save the full text of the research report output. \n\n3. Output the content retrieved from investoday and the Internal Research Report Retrieval Agent in full. \n\n\n\n\n\nThe output must be divided into two sections:\n#1. Title: \u201cinvestoday\u201d\nDirectly output the content collected from investoday without any additional processing.\n#2. Title: \"Internal Research Report Retrieval Agent\"\nDirectly output the content provided by the Internal Research Report Retrieval Agent.\n", + "temperature": 0.1, + "temperatureEnabled": false, + "tools": [ + { + "component_name": "Agent", + "id": "Agent:MightyIdeasGlow", + "name": "Internal Research Report Retrieval Agent", + "params": { + "cite": true, + "delay_after_error": 1, + "description": "You are a senior financial content analyst who can accurately identify the companies, stock codes, industries or topics mentioned in user questions, and completely extract relevant research content from the knowledge base to ensure that data, opinions and conclusions are not lost.", + "exception_default_value": "", + "exception_goto": [], + "exception_method": "", + "frequencyPenaltyEnabled": false, + "frequency_penalty": 0.7, + "llm_id": "kimi-k2-turbo-preview@Moonshot", + "maxTokensEnabled": false, + "max_retries": 3, + "max_rounds": 1, + "max_tokens": 256, + "mcp": [], + "message_history_window_size": 12, + "outputs": { + "content": { + "type": "string", + "value": "" + } + }, + "presencePenaltyEnabled": false, + "presence_penalty": 0.4, + "prompts": [ + { + "content": "{sys.query}", + "role": "user" + } + ], + "sys_prompt": " \n\nRead user input \u2192 Identify the involved company/stock (supports abbreviations, full names, codes, and aliases) \u2192 Retrieve the most relevant research reports from the knowledge base \u2192 Output the full text of the research report, retaining the original format, data, chart descriptions, and risk warnings. \n\n\n\n\n\n \n\n1. Exact Match: Prioritize exact matches of company full names and stock codes. \n\n2. Content Fidelity: Fully retain the research report text stored in the knowledge base without deletion, modification, or omission of paragraphs. \n\n3. Original Data: Retain table data, dates, units, etc., in their original form. \n\n4. Complete Viewpoints: Include investment logic, financial analysis, industry comparisons, earnings forecasts, valuation methods, risk warnings, etc. \n\n5. Merging Multiple Reports: If there are multiple relevant research reports, output them in reverse chronological order. \n\n\n\n6. No Results Feedback: If no matching reports are found, output \u201cNo related research reports available in the knowledge base.\u201d\n\n\n\n ", + "temperature": 0.1, + "temperatureEnabled": false, + "tools": [ + { + "component_name": "Retrieval", + "name": "Retrieval", + "params": { + "cross_languages": [], + "description": "A knowledge base of research reports on stock analysis by senior experts", + "empty_response": "", + "kb_ids": [ + "60c53ed89acc11f0bc1e7a2a6d0b2755" + ], + "keywords_similarity_weight": 0.7, + "outputs": { + "formalized_content": { + "type": "string", + "value": "" + } + }, + "rerank_id": "", + "similarity_threshold": 0.2, + "top_k": 1024, + "top_n": 8, + "use_kg": false + } + } + ], + "topPEnabled": false, + "top_p": 0.3, + "user_prompt": "This is the order you need to send to the agent.", + "visual_files_var": "" + } + } + ], + "topPEnabled": false, + "top_p": 0.3, + "user_prompt": "", + "visual_files_var": "" + } + }, + "upstream": [ + "Switch:FluffyCoinsSell" + ] + }, + "Agent:SharpSlothsSlide": { + "downstream": [ + "Message:OliveLawsArgue" + ], + "obj": { + "component_name": "Agent", + "params": { + "cite": true, + "delay_after_error": 1, + "description": "", + "exception_default_value": "", + "exception_goto": [], + "exception_method": "", + "frequencyPenaltyEnabled": false, + "frequency_penalty": 0.7, + "llm_id": "qwen-turbo-latest@Tongyi-Qianwen", + "maxTokensEnabled": false, + "max_retries": 3, + "max_rounds": 1, + "max_tokens": 256, + "mcp": [], + "message_history_window_size": 12, + "outputs": { + "content": { + "type": "string", + "value": "" + } + }, + "presencePenaltyEnabled": false, + "presence_penalty": 0.4, + "prompts": [ + { + "content": "User query questions:\n\n\n\n{sys.query}\n\n\n\nInformation Extraction Agent:\n\n{Agent:SadDodosRescue@content}", + "role": "user" + } + ], + "sys_prompt": " \n\nYou are a senior investment banking (IB) analyst with years of experience in capital market research. You excel at writing investment research reports covering publicly listed companies, industries, and macroeconomics. You possess strong financial analysis skills and industry insights, combining quantitative and qualitative analysis to provide high-value references for investment decisions. \n\n**You are able to retain and present differentiated viewpoints from various reports and sources in your research, and when discrepancies arise, you do not merge them into a single conclusion. Instead, you compare and analyze the differences.** \n\n\n \n\n\n\n\n \n\nYou will receive financial information extracted by the information extraction agent.\n\n \n\n\n\nBased on the content returned by the information extraction agent (no fabrication of data), write a professional, complete, and structured investment research report. The report must be logically rigorous, clearly organized, and use professional language, suitable for reference by fund managers, institutional investors, and other professional readers.\nWhen there are differences in analysis or forecasts between different reports or institutions, you must list and identify the sources in the report. You should not select only one viewpoint. You need to point out the differences, their possible causes, and their impact on investment judgments.\n\n\n\n\n##1. Summary\nProvide a concise overview of the company\u2019s core business, recent performance, industry positioning, and major investment highlights.\nSummarize key conclusions in 3-5 sentences.\nHighlight any discrepancies in core conclusions and briefly describe the differing viewpoints and areas of disagreement.\n##2. Company Overview\nDescribe the company's main business, core products/services, market share, competitive advantages, and business model.\nHighlight any differences in the description of the company\u2019s market position or competitive advantages from different sources. Present and compare these differences.\n##3. Recent Financial Performance\nSummarize key metrics from the latest financial report (e.g., revenue, net profit, gross margin, EPS).\nHighlight the drivers behind the trends and compare the differential analyses from different reports. Present this comparison in a table.\n##4. Industry Trends & Opportunities\nOverview of industry development trends, market size, and major drivers.\nIf different sources provide differing forecasts for industry growth rates, technological trends, or competitive landscape, list these and provide background information. Present this comparison in a table.\n##5. Investment Recommendation\nProvide a clear investment recommendation based on the analysis above (e.g., \"Buy/Hold/Neutral/Sell\"), presented in a table.\nInclude investment ratings or recommendations from all sources, with the source and date clearly noted.\nIf you provide a combined recommendation based on different viewpoints, clearly explain the reasoning behind this integration.\n##6. Appendix & References\nList the data sources, analysis methods, important formulas, or chart descriptions used.\nAll references must come from the information extraction agent and the company financial data table provided, or publicly noted sources.\nFor differentiated viewpoints, provide full citation information (author, institution, date) and present this in a table.\n\n\n\n\nLanguage Style: Financial, professional, precise, and analytical.\nViewpoint Retention: When there are multiple viewpoints and conclusions, all must be retained and compared. You cannot choose only one.\nCitations: When specific data or viewpoints are referenced, include the source in parentheses (e.g., Source: Morgan Stanley Research, 2024-05-07).\nFacts: All data and conclusions must come from the information extraction agent or their noted legitimate sources. No fabrication is allowed.\nReadability: Use short paragraphs and bullet points to make it easy for professional readers to grasp key information and see the differences in viewpoints.\n\n\n\n\nGenerate a complete investment research report that meets investment banking industry standards, which can be directly used for institutional investment internal reference, while faithfully retaining differentiated viewpoints from various reports and providing the corresponding analysis.\n", + "temperature": 0.1, + "temperatureEnabled": false, + "tools": [], + "topPEnabled": false, + "top_p": 0.3, + "user_prompt": "", + "visual_files_var": "" + } + }, + "upstream": [ + "Agent:SadDodosRescue" + ] + }, + "CodeExec:LightSheepTrade": { + "downstream": [ + "Message:OliveLawsArgue" + ], + "obj": { + "component_name": "CodeExec", + "params": { + "arguments": { + "input_text": "YahooFinance:QuickAdsDig@report" + }, + "lang": "python", + "outputs": { + "md_table": { + "type": "String", + "value": "" + } + }, + "script": "import re\n\ndef format_number(value: str) -> str:\n \"\"\"Convert scientific notation or floating-point numbers to comma-separated numbers\"\"\"\n try:\n num = float(value)\n if num.is_integer():\n return f\"{int(num):,}\" # If it's an integer, format without decimal places\n else:\n return f\"{num:,.2f}\" # Otherwise, keep two decimal places and add commas\n except:\n return value # Return the original value if it's not a number (e.g., \u2014 or empty)\n\ndef extract_md_table_single_column(input_text: str) -> str:\n # Use English indicators directly\n indicators = [\n \"Total Assets\", \"Total Equity\", \"Tangible Book Value\", \"Total Debt\", \n \"Net Debt\", \"Cash And Cash Equivalents\", \"Working Capital\", \n \"Long Term Debt\", \"Common Stock Equity\", \"Ordinary Shares Number\"\n ]\n \n # Core indicators and their corresponding units\n unit_map = {\n \"Total Assets\": \"USD\",\n \"Total Equity\": \"USD\",\n \"Tangible Book Value\": \"USD\",\n \"Total Debt\": \"USD\",\n \"Net Debt\": \"USD\",\n \"Cash And Cash Equivalents\": \"USD\",\n \"Working Capital\": \"USD\",\n \"Long Term Debt\": \"USD\",\n \"Common Stock Equity\": \"USD\",\n \"Ordinary Shares Number\": \"Shares\"\n }\n\n lines = input_text.splitlines()\n\n # Automatically detect the date column, keeping only the first one\n date_pattern = r\"\\d{4}-\\d{2}-\\d{2}\"\n header_line = \"\"\n for line in lines:\n if re.search(date_pattern, line):\n header_line = line\n break\n\n if not header_line:\n raise ValueError(\"Date column header row not found\")\n\n dates = re.findall(date_pattern, header_line)\n first_date = dates[0] # Keep only the first date\n header = f\"| Indicator | {first_date} |\"\n divider = \"|------------------------|------------|\"\n\n rows = []\n for ind in indicators:\n unit = unit_map.get(ind, \"\")\n display_ind = f\"{ind} ({unit})\" if unit else ind\n\n found = False\n for line in lines:\n if ind in line:\n # Match numbers and possible units\n pattern = r\"(nan|[0-9\\.]+(?:[eE][+-]?\\d+)?)\"\n values = re.findall(pattern, line)\n # Replace 'nan' with '\u2014' and format the number\n first_value = values[0].strip() if values and values[0].strip().lower() != \"nan\" else \"\u2014\"\n first_value = format_number(first_value) if first_value != \"\u2014\" else \"\u2014\"\n rows.append(f\"| {display_ind} | {first_value} |\")\n found = True\n break\n if not found:\n rows.append(f\"| {display_ind} | \u2014 |\")\n\n md_table = \"\\n\".join([header, divider] + rows)\n return md_table\n\ndef main(input_text: str):\n return extract_md_table_single_column(input_text)\n" + } + }, + "upstream": [ + "YahooFinance:QuickAdsDig" + ] + }, + "Message:OliveLawsArgue": { + "downstream": [], + "obj": { + "component_name": "Message", + "params": { + "content": [ + "Company financial statements:\n\n{CodeExec:LightSheepTrade@md_table}\n\n\n{Agent:SharpSlothsSlide@content}" + ] + } + }, + "upstream": [ + "Agent:SharpSlothsSlide", + "CodeExec:LightSheepTrade" + ] + }, + "Message:TwentyBanksLeave": { + "downstream": [], + "obj": { + "component_name": "Message", + "params": { + "content": [ + "Your query is not supported." + ] + } + }, + "upstream": [ + "Switch:FluffyCoinsSell" + ] + }, + "Switch:FluffyCoinsSell": { + "downstream": [ + "YahooFinance:QuickAdsDig", + "Agent:SadDodosRescue", + "Message:TwentyBanksLeave" + ], + "obj": { + "component_name": "Switch", + "params": { + "conditions": [ + { + "items": [ + { + "cpn_id": "Agent:ManyToesBrush@content", + "operator": "not contains", + "value": "Not Found" + } + ], + "logical_operator": "and", + "to": [ + "YahooFinance:QuickAdsDig", + "Agent:SadDodosRescue" + ] + } + ], + "end_cpn_ids": [ + "Message:TwentyBanksLeave" + ] + } + }, + "upstream": [ + "Agent:ManyToesBrush" + ] + }, + "YahooFinance:QuickAdsDig": { + "downstream": [ + "CodeExec:LightSheepTrade" + ], + "obj": { + "component_name": "YahooFinance", + "params": { + "balance_sheet": true, + "cash_flow_statement": false, + "financials": false, + "history": false, + "info": false, + "news": false, + "outputs": { + "report": { + "type": "string", + "value": "" + } + }, + "stock_code": "sys.query" + } + }, + "upstream": [ + "Switch:FluffyCoinsSell" + ] + }, + "begin": { + "downstream": [ + "Agent:ManyToesBrush" + ], + "obj": { + "component_name": "Begin", + "params": { + "enablePrologue": true, + "inputs": {}, + "mode": "conversational", + "prologue": "Hi! I'm your assistant. What can I do for you?" + } + }, + "upstream": [] + } + }, + "globals": { + "sys.conversation_turns": 0, + "sys.files": [], + "sys.query": "", + "sys.user_id": "" + }, + "graph": { + "edges": [ + { + "data": { + "isHovered": false + }, + "id": "xy-edge__Agent:ManyToesBrushtool-Tool:AngryRabbitsPlayend", + "source": "Agent:ManyToesBrush", + "sourceHandle": "tool", + "target": "Tool:AngryRabbitsPlay", + "targetHandle": "end" + }, + { + "data": { + "isHovered": false + }, + "id": "xy-edge__Agent:SadDodosRescuestart-Agent:SharpSlothsSlideend", + "source": "Agent:SadDodosRescue", + "sourceHandle": "start", + "target": "Agent:SharpSlothsSlide", + "targetHandle": "end" + }, + { + "data": { + "isHovered": false + }, + "id": "xy-edge__Agent:SadDodosRescueagentBottom-Agent:MightyIdeasGlowagentTop", + "source": "Agent:SadDodosRescue", + "sourceHandle": "agentBottom", + "target": "Agent:MightyIdeasGlow", + "targetHandle": "agentTop" + }, + { + "data": { + "isHovered": false + }, + "id": "xy-edge__Agent:MightyIdeasGlowtool-Tool:FullIconsStopend", + "source": "Agent:MightyIdeasGlow", + "sourceHandle": "tool", + "target": "Tool:FullIconsStop", + "targetHandle": "end" + }, + { + "data": { + "isHovered": false + }, + "id": "xy-edge__YahooFinance:QuickAdsDigstart-CodeExec:LightSheepTradeend", + "markerEnd": "logo", + "source": "YahooFinance:QuickAdsDig", + "sourceHandle": "start", + "style": { + "stroke": "rgba(91, 93, 106, 1)", + "strokeWidth": 1 + }, + "target": "CodeExec:LightSheepTrade", + "targetHandle": "end", + "type": "buttonEdge", + "zIndex": 1001 + }, + { + "data": { + "isHovered": false + }, + "id": "xy-edge__Agent:SharpSlothsSlidestart-Message:OliveLawsArgueend", + "markerEnd": "logo", + "source": "Agent:SharpSlothsSlide", + "sourceHandle": "start", + "style": { + "stroke": "rgba(151, 154, 171, 1)", + "strokeWidth": 1 + }, + "target": "Message:OliveLawsArgue", + "targetHandle": "end", + "type": "buttonEdge", + "zIndex": 1001 + }, + { + "data": { + "isHovered": false + }, + "id": "xy-edge__beginstart-Agent:ManyToesBrushend", + "markerEnd": "logo", + "source": "begin", + "sourceHandle": "start", + "style": { + "stroke": "rgba(151, 154, 171, 1)", + "strokeWidth": 1 + }, + "target": "Agent:ManyToesBrush", + "targetHandle": "end", + "type": "buttonEdge", + "zIndex": 1001 + }, + { + "data": { + "isHovered": false + }, + "id": "xy-edge__Agent:ManyToesBrushstart-Switch:FluffyCoinsSellend", + "source": "Agent:ManyToesBrush", + "sourceHandle": "start", + "target": "Switch:FluffyCoinsSell", + "targetHandle": "end" + }, + { + "data": { + "isHovered": false + }, + "id": "xy-edge__Switch:FluffyCoinsSellCase 1-YahooFinance:QuickAdsDigend", + "markerEnd": "logo", + "source": "Switch:FluffyCoinsSell", + "sourceHandle": "Case 1", + "style": { + "stroke": "rgba(151, 154, 171, 1)", + "strokeWidth": 1 + }, + "target": "YahooFinance:QuickAdsDig", + "targetHandle": "end", + "type": "buttonEdge", + "zIndex": 1001 + }, + { + "data": { + "isHovered": false + }, + "id": "xy-edge__Switch:FluffyCoinsSellCase 1-Agent:SadDodosRescueend", + "markerEnd": "logo", + "source": "Switch:FluffyCoinsSell", + "sourceHandle": "Case 1", + "style": { + "stroke": "rgba(151, 154, 171, 1)", + "strokeWidth": 1 + }, + "target": "Agent:SadDodosRescue", + "targetHandle": "end", + "type": "buttonEdge", + "zIndex": 1001 + }, + { + "data": { + "isHovered": false + }, + "id": "xy-edge__Switch:FluffyCoinsSellend_cpn_ids-Message:TwentyBanksLeaveend", + "markerEnd": "logo", + "source": "Switch:FluffyCoinsSell", + "sourceHandle": "end_cpn_ids", + "style": { + "stroke": "rgba(151, 154, 171, 1)", + "strokeWidth": 1 + }, + "target": "Message:TwentyBanksLeave", + "targetHandle": "end", + "type": "buttonEdge", + "zIndex": 1001 + }, + { + "data": { + "isHovered": false + }, + "id": "xy-edge__CodeExec:LightSheepTradestart-Message:OliveLawsArgueend", + "markerEnd": "logo", + "source": "CodeExec:LightSheepTrade", + "sourceHandle": "start", + "style": { + "stroke": "rgba(91, 93, 106, 1)", + "strokeWidth": 1 + }, + "target": "Message:OliveLawsArgue", + "targetHandle": "end", + "type": "buttonEdge", + "zIndex": 1001 + }, + { + "data": { + "isHovered": false + }, + "id": "xy-edge__Agent:SadDodosRescuetool-Tool:ClearKiwisRollend", + "source": "Agent:SadDodosRescue", + "sourceHandle": "tool", + "target": "Tool:ClearKiwisRoll", + "targetHandle": "end" + } + ], + "nodes": [ + { + "data": { + "form": { + "enablePrologue": true, + "inputs": {}, + "mode": "conversational", + "prologue": "Hi! I'm your assistant. What can I do for you?" + }, + "label": "Begin", + "name": "begin" + }, + "dragging": false, + "id": "begin", + "measured": { + "height": 48, + "width": 200 + }, + "position": { + "x": -250.58492312820874, + "y": 304.13718826989873 + }, + "selected": false, + "sourcePosition": "left", + "targetPosition": "right", + "type": "beginNode" + }, + { + "data": { + "form": { + "cite": true, + "delay_after_error": 1, + "description": "", + "exception_default_value": "", + "exception_goto": [], + "exception_method": "", + "frequencyPenaltyEnabled": false, + "frequency_penalty": 0.7, + "llm_id": "qwen-turbo-latest@Tongyi-Qianwen", + "maxTokensEnabled": false, + "max_retries": 3, + "max_rounds": 1, + "max_tokens": 256, + "mcp": [], + "message_history_window_size": 12, + "outputs": { + "content": { + "type": "string", + "value": "" + } + }, + "presencePenaltyEnabled": false, + "presence_penalty": 0.4, + "prompts": [ + { + "content": "{sys.query}", + "role": "user" + } + ], + "sys_prompt": " \n\nYour responsibility is to identify and extract the stock name or abbreviation from the user's natural language query and return the corresponding unique stock code.\n\n \n\n\n\n \n\n1. Only one result is allowed: - If a stock is identified \u2192 only return the corresponding stock code; - If no stock is identified \u2192 only return \u201cNot Found\u201d. 2. **Do not** output any additional text, punctuation, explanation, prefixes, or line breaks. 3. The output must strictly adhere to the . \n\n\n\n\n\nOnly output the stock code (e.g., AAPL or 600519)\nOr only output \u201cNot Found\u201d\n\n\n\n\nUser input: \u201cHelp me check the research report of Apple\u201d \u2192 Output: AAPL\nUser input: \u201cHow is Maotai\u2019s financial performance\u201d \u2192 Output: 600519\nUser input: \u201cHow is the Shanghai Composite Index doing today\u201d \u2192 Output: Not Found\n\n\n\n - Tavily Search: Use this tool when you are unsure of the stock code. - If you are confident, you do not need to use the tool. \n\n\n\n\n\nOnly output the result, no explanations, hints, or notes allowed.\nThe output can only be the stock code or \u201cNot Found\u201d, otherwise, it is considered an incorrect answer.\n", + "temperature": 0.1, + "temperatureEnabled": false, + "tools": [ + { + "component_name": "TavilySearch", + "name": "TavilySearch", + "params": { + "api_key": "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", + "days": 7, + "exclude_domains": [], + "include_answer": false, + "include_domains": [], + "include_image_descriptions": false, + "include_images": false, + "include_raw_content": true, + "max_results": 5, + "outputs": { + "formalized_content": { + "type": "string", + "value": "" + }, + "json": { + "type": "Array", + "value": [] + } + }, + "query": "sys.query", + "search_depth": "basic", + "topic": "general" + } + } + ], + "topPEnabled": false, + "top_p": 0.3, + "user_prompt": "", + "visual_files_var": "" + }, + "label": "Agent", + "name": "Extract Stock Code Agent" + }, + "dragging": false, + "id": "Agent:ManyToesBrush", + "measured": { + "height": 76, + "width": 200 + }, + "position": { + "x": 1.784314979916303, + "y": 285.7261182739586 + }, + "selected": false, + "sourcePosition": "right", + "targetPosition": "left", + "type": "agentNode" + }, + { + "data": { + "form": { + "description": "This is an agent for a specific task.", + "user_prompt": "This is the order you need to send to the agent." + }, + "label": "Tool", + "name": "flow.tool_0" + }, + "dragging": false, + "id": "Tool:AngryRabbitsPlay", + "measured": { + "height": 48, + "width": 200 + }, + "position": { + "x": -1.1174997064789522, + "y": 392.2709327777357 + }, + "selected": false, + "sourcePosition": "right", + "targetPosition": "left", + "type": "toolNode" + }, + { + "data": { + "form": { + "content": [ + "Your query is not supported." + ] + }, + "label": "Message", + "name": "Reply to irrelevant message node" + }, + "dragging": false, + "id": "Message:TwentyBanksLeave", + "measured": { + "height": 48, + "width": 200 + }, + "position": { + "x": 1274.991898394738, + "y": 540.2215056031129 + }, + "selected": false, + "sourcePosition": "right", + "targetPosition": "left", + "type": "messageNode" + }, + { + "data": { + "form": { + "balance_sheet": true, + "cash_flow_statement": false, + "financials": false, + "history": false, + "info": false, + "news": false, + "outputs": { + "report": { + "type": "string", + "value": "" + } + }, + "stock_code": "sys.query" + }, + "label": "YahooFinance", + "name": "YahooFinance" + }, + "dragging": false, + "id": "YahooFinance:QuickAdsDig", + "measured": { + "height": 48, + "width": 200 + }, + "position": { + "x": 676.5378050046916, + "y": 74.09222900489664 + }, + "selected": false, + "sourcePosition": "right", + "targetPosition": "left", + "type": "ragNode" + }, + { + "data": { + "form": { + "cite": true, + "delay_after_error": 1, + "description": "", + "exception_default_value": "", + "exception_goto": [], + "exception_method": "", + "frequencyPenaltyEnabled": false, + "frequency_penalty": 0.7, + "llm_id": "kimi-k2-turbo-preview@Moonshot", + "maxTokensEnabled": false, + "max_retries": 3, + "max_rounds": 1, + "max_tokens": 256, + "mcp": [ + { + "mcp_id": "30d6ef8ea8d511f0828382e3548809fa", + "tools": {} + } + ], + "message_history_window_size": 12, + "outputs": { + "content": { + "type": "string", + "value": "" + } + }, + "presencePenaltyEnabled": false, + "presence_penalty": 0.4, + "prompts": [ + { + "content": "user's query is {sys.query}\n\n\n{Agent:ManyToesBrush@content}\n", + "role": "user" + } + ], + "sys_prompt": " \n\nYou are the information extraction agent. You understand the user\u2019s query and delegate tasks to investoday and the internal research report retrieval agent. \n\n \n\n\n\n 1. Based on the stock code output by the \"Extract Stock Code\" agent, call investoday's list_news to retrieve the latest authoritative research reports and views, and save all publicly available key information. \n\n2. Call the \"Internal Research Report Retrieval Agent\" and save the full text of the research report output. \n\n3. Output the content retrieved from investoday and the Internal Research Report Retrieval Agent in full. \n\n\n\n\n\nThe output must be divided into two sections:\n#1. Title: \u201cinvestoday\u201d\nDirectly output the content collected from investoday without any additional processing.\n#2. Title: \"Internal Research Report Retrieval Agent\"\nDirectly output the content provided by the Internal Research Report Retrieval Agent.\n", + "temperature": 0.1, + "temperatureEnabled": false, + "tools": [], + "topPEnabled": false, + "top_p": 0.3, + "user_prompt": "", + "visual_files_var": "" + }, + "label": "Agent", + "name": "Information Extraction Agent" + }, + "dragging": false, + "id": "Agent:SadDodosRescue", + "measured": { + "height": 76, + "width": 200 + }, + "position": { + "x": 674.0210917308762, + "y": 154.63747017677127 + }, + "selected": false, + "sourcePosition": "right", + "targetPosition": "left", + "type": "agentNode" + }, + { + "data": { + "form": { + "arguments": { + "input_text": "YahooFinance:QuickAdsDig@report" + }, + "lang": "python", + "outputs": { + "md_table": { + "type": "String", + "value": "" + } + }, + "script": "import re\n\ndef format_number(value: str) -> str:\n \"\"\"Convert scientific notation or floating-point numbers to comma-separated numbers\"\"\"\n try:\n num = float(value)\n if num.is_integer():\n return f\"{int(num):,}\" # If it's an integer, format without decimal places\n else:\n return f\"{num:,.2f}\" # Otherwise, keep two decimal places and add commas\n except:\n return value # Return the original value if it's not a number (e.g., \u2014 or empty)\n\ndef extract_md_table_single_column(input_text: str) -> str:\n # Use English indicators directly\n indicators = [\n \"Total Assets\", \"Total Equity\", \"Tangible Book Value\", \"Total Debt\", \n \"Net Debt\", \"Cash And Cash Equivalents\", \"Working Capital\", \n \"Long Term Debt\", \"Common Stock Equity\", \"Ordinary Shares Number\"\n ]\n \n # Core indicators and their corresponding units\n unit_map = {\n \"Total Assets\": \"USD\",\n \"Total Equity\": \"USD\",\n \"Tangible Book Value\": \"USD\",\n \"Total Debt\": \"USD\",\n \"Net Debt\": \"USD\",\n \"Cash And Cash Equivalents\": \"USD\",\n \"Working Capital\": \"USD\",\n \"Long Term Debt\": \"USD\",\n \"Common Stock Equity\": \"USD\",\n \"Ordinary Shares Number\": \"Shares\"\n }\n\n lines = input_text.splitlines()\n\n # Automatically detect the date column, keeping only the first one\n date_pattern = r\"\\d{4}-\\d{2}-\\d{2}\"\n header_line = \"\"\n for line in lines:\n if re.search(date_pattern, line):\n header_line = line\n break\n\n if not header_line:\n raise ValueError(\"Date column header row not found\")\n\n dates = re.findall(date_pattern, header_line)\n first_date = dates[0] # Keep only the first date\n header = f\"| Indicator | {first_date} |\"\n divider = \"|------------------------|------------|\"\n\n rows = []\n for ind in indicators:\n unit = unit_map.get(ind, \"\")\n display_ind = f\"{ind} ({unit})\" if unit else ind\n\n found = False\n for line in lines:\n if ind in line:\n # Match numbers and possible units\n pattern = r\"(nan|[0-9\\.]+(?:[eE][+-]?\\d+)?)\"\n values = re.findall(pattern, line)\n # Replace 'nan' with '\u2014' and format the number\n first_value = values[0].strip() if values and values[0].strip().lower() != \"nan\" else \"\u2014\"\n first_value = format_number(first_value) if first_value != \"\u2014\" else \"\u2014\"\n rows.append(f\"| {display_ind} | {first_value} |\")\n found = True\n break\n if not found:\n rows.append(f\"| {display_ind} | \u2014 |\")\n\n md_table = \"\\n\".join([header, divider] + rows)\n return md_table\n\ndef main(input_text: str):\n return extract_md_table_single_column(input_text)\n" + }, + "label": "CodeExec", + "name": "Code-generated balance sheet" + }, + "dragging": false, + "id": "CodeExec:LightSheepTrade", + "measured": { + "height": 48, + "width": 200 + }, + "position": { + "x": 970.444642975358, + "y": 74.04386270784316 + }, + "selected": false, + "sourcePosition": "right", + "targetPosition": "left", + "type": "ragNode" + }, + { + "data": { + "form": { + "cite": true, + "delay_after_error": 1, + "description": "", + "exception_default_value": "", + "exception_goto": [], + "exception_method": "", + "frequencyPenaltyEnabled": false, + "frequency_penalty": 0.7, + "llm_id": "qwen-turbo-latest@Tongyi-Qianwen", + "maxTokensEnabled": false, + "max_retries": 3, + "max_rounds": 1, + "max_tokens": 256, + "mcp": [], + "message_history_window_size": 12, + "outputs": { + "content": { + "type": "string", + "value": "" + } + }, + "presencePenaltyEnabled": false, + "presence_penalty": 0.4, + "prompts": [ + { + "content": "User query questions:\n\n\n\n{sys.query}\n\n\n\nInformation Extraction Agent:\n\n{Agent:SadDodosRescue@content}", + "role": "user" + } + ], + "sys_prompt": " \n\nYou are a senior investment banking (IB) analyst with years of experience in capital market research. You excel at writing investment research reports covering publicly listed companies, industries, and macroeconomics. You possess strong financial analysis skills and industry insights, combining quantitative and qualitative analysis to provide high-value references for investment decisions. \n\n**You are able to retain and present differentiated viewpoints from various reports and sources in your research, and when discrepancies arise, you do not merge them into a single conclusion. Instead, you compare and analyze the differences.** \n\n\n \n\n\n\n\n \n\nYou will receive financial information extracted by the information extraction agent.\n\n \n\n\n\nBased on the content returned by the information extraction agent (no fabrication of data), write a professional, complete, and structured investment research report. The report must be logically rigorous, clearly organized, and use professional language, suitable for reference by fund managers, institutional investors, and other professional readers.\nWhen there are differences in analysis or forecasts between different reports or institutions, you must list and identify the sources in the report. You should not select only one viewpoint. You need to point out the differences, their possible causes, and their impact on investment judgments.\n\n\n\n\n##1. Summary\nProvide a concise overview of the company\u2019s core business, recent performance, industry positioning, and major investment highlights.\nSummarize key conclusions in 3-5 sentences.\nHighlight any discrepancies in core conclusions and briefly describe the differing viewpoints and areas of disagreement.\n##2. Company Overview\nDescribe the company's main business, core products/services, market share, competitive advantages, and business model.\nHighlight any differences in the description of the company\u2019s market position or competitive advantages from different sources. Present and compare these differences.\n##3. Recent Financial Performance\nSummarize key metrics from the latest financial report (e.g., revenue, net profit, gross margin, EPS).\nHighlight the drivers behind the trends and compare the differential analyses from different reports. Present this comparison in a table.\n##4. Industry Trends & Opportunities\nOverview of industry development trends, market size, and major drivers.\nIf different sources provide differing forecasts for industry growth rates, technological trends, or competitive landscape, list these and provide background information. Present this comparison in a table.\n##5. Investment Recommendation\nProvide a clear investment recommendation based on the analysis above (e.g., \"Buy/Hold/Neutral/Sell\"), presented in a table.\nInclude investment ratings or recommendations from all sources, with the source and date clearly noted.\nIf you provide a combined recommendation based on different viewpoints, clearly explain the reasoning behind this integration.\n##6. Appendix & References\nList the data sources, analysis methods, important formulas, or chart descriptions used.\nAll references must come from the information extraction agent and the company financial data table provided, or publicly noted sources.\nFor differentiated viewpoints, provide full citation information (author, institution, date) and present this in a table.\n\n\n\n\nLanguage Style: Financial, professional, precise, and analytical.\nViewpoint Retention: When there are multiple viewpoints and conclusions, all must be retained and compared. You cannot choose only one.\nCitations: When specific data or viewpoints are referenced, include the source in parentheses (e.g., Source: Morgan Stanley Research, 2024-05-07).\nFacts: All data and conclusions must come from the information extraction agent or their noted legitimate sources. No fabrication is allowed.\nReadability: Use short paragraphs and bullet points to make it easy for professional readers to grasp key information and see the differences in viewpoints.\n\n\n\n\nGenerate a complete investment research report that meets investment banking industry standards, which can be directly used for institutional investment internal reference, while faithfully retaining differentiated viewpoints from various reports and providing the corresponding analysis.\n", + "temperature": 0.1, + "temperatureEnabled": false, + "tools": [], + "topPEnabled": false, + "top_p": 0.3, + "user_prompt": "", + "visual_files_var": "" + }, + "label": "Agent", + "name": "Research report generation agent" + }, + "id": "Agent:SharpSlothsSlide", + "measured": { + "height": 76, + "width": 200 + }, + "position": { + "x": 974.0210917308762, + "y": 154.63747017677127 + }, + "selected": false, + "sourcePosition": "right", + "targetPosition": "left", + "type": "agentNode" + }, + { + "data": { + "form": { + "content": [ + "Company financial statements:\n\n{CodeExec:LightSheepTrade@md_table}\n\n\n{Agent:SharpSlothsSlide@content}" + ] + }, + "label": "Message", + "name": "Reply message node" + }, + "dragging": false, + "id": "Message:OliveLawsArgue", + "measured": { + "height": 48, + "width": 200 + }, + "position": { + "x": 1279.3354680249918, + "y": 83.53099404318621 + }, + "selected": false, + "sourcePosition": "right", + "targetPosition": "left", + "type": "messageNode" + }, + { + "data": { + "form": { + "cite": true, + "delay_after_error": 1, + "description": "You are a senior financial content analyst who can accurately identify the companies, stock codes, industries or topics mentioned in user questions, and completely extract relevant research content from the knowledge base to ensure that data, opinions and conclusions are not lost.", + "exception_default_value": "", + "exception_goto": [], + "exception_method": "", + "frequencyPenaltyEnabled": false, + "frequency_penalty": 0.7, + "llm_id": "kimi-k2-turbo-preview@Moonshot", + "maxTokensEnabled": false, + "max_retries": 3, + "max_rounds": 1, + "max_tokens": 256, + "mcp": [], + "message_history_window_size": 12, + "outputs": { + "content": { + "type": "string", + "value": "" + } + }, + "presencePenaltyEnabled": false, + "presence_penalty": 0.4, + "prompts": [ + { + "content": "{sys.query}", + "role": "user" + } + ], + "sys_prompt": " \n\nRead user input \u2192 Identify the involved company/stock (supports abbreviations, full names, codes, and aliases) \u2192 Retrieve the most relevant research reports from the knowledge base \u2192 Output the full text of the research report, retaining the original format, data, chart descriptions, and risk warnings. \n\n\n\n\n\n \n\n1. Exact Match: Prioritize exact matches of company full names and stock codes. \n\n2. Content Fidelity: Fully retain the research report text stored in the knowledge base without deletion, modification, or omission of paragraphs. \n\n3. Original Data: Retain table data, dates, units, etc., in their original form. \n\n4. Complete Viewpoints: Include investment logic, financial analysis, industry comparisons, earnings forecasts, valuation methods, risk warnings, etc. \n\n5. Merging Multiple Reports: If there are multiple relevant research reports, output them in reverse chronological order. \n\n\n\n6. No Results Feedback: If no matching reports are found, output \u201cNo related research reports available in the knowledge base.\u201d\n\n\n\n ", + "temperature": 0.1, + "temperatureEnabled": false, + "tools": [ + { + "component_name": "Retrieval", + "name": "Retrieval", + "params": { + "cross_languages": [], + "description": "A knowledge base of research reports on stock analysis by senior experts", + "empty_response": "", + "kb_ids": [ + "60c53ed89acc11f0bc1e7a2a6d0b2755" + ], + "keywords_similarity_weight": 0.7, + "outputs": { + "formalized_content": { + "type": "string", + "value": "" + } + }, + "rerank_id": "", + "similarity_threshold": 0.2, + "top_k": 1024, + "top_n": 8, + "use_kg": false + } + } + ], + "topPEnabled": false, + "top_p": 0.3, + "user_prompt": "This is the order you need to send to the agent.", + "visual_files_var": "" + }, + "label": "Agent", + "name": "Internal Research Report Retrieval Agent" + }, + "dragging": false, + "id": "Agent:MightyIdeasGlow", + "measured": { + "height": 76, + "width": 200 + }, + "position": { + "x": 787.966928431608, + "y": 270.12089782504677 + }, + "selected": false, + "sourcePosition": "right", + "targetPosition": "left", + "type": "agentNode" + }, + { + "data": { + "form": { + "description": "This is an agent for a specific task.", + "user_prompt": "This is the order you need to send to the agent." + }, + "label": "Tool", + "name": "flow.tool_1" + }, + "dragging": false, + "id": "Tool:FullIconsStop", + "measured": { + "height": 48, + "width": 200 + }, + "position": { + "x": 786.0879409003913, + "y": 373.7912225392144 + }, + "selected": false, + "sourcePosition": "right", + "targetPosition": "left", + "type": "toolNode" + }, + { + "data": { + "form": { + "conditions": [ + { + "items": [ + { + "cpn_id": "Agent:ManyToesBrush@content", + "operator": "not contains", + "value": "Not Found" + } + ], + "logical_operator": "and", + "to": [ + "YahooFinance:QuickAdsDig", + "Agent:SadDodosRescue" + ] + } + ], + "end_cpn_ids": [ + "Message:TwentyBanksLeave" + ] + }, + "label": "Switch", + "name": "Switch" + }, + "dragging": false, + "id": "Switch:FluffyCoinsSell", + "measured": { + "height": 146, + "width": 200 + }, + "position": { + "x": 244.5649388872756, + "y": 249.25263304293162 + }, + "selected": false, + "sourcePosition": "right", + "targetPosition": "left", + "type": "switchNode" + }, + { + "data": { + "form": { + "description": "This is an agent for a specific task.", + "user_prompt": "This is the order you need to send to the agent." + }, + "label": "Tool", + "name": "flow.tool_2" + }, + "id": "Tool:ClearKiwisRoll", + "measured": { + "height": 44, + "width": 200 + }, + "position": { + "x": 592.0210917308762, + "y": 294.6374701767713 + }, + "sourcePosition": "right", + "targetPosition": "left", + "type": "toolNode" + }, + { + "data": { + "form": { + "text": "Regarding the MCP message for the Information Extraction Agent: You must manually add an MCP in MCP Servers before you can use it!" + }, + "label": "Note", + "name": "MCP Note" + }, + "dragHandle": ".note-drag-handle", + "dragging": false, + "height": 185, + "id": "Note:SadWallsSniff", + "measured": { + "height": 185, + "width": 328 + }, + "position": { + "x": 527.9711365245946, + "y": 448.2236919343899 + }, + "resizing": false, + "selected": false, + "sourcePosition": "right", + "targetPosition": "left", + "type": "noteNode", + "width": 328 + } + ] + }, + "history": [], + "messages": [], + "path": [], + "retrieval": [] + }, + "avatar": + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAABn4SURBVHgBXZoJkF3nVef/79779rWX16u61Vq6ZVmyJDuObCvYshMlYIIdB5LBhmTGMzWTYiBTSaaKmoGpqShVw0zN1FRNqKEIFBAoIOBKKAfHwY4xxsYxwotsy9rVi1rd6vV199v35V5+3+1AAVJ1vffuu/f7zneW//mfc15A/+Lfjz359MNW3fuUnYk9YbU6U5Zty/Ikm1ev46ob8Pz3tsWfbclzPYVDIVmOrUCABQKWbN7bfPB474QcRUNhBbgW4zUcjapUKqsnnsskZbO2eTbkBLk3JIc1rJ4UCvM5HJZlWYrY9gXHCV6wgpGv/dtHH7j1T+UN/MObh594IlPrhb9qR2NftpptBZJRBSIhhYVgpbqCoaC8/oTcYl1WPKJAq6tALCwjdchx5JgDNdqyg+Ygln/ITCatXq+nTrfLZ+7hrydXQSMs96mHMoJBBbg3GGUtVwpHgrK6rqxUXBFzIKEE9rFY00IZqUTi66Fg8mufvntf8R8PcOLhJzJet/1qMBw6EbAC/qI8pmCQTTkEIvmbukbjfG85lnrdngJGQ+2ev6mdScmqcjjXnGlXL5n+Pl9wY6Fao67+dEaxWExb21sYCqETEfW430HLxqIO6zh85oIinr1rAfYMRiOKRMJyGy3tO3lC+dXchVC9/cinH7m76JiNOp3WV23LOtG12JwHOmgy1B9Wj1cLtVi+pjgGr6iRa0ZAj7dNDmSp2eRKsaIgmxhthlCAsUypUJSN5RyjgFgEwbcVjUR40vc0uWja9ZDYDuNuXMMdXRQV4VRuEoFZw8hj8yw+hbt21Wz0tC8dOpHz2l9lma8E7rj/4algPLkYxB97+YqcRAL3aMrqS7M5Wwfw/VwJTaL54T51d8qy03E5TRyV14Dn+Ro3GwUKNXkccDCdVjKVVCKe0E4+Lyyrbq+rdCqFS7mq1Rsc3vZ9P2xci++NBQIow8Idzf1mP/tHsdE3Nq7U4KA25m9q7z0ndDpU16tzOXUC1iOO5VlfVQNNtjucGH3Xar67YHN5NTYxAWu0z8Iq12XjUkGUZhk3abV9bRoNB/BZOxYVkvrPlAnUBuuGEabdbvs+7xILAYu7ccOeSyzgJhag0Ot2FHM8RZwQ1z2VUGCMg6Vw3irPRtot3T2UUqUQ1VUUUMcz2h28JBR8wuHfCRMgSKgEiwXcnlqurRSCVBEuhNm6xi2M0/BwCGEttNk1aNHoqMt95lBWnoODHBYC1+t1P4iDuEej2fTdqtXk3k6PuAr58WEQzLh7h/WiWKrH/jsIHg7i81hCxjXZz+JznUN3fDl43iMOiZHTe1K6XHI/5bjdzglWUwqTCa0kcZlNNh5wLaUCHcV5nQ/sChMyiIPOM2y9ysYjJhRsNMm1ENG7wfWAZ/sRYizU35/ZxQncbKdYUnZgQMChytUKWwEC6ai6bVd2PIZQjrwymsUCAbzBigVVxxGMtV3jeuZQ0Zh6yLldbmrcQcHV6hRPYXJMVcEX0i6BB+hH8cdcQD7s1VkgGNgVqMs9YQKvznsH/yyxWYRrFSzXjxWDBHmP/+ZfF/hcWlrx/ThoUA0trq9v+ohkh3Ad4/MIYmKiFQIAeL5RqxPkIf+7HlZWraNIf7/6slnlgXIvFFN2ZFAXb81qYDCKXgCSRqOBiUEZTlTjMEiqWNdkEoPtATRl+Umoi/+af3Xu6ZnA61iqIrhlNI6weaeDdawfJbCAj0J7xkbwVVIWlu31+B7hYuSQ3E7RjwkbSG0R0NkTd6nDOne5Nf3t5QUlSBExN7Ybf2Zf3PLc+fPK7t0j59INuYWSQkMxjScIeuPDrttGe13cpqdxhGwg0J1YbBiV78MtjP/ZJlhNRkbLE7gMnqMjuNYI1w/ydxhtmCDtsoaLVnudjgpsVCmXWd/lCQ7GM3W0bN4E8PlOOadwe1vD00c001rSk86Svvnff0lhXKpjoBQloEPVAITt28sKED+l/LbaxMrf3d7STII0Ozg6cdagTogcXv8RhkcsH0zUAXiM6A4JbTqZ0DqRnzCLYuIWB2vgcr2Aq1KQLGoH/QRkGwQzMWHwn+csJOh2uj7UWhyyZfwbCwa8jt5/7o+0Ta4/eOiwduZuaO3l7yo6MKann/oZ/fDSgjZy2xrMZJQYzGp78aYqJMAmsVTBqmHyQprsbacHR86aU/aM2dFeDb/ukFXKbFZD41UEjSFcPwfLodUWB6sYC+DTLVyoymNBArChXbfxcwLPhklqk3vG/UydHugDhRpK4s8HjhxVEUEuvvis7Mkp3CMORenTzPEP6e3f/X/qC/e0MXtRP3dyWgcfeEh/M3dbaVwxv7yoaDIJYNiKRENYKKC1OtQlkx076wR3tdfGdNPplA6Fo1rpiyuOptrJGDjcUQbzbYV34yLUl1DQnCSb9P3UQhNWHz5b7/iIYyDU4Pz62qqS0zNam70OlwqrVqvKY+1LLz/r43ttcUl3/dKv6NCxuzQQS+q9v3hWze0cCS+mdbL4mFfTz5x5EKWFdeGdi6puFUArY9GO2ibIDceK9w+fDSK4IV4RkOD+ZJ/eLRXVIHA6YPcpJ6wqsRAjkLeMtuEjhp1ahty1jZ/h89WGuiSXHq7iEj9dPxZcpRE2MjisBn4LHOjAPffowEdO6s9+7WsKgCj1ekn3fuJT6i0ua+vVF/QTj/+0vvudb6uczykVsf0DF9bWNNrO6bGP3K3TD9ynhfVtza6sKZOIg1hhOW0PMxBoU5hmEI1/Z+OaxlNpPVho6GK7qWv1pu7L9OsmWTdG8FRMAHbI3CYH9yw/q5IMFQBNjBsGDe02Lkm8uKmMHnrqKX3/C78od3JI+Z0deVXPsCufA5k4s8k1VshTkcPWW1X9zu9/U2ee/iKWvqiDRayQW1e9PKNL589pdP8d+spPflLvF+7VH3zvJfUlyf4Dif6zSRDBQ8vzga7qwGezXNFGJq7j8bgWgJ5KteqTtgN9fVoCjiIEtFsFo+FGdgIt98FEh/uVJYY6HMBk6eGRSaya1PjHHtUvux/o987d0MLSvA4//ON6+09+T/EYGhwe0tHJ/VojX4RAt2igp5F4QLmZM7r01jmtzy9AQzrIs72bACtFrdy4rAMZR594/DP69st/K/vRfTNnJwYGdSltq238G9dx8e8eB1nz5BOsYqWhPHzFjkdV8LMoGm431K401YYzcYOa3PNYcsDPsPck0koQcEcP7pdz9G6dufV9pVrLOnn6jDb2fUgvZN/Wf3vxiv7/5Tm998IPwPqmocEajoV093haz11a0p0P/6Q2gc+b75wH8aAbYZgBlMOGuRZhteGdRT31rz8vuxSKn303vym71lKn1lQXHuMZhoZLyPAYWKeBQ5OVE2ijTQwEWmg/aIoN+Vm1iy+GiZ8Oad7UC3lcL3L0uG73DaiaGdV3fvkLmh7J6tr1eT374hv6TH9NS+EJxT/+pJbQdJ29ajxbw3oX1ne0tFWDKBa0j/g49oUv6o1v/IZSwKkRy2R1QFmlSkWVhYvIQBHR37P9oiWA9mWExTcdUMg4czgIfydIDTxW+D5LpbaIFz/ohXQxEdQS353JDirO9XcXlsH5nhr48qnsqDKkhxaIMb/d0xkCe2h4RNlIVrEBaXxqQDmgsUM+6IJuQYJ/C7YbTEX0vx8a1TeKgzrY2FQpPKMD/+Y/a/n5r8PH7lAXYBkcHZILvehtl0x5QjICUQhrmKh82uy1u/IMPJKg2qYGdluYLkLCsjSGqc9EEnppa01n7KzuiiX0/q0VdXmubTI2ya1+e0XNeo31Apo8OKjnt2v61rWc1rdXVHQiqj36H3R2tKKfff45BYHLkClWsEC8E6CYaatvZEQnT53RJ17/Hf3Cb35Dx6f3qvfEl7T1wm/iu6AeAGFSbGzqgJwuHwIBSDLUuNM15JY4IEm5Xl1uu+SXiB5m64EWNjh8Cfy1TCDji6/kNqnCgn7ZaMixqZNrhW19/oVXdP75H1DRuXrzc5+DJznK1sH5Xl73ffwxffG5D/Stn9oL/U4obmgHQmXhQs1AWyk3qDbWakEa5wtNZQCOrTIFFUqzf/q/qvZXvyWvUSJRwnapP5w2JvYTAsVHh4VMZdRD8yFyg21qXwQMUuQbSiBT5HsU4emEPKq3yFRW2q5Ty0I3sgNqLa6osQnRntpDVm9pemiPLv7wRe09OKOX1pb1c5/6rC6BAHcMxvTmRgMcH/C7HN1uW/BWnwSGgGir1dD1K7N6bbYAQ4mpZkXw9fYuR/r0r6j0xjPqLCygOFiuIVquoexGOOAwODSgINnXmhxVL+r4nQdDwFpNgpyMWy9X1douqIm7tCFUDVCpt1NT4/otJcgXJ3/1a3r2qSeJwYI28mUoOBifL1DAU8w3EXZzTQCK/t335nCXjrbZv4yCmlxsEVOIr/ztJZ0/d075cFq5qtm76bt1extiuL6o1plfkDNxp9Z28BBDpzsGv8O26rm8miXM1T8sG3+sr22rvrrp02mfjm7m8VfHr8yCbOyA3VncJ4OVZgbjClFwDH70x5W/eoE1cSk+mxhrVEuaOnAEV7J1ZGKPXpzNaSpOKkRhHsKbCsuQrBLWPDSY1P2PnNZLR+r6w3v79D+f+qTOHDuuPkPW68Ti9EE9VnxdH5uA2mzlTRIFDFnERdgA7QsPTSf8fgzIZNgedWrXdnz4MmTNweRDQOY8afSQ11UVpPLI0qdjZT2zsEr6h2ok+1VevKE9WNAUNGNZFNI3rEIuJ6cA23UMeQxq8+13DXFVkCQa7rV0lELgJzSnP/3jW7DSETnVDbXfe0cPHTuqn3/8DuVHjin3/Df1F7DWcDoLItEcMIL55Iu/4YCpEF210fadVFZXKV6yLD5H5WpyQwRh4hyC8l9RrHabw6fI4rMrc3oRmtAcPKIEkFqCTeYvvaMrf95TBDoxsGdEgc0lMi/dBRCvu1PVcDKjFtTkw7joTahMOU7yK6/r2389p1AyK2tjh07EIWoQMP/yVa2gbbv1kvbe/1E93D+h3/8//0M3dhoEdzx51sRAFhqNh1EiUib2D/l1QHEnpwrVWtcL+OjUAYEKwFzBaC3sl/dk4oI++8p5hX/3f2krlFZ3+E5de/YPlIgmNL5nQhMPP0jFRg29sqBj46Oau/CmjhcLNKkqSnaq2gbXTz/4UZ2bvaqdaJ+K759TDppcqHW1vbGmBofCjJSUZbyMcpaYS2DZH3vwPq0tL0NlYsmzploi/6ptagCE6vvwKTWSaa3PzWIHw/Hd3ToXAmazyB6ZOsDWnhyoU0ebn/lF/ZfcM7qymterlIT2+rJP7PpBpiLIVrhyVa14SjfefkclUG8Wyy1Ata9Tsa1vrSpfKSk5PqEWLtsaOqjilYu0WpoqNMoq4PdNINUJUYoCBk34kNeqI1NEj5w+SSfP3aUKPVO0AF8WUJWdPqZhTP3Jxde0Xu7qmc2GEtSwcSA2geAmjdAi023M37DSus3HLei550QVYh1TrfXDFCtAr7e8ItNrpRlCIB9Qkjr4Ku2XEC4RH0yBcFXNL8zq3r6sGmTxUIie7Ec/q635d+XemiMHOVqloLcWbmuIrsZQIqQyqJje3FE+PwLkAk8tgjadSPkcIwMmm2orBrY/dmxIowP4rOFFnomNgHYoKHKw1OIamh4aU5pDDONSMVNkgOGbb7yoyJ0EHAw2TEXmAIFOdLc5u7G6qg8ufeD3OD261IZXKdWnBsk0wsHqFPwFq6t74VwnP/N57T/1uKrJMW3TnVgvNbVM7rm+WdC5y8u6eHNFV2fnsQBYHIum1aY5G8CUywiTIv1u8toCaTrZMR35V4+qdv511fk+QcM1Wy3qKtB47Pj96myuq0XGDRtnQ8gwoGBaLpFT92vtjXN+R4LOAd3mpMK41Eg3o3mgOtppyTm0XxZuZFhwaWtdSStGXLh6/8AeFc+9qUH2egz6XSLj1mY/0Buv/5WaqYSEdTdrFE65oqnPLfxvXP0Hjyg7PqLJvig9mpDGTKGCK4yN7dXhz/1HEhqLE+imClldu+XXD6nHn1R++rBcGvpfeWVDb94qKXPqPgrvimqv/VCTExMaGhrS2DjtEGIiMTquSrFK5Ud2oAK0C2V1OIzpyb41f00TQ/2KkY+KuS0quYyvwPegK7eWb6saTum+8aSGIH5rPLdOgtuiOHJCYH9m/IDSR46B65sa2ajpnaDl04s21LoaoiVCnFjebteievMKhRhJz+Q1kGGHHLA3S8La069QzqMEzMndyCmEpgyDrVVrKtJvbR85pOr6Gq1yStECldk9R9VbXkX7pqMWg1Jwb6miWSyTpi4ILG/iCkkQDAinuGoYgIGtulgpBmfb3sirlGgYKoF746OJgWHdlY3qCKfMkLNuYsrvzBZ1bqPsN6uCbNRbvab9X/41xe4/tRvIUOggjdeN739X5ztx5QlEFzdzsZ6hHvPz8xT3PZ/Hp4ibI3D6mZkZWh10wa8v+Fk6ColLtoBvtHqrXlZsdFgdYNTtZzhCvFmAQg+reaU25aytCTpzh8ay2t8fUaJbM5k46He/6rWKtkxQUWVV+FzGfN9brSo+xiaVsuLtsiKT/Zp87Oe1NfuuD6tWi3Lz5qxuzb+l7ha8Z2YM7TO8IF+YdY8dO6ZVAtemOmvTGl8lcBugWwhopbWgRrHrT3tazbq6fUlYJh3xyUlZN5eQB9Tjth3K3CkaCxlK0EsJCiRIZ8yMraDl7SiUPzZzlJlVUYtvvaFzS+t6aaenSpv2IoyzaHqmqyua/bM/VHf1hgYbVb9J1c1Xd+dSmC96eEb182/KhQ+571+QhwVMC92QxLm5OdYu+YON4jaZGpcbP3CQFmMcxIIrDVA0TU3StOXz4prqNK1a595SmQ6H1zKtSprMVhDkszWHUuMz92h5M6dqx/M9wDQPnP6j91IVtVVeuKT3ljf81D25cdsPYKdC8bC6rh37kr5w1x6duOegziLsULStX707qOeJldDhaZhzFwaLZs30xe/MgWCtFrOB8D/+mTnZxMR+hKMymzlOlZejS83YCVfaqdS0D7cbowVTBXYbZqyF61Rx2zavQyTPJLGRYkR17dC9qq9cY5+w38IJPPh/v+Xl3v878Psv1SOrOlxM7j8ENkNht27LQihn8i79p/GiTgx6emJ2TB8rXdazjw5p4FX8842/gYXSJifYqIL8nlASl+mjEDHzsJUVqjPmY2lKTCfWpyiJyczali6d94d49DPUgd1FE8MKfuQ+BTmQaSzTttUgYNHh3qhJlASxFzRTzpg2zr+klAFEz5BL04yt5OWWdmRl+vzCfOvyOwqOZyhmKDAYxLUX5+TsHZGdW1UkMa2V6yU9816T6itB06lFWW1pFHpw2fSMyAMVgtS4jnEjG81m0Ozgnv1UrVH1DWT1+g+eoz0Y8WdqRVNnUJZOD4+CPFATaEuZvkyQwcCGqdYI8B73mvGAk6bZtrWjwWOntUzCDKNsPAZ0gdfXQnCedtV0GBllRulQMA0xZO3Dd6r57Pf06kpI79IGCZ46qd5WUb89n9DU7BoIFlETTSyR0U0P2tQBZt47gDIyfRnt4PuDew8jSJt5AgyWLlybWLLhNi0g1jTLRk6eUm2rhPKiPp3oeA0CGx/vZxYHEplZswMdCa4VyOoRkqWjj/zsvyenwFAjE4eebqxcz3RXb8umw+CZ7At1MHOt1ukH1P/iKyo9cFKNK9f9GW8bvs/kWvuStt7aqFKZuaqgiSrU2/SQXExtJjEdBN7e2lJqbD89pJbf0TZDvQSJaGNtxbeOqVI7BKkzMKpCiAGH6Texh0tjzaHvGmp2FXbNoN31K0ZrtF/BjukIun6VF7IYfm+f/+vndm5cUR3o6xycUoJ+SwA+NEoGPfr9l5Whu+BNjCsGjbYhYUuzs9owU5YD05R5FIADaYWyaQVm9vJ9iAZUBMpR9d1o35EPK2KuESNm8m66cctLi36C6wKjXYYnA4c+pBrFTGBiiooL/gTKxUHAcAW+BOSaRNcj0Rmhu7OrKmDRMgc0zfBSpXzBbuzkm71G/ekQvjOQIpXnd/xgjGT7tEjLI28zFVuYV8R0LNDiDjOn0J3TmiIjX4/3+w1W0xaM0ADzUIIH+jT5PLr/sIaHssygqXdBliKJantnW2lcq0gHOkhA7jn1EEMONBmJKTxAp5tau0sxFTBNY6gEPJrOCOUTGbhthldYhcWpGCmkGOXGUrFP747UHefrzrFjX4ouLqjJnNjdySvA5iGSkoeAlnEpTN4dG1QEfI4StB8fDuvbN4qKmK6GMTFa7WGlJAOKPiqvNsnJdNz8nwPwnUGkvXsnqewC/uzYY9oY4GB182MJ5gM9CJpVLkIooQfmNxnlmrpQCdMDCg9kSJrsa36KwDqmDck46NcvnHv5y/6kXp/7qbPulZXTtWr9hGhhB2+vMi+uqoPpLPqhNkhhJi5BFvfI1l0zMadK8pgHNDmMNZBQemJSiZLrx0ABTZtez759+/xeq/lZQokktULDy3SzQwRwB0u5GYQhs1p0QQJLqwR13bSr1WEPe2jQz0lhMq5FJyRA97o7AuXfrppfElzIDA6d/Wc/9jA/bXAGUmc1PfMlB5NaWMOChPVMl67V8fv9AdP/N81fRqCxSl2N6UkGHXSVO7Y/wHAJXFNf7y4M+6SLbcDAtOKNBZKJpAapmefg8d2ePy1Xl4N0yBlmzKpWhW4IHQ+g00wtLSDcpeTs4U62mTPzF3S9X3ed8NlbF14r/ssD+P8ikcyUG9FZr1w+DnSc6JkZuJmv4ibuUEZOjTnZ3il1CgScmSIy57UIapcmrxkCBs17z/ULmDgEzrhPhMDObeV87DfXzc9tzO8kDM0L0tJ0OZSDlkOmO9Lp+lMeSjcSXmBX8E7vFo2358YisT9/8/r51/6pvH8PiQBu4M5gvF0AAAAASUVORK5CYII=" +} + diff --git a/agent/templates/title_chunker.json b/agent/templates/title_chunker.json new file mode 100644 index 0000000..7b8c8a7 --- /dev/null +++ b/agent/templates/title_chunker.json @@ -0,0 +1,369 @@ +{ + "id": 25, + "title": { + "en": "Title Chunker", + "zh": "标题切片" + }, + "description": { + "en": "This template slices the parsed file based on its title structure. It is ideal for documents with well-defined headings, such as product manuals, legal contracts, research reports, and academic papers.", + "zh": "此模板将解析后的文件按标题结构进行切片,适用于具有清晰标题层级的文档类型,如产品手册、合同法规、研究报告和学术论文等。" + }, + "canvas_type": "Ingestion Pipeline", + "canvas_category": "dataflow_canvas", + "dsl": { + "components": { + "File": { + "obj": { + "component_name": "File", + "params": {} + }, + "downstream": [ + "Parser:HipSignsRhyme" + ], + "upstream": [] + }, + "Parser:HipSignsRhyme": { + "obj": { + "component_name": "Parser", + "params": { + "outputs": { + "html": { + "type": "string", + "value": "" + }, + "json": { + "type": "Array", + "value": [] + }, + "markdown": { + "type": "string", + "value": "" + }, + "text": { + "type": "string", + "value": "" + } + }, + "setups": { + "pdf": { + "output_format": "json", + "suffix": [ + "pdf" + ], + "parse_method": "DeepDOC" + }, + "text&markdown": { + "output_format": "text", + "suffix": [ + "md", + "markdown", + "mdx", + "txt" + ] + }, + "word": { + "output_format": "json", + "suffix": [ + "doc", + "docx" + ] + } + } + } + }, + "downstream": [ + "HierarchicalMerger:BusyPoetsSearch" + ], + "upstream": [ + "File" + ] + }, + "Tokenizer:NeatRadiosEnd": { + "obj": { + "component_name": "Tokenizer", + "params": { + "fields": "text", + "filename_embd_weight": 0.1, + "outputs": {}, + "search_method": [ + "embedding", + "full_text" + ] + } + }, + "downstream": [], + "upstream": [ + "HierarchicalMerger:BusyPoetsSearch" + ] + }, + "HierarchicalMerger:BusyPoetsSearch": { + "obj": { + "component_name": "HierarchicalMerger", + "params": { + "hierarchy": 3, + "levels": [ + [ + "^#[^#]" + ], + [ + "^##[^#]" + ], + [ + "^###[^#]" + ], + [ + "^####[^#]" + ] + ], + "outputs": { + "chunks": { + "type": "Array", + "value": [] + } + } + } + }, + "downstream": [ + "Tokenizer:NeatRadiosEnd" + ], + "upstream": [ + "Parser:HipSignsRhyme" + ] + } + }, + "globals": {}, + "graph": { + "nodes": [ + { + "data": { + "label": "File", + "name": "File" + }, + "id": "File", + "measured": { + "height": 48, + "width": 200 + }, + "position": { + "x": 50, + "y": 200 + }, + "sourcePosition": "left", + "targetPosition": "right", + "type": "beginNode" + }, + { + "data": { + "form": { + "outputs": { + "html": { + "type": "string", + "value": "" + }, + "json": { + "type": "Array", + "value": [] + }, + "markdown": { + "type": "string", + "value": "" + }, + "text": { + "type": "string", + "value": "" + } + }, + "setups": [ + { + "fileFormat": "pdf", + "output_format": "json", + "parse_method": "DeepDOC" + }, + { + "fileFormat": "text&markdown", + "output_format": "text" + }, + { + "fileFormat": "word", + "output_format": "json" + } + ] + }, + "label": "Parser", + "name": "Parser" + }, + "dragging": false, + "id": "Parser:HipSignsRhyme", + "measured": { + "height": 204, + "width": 200 + }, + "position": { + "x": 316.99524094206413, + "y": 195.39629819663406 + }, + "selected": false, + "sourcePosition": "right", + "targetPosition": "left", + "type": "parserNode" + }, + { + "data": { + "form": { + "fields": "text", + "filename_embd_weight": 0.1, + "outputs": {}, + "search_method": [ + "embedding", + "full_text" + ] + }, + "label": "Tokenizer", + "name": "Indexer" + }, + "dragging": false, + "id": "Tokenizer:NeatRadiosEnd", + "measured": { + "height": 120, + "width": 200 + }, + "position": { + "x": 855.3572909622682, + "y": 199.08562542263914 + }, + "selected": false, + "sourcePosition": "right", + "targetPosition": "left", + "type": "tokenizerNode" + }, + { + "data": { + "form": { + "hierarchy": "3", + "levels": [ + { + "expressions": [ + { + "expression": "^#[^#]" + } + ] + }, + { + "expressions": [ + { + "expression": "^##[^#]" + } + ] + }, + { + "expressions": [ + { + "expression": "^###[^#]" + } + ] + }, + { + "expressions": [ + { + "expression": "^####[^#]" + } + ] + } + ], + "outputs": { + "chunks": { + "type": "Array", + "value": [] + } + } + }, + "label": "HierarchicalMerger", + "name": "Title Chunker" + }, + "dragging": false, + "id": "HierarchicalMerger:BusyPoetsSearch", + "measured": { + "height": 80, + "width": 200 + }, + "position": { + "x": 587.0312356829183, + "y": 197.9169308584236 + }, + "selected": false, + "sourcePosition": "right", + "targetPosition": "left", + "type": "splitterNode" + }, + { + "data": { + "form": { + "text": "It is ideal for documents with well-defined headings, such as product manuals, legal contracts, research reports, and academic papers." + }, + "label": "Note", + "name": "Chunk by Title" + }, + "dragHandle": ".note-drag-handle", + "dragging": false, + "height": 159, + "id": "Note:KhakiBerriesPick", + "measured": { + "height": 159, + "width": 323 + }, + "position": { + "x": 623.9675370532708, + "y": 369.74281927307146 + }, + "resizing": false, + "selected": false, + "sourcePosition": "right", + "targetPosition": "left", + "type": "noteNode", + "width": 323 + } + ], + "edges": [ + { + "id": "xy-edge__Filestart-Parser:HipSignsRhymeend", + "source": "File", + "sourceHandle": "start", + "target": "Parser:HipSignsRhyme", + "targetHandle": "end" + }, + { + "id": "xy-edge__Parser:HipSignsRhymestart-HierarchicalMerger:BusyPoetsSearchend", + "source": "Parser:HipSignsRhyme", + "sourceHandle": "start", + "target": "HierarchicalMerger:BusyPoetsSearch", + "targetHandle": "end", + "data": { + "isHovered": false + } + }, + { + "data": { + "isHovered": false + }, + "id": "xy-edge__HierarchicalMerger:BusyPoetsSearchstart-Tokenizer:NeatRadiosEndend", + "markerEnd": "logo", + "source": "HierarchicalMerger:BusyPoetsSearch", + "sourceHandle": "start", + "style": { + "stroke": "rgba(91, 93, 106, 1)", + "strokeWidth": 1 + }, + "target": "Tokenizer:NeatRadiosEnd", + "targetHandle": "end", + "type": "buttonEdge", + "zIndex": 1001 + } + ] + }, + "history": [], + "messages": [], + "path": [], + "retrieval": [] + }, + "avatar": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAABMaSURBVHgBbVprjF3Vdf72Puc+5s7Lnhm/x/ZgCHWAFLdVKEqogJY2ikIJEUSBhiZFIaVRKZj+KFHa1G7apBS1KlGpkh99EEUgykMiKlFpFCUIyS1pSTFCQLADHmNjjz3jec99nXP27rfW3ufOtZOxru/cc8/Zez2+9a3HHoMLfv7skjuuy/Li4wbmZn6c8t7rdWMMnHOw1kKuyWf56f9dfuSe8rO8y/fyjFyXn/7fy+/llSRJeNbyWW5pZQ2+jOzvcbiW1A67wv3FwelHp/vl7e18cOr3NrTQOcCb9/cLVQpcblIUxfrDZl0B2UffRSlVQp5z5ylXGqEnbFSg/935gsIn4bmoPC8i4T+qw//xMJ8URRZ7CqjwvvVDY5J9pcVE0J4l5Tbjoxg+COc8wtdiUW6a2AuEkZco7HrrqTAXeKp8uai0tWKsILzjq5Kk+rvhtcRENbw/nAPXixK6Yst1DvChfaUAsplYqTSzRxGFFeFsUEldbPW65b1BIYuAOLm5vGZU6XK9fkiWgpfXre69rlgqcKMXfbhB1ynkszH7qkgP6DpfnLptiiIc+xlXRryLRZwzUSgfrBE3UGGCsYKgIkwvNoJFXQ+O8nLhnd7xbh3Fcm+aprqn/B7ihEpH+NJMVM6qBxROGhv8nNrr6R97oMR4vwLBwtS4KGXyKoKJiyo+uai8yz9ZmDsHTIoBihA/Vr9yEXrBa/3wRLAvCnlAl0j5lqvysk4JOlnDYj3WqCJcbm5Oucw+9LmzDFoIbHxF9lPLCkxUEB/CIeDZB6bgPy6G7rJHa1UepZUq0BvTmkc3p7I0XrfNbQuiV7DM640hoFI3PYsHAnJBaBNgozKlia7psG7kQgnGfTylCL3ADewQAtCaCq3iVFi57n0ZxxSKgeW4YLaWYHm+wMQu4PIbEuy5po6pq6sY2lxFbUiYhJtXJOC76sW800a3VaC1lGH+WAevfx+YfsngnVcdOh1gYrMoI1okEQnB43mWhz0FlqqZwInXvZ9KSyT6qK08ldBcOYOpYgOG1SCBj8kKFcyeLDC2HfitL6b41Ts3YnCsyuclaMUTEqxpQIawTyYWr/P5nJasoz7kUB8BxnYWeN+vZ7ylzRubOPGywXNfK/Dysx5jVKQ2ZHpxkiZJdEjwgigpwSxBzyD+Hd+fUKzC2vQeEOsrZBKDueMeOy73+My/jGPylwaVZfIOnxNLwcYArfHBNCakAM3CdSKfFxSgUHgqMn0WmAti2Sbh1ETW9njij7p44Z8Mtl7MjQuD3NEDgkFCz0aCSAQRjBfzJ7tu8/38LIuLNW0P38TumlGP7P+Pcey+aphYLoKwIqCuWGbeCoUSxqgqjeqLMIKwmI15hEIrNjTKhXWohFKjC5jPm6gNLmPpdII//UAbI42UMPS9xBYBQcNW4POuGGZd+BC8gS4Fe0nF4MxJh6tur+Dvz+3C9l+s00KF4tBqLhAz2kiGRtlIc7FxCGGWR5bxwUO6vHjIqjVBkoCv8zKvoaoGSNJBenUbGmMpHjlXR23MIWtCvR1ShlcP5HmHy9DQJfbLNG5iGqd3cOJoji+9MIpbH96C9mre5yWjTlcLK3UmgszoCeFNocGcG7XVo/QhBchDHhBgm1TjAvSUsXUuMUAv1PhkVVJeVHgcnZUBHHi1hsYWmqMLJQUJYMn6gXByiYP1pJQkAcc2yXH6bY+HT2zC7g+O0OqZJI31hCWL9FQxIT0pZYQrmhlc4H7hdNqL75n+rt4RZeQVadkrxGp8Mg0wdMryvDSK9nJQIpfMzF0phvyvDAlFZn+1KfydOJx6exUPvTNAOqxrginpUwJTaBa+QAB/zF8avEmkuBjLNigCTUBiUXmGQUxKVY+YbogPUUrjqIjJi+ukkn8SVdRWGXOrA/jq0RSnT9gASGI/JUTSpBo8UP6kaQ0nj5/CAz8YwshWWi4vgj1NSaX0DuPTVEJG1LRvArVJUJbcDR/hHmsmX8a5QFUh5VRgR3ZSr/iuKufFMLoW73Oh9lRMpKM0bB33fzfFwntSQlQDCRRFTHpRifbaKq65NcHe62twmVjcoxfWQia1HN3/ZoJ7ja6uZqqQjym+lFxVtSYKG12EsqI1amWtVK1Rz3ifa5wInuV3Lx6PPtPiORQQvD6GvR9hsvwVq/obk4ZCslf703ULi0v43GMJceeDlWghNZaEaIMlwWND8Ic3onP8MnS+OwE7UESRgyAmVnaa9MI+uk6onxxClZwHWq1KVoSSRcgLwWCI0AtwsiGe4nveGcPvfstg4WQwqhBOWtJnc6GN2/+S6bnDJCG1h9zilkk0tUCRtEz97Xk8Pd3G8OvL+MgHh9Tlap+kiAFMjs9imS0QkDRQkYDshAAVtDDJoc79/nqNXmzAjo7x+Qz1O5cgpKV0K0nLB8MZG+GictYwfkkVu65sY+1cTPbhC2BluYXr/jBRo5sB1i4NWm6IbFOZ0x5AIr+5N8dH3RFcu4MiTS2jLIWz13IUR8gUL5HTc6+ZVZKUm8/R+ocM2ROb0f7HDXAzRulVY0NMVyO77RyDHaoBfQSgHtLE63WdkrYlfopsENfur6C1EAwfPZDgihtaqDQqrCgrWLyXbDTWgFmqo3LTGmrXnIVrT6DxKSaZD+/hQh0kO7h4h0ueoIBP8769m+iwFtIhMsQVThMP2ty8VdUaCBsp1/wqksk8JLm61yDUGHL1YEUtF6R65B0nqMQa8wLvSd/Hmollp2ed5fIBXPnJJTx2l0ddiEcy8dKcwyfvJKF1+fACrfpTJpbLKrDDQzDkYVTP8Muz3HAUlUlCB51o5Rqtx70Z3KgSTiMENStU1yT9DZP5zwSaLLot3joA985GZPMrsFe1lNkMS2tTSbRg1PpLvHaOBnl8mEmaMBwkfCfG0P7PWaQ3riK9lFDM2IvVath2aYHOWgzi1lqGi65OlGmKFaJqwCvmpW5xJ+vIn98KP78RrecXsPild9H+9jA6j04qBTYfYj0yxCTVIqZrxPvRceSPjCP7cYLOE23kSRN+cQEFGc4WTFJHqfFZ0uM4PdIlVbeoXB7YSj43H+Q64/x+iJ9ZelvmjfTGK+AObaMsbHZMoeX2jl9mvHZDjkGNTcXoFqkMCix9jiuPtAiZDvKVReQnV5AdGoQ7shHFu6TQt7jpm6S9I07rFzfIjZjn3fwZZEvzsFwLW1kWMJvaBkOSybForwlHI1+ch1tqo5ghxCq55kLDfZyGboLuczTGFqv5xxF+tVt4f5fB/cpRXk+QvUg4VmU/i0t+zbC8EQ8wG6YSTLSe9Jm+0QrU2WqH9q5FBWbm4Igiu0XqfRfoT8KnWEb3++eo5CotTAaiIvnyIpVaQcJEiPdzHSEFF5KTN02kt88i2dcOI5p2F4WYX5NhB8Xb9KRlXHQpzy8Q95u4z5UrVIJ7Dg+gOEuSyGSa0ozVrQmdh7R1RlxT5YXrFlH83wjSDXGKQLkGPk+B9tAb38tC+8fOyjpudsZg4sU65m+it5hDLC2bvr/A0KcyureKwU/XsPoAE15DWIV7NBh0O0KKwTDTVJUlA+PDJbEIIft4ZvmCcPT/W0XruYwC0rugTG+cRn6Ke5/LVTHJF1IBSAeNTsvp5MGteIwfaGHmY4Mx6TAWWBvVrpqhcTfpgwVd6+n2bG0O1VzqGEKCyhta1Fa5wYzIRwt1aZBkBe3DVHa8huomQmZMatY1LZ+zmSY6nO7U5hhJZ1mHfrTNzy0tVQRCtcmthNgy8qECFRF4fB5VNlF2AzSTv/tKjsqADhbYrGRJgIYMpyQ1DBKXnSxMJ5h4Cr/C9zmt31Uxuth3GaCLzBcp7006yn6av4tQWkhPI6ySWyojGUzY5mzIzKJ0MsHnzzaVJs0EKZuUbHc4rY/ABqbzkzkM3gts+HNS9LWDKN4cw+Bt/J6VsWTs4y8TahVSbMYFOq0EK7MOg6NpUEA5N9dJgF8KDbSrrdGSpL+V4ZBsBIPLRtO8tWSrLgMwJ94W6fRXKSyhVv8AP9cC3+fzC8wRG7D85RFSLK9/aE55X/KFXWshf9dj+L4aZm9hgtxtUB1vYPFuqVAp8KAQAjD72Rzj3yIdM1n+9MccJmyXYk/qbGaEk69IH+C0bExHwzApW1lCZdRg9cuXYvUru6kA+b8IjY2tMXxcNXQGjULHJfnCImG3htW/IX4fMRpi9YtZGHZYPmcM8MU5ZCcW0ZleQTrFpMkmqXN6Bu2ZU8jfY098kUXjrgbzRY5sgcw1wudG2wyNDtpvNVH/zdB9vPF8V9vMnHuS5XIMb/T40TMDSOqxq9oiKZva89UkPeYn1tB9nYsRw55VanduHt1zM+i8tSLRiIGbqsinGYz0QLG8yoXPoZhd1e5q8H4q80YHnRmuwXqrOMWAn2a9VR2Af69AdqqF7hnCrE64tXOMfmEQm57agOoV5HnXQpEwe+9rYvxpzpFuJ4ngFJ56IMGGTVYJJfVW23b86Nk67vrXeQqQYPR+BvJVzMZ7ibGBFNkiLXySw6h6Fdm79FLKYGMKrjJJOTYXw5/eSGWXsPJkUz1XmRrEwC0VrcEqmxrYfsig9b22xk5lNwP3QyPaju48MsEYypU65bNrGe17k51VjH+NuUTgIx2ddGhNBn97Ecff3IFT73SxeZsJY8t7dn/Cy3zl7HsGX32xicnLCRG2lORBrD7J4Fqp090G9RsYuCkZaI3ZsUFGkdGbVJorYT5qBya08gzkRmxnQrdhhqOznKrT6tUwi0pTZmUWKgFsE633VRiUg0YTSEEqPu3g5hW6tdEE909uRqPO/JE77Y1TmdNI1pzYZvHsQzX88dMdjlGUXTH0GVIgs7OXEqAttQvdNsDsm8vMk4tnTvldq8XOLJlEMtxGhBrRxQTldH6jVW457BWl8kJrYR9LZc0vvtRAUjQfKGaVIArOhqrDBZ55YAK+SdarlCP+MJlRHvekukPfSTB7rBYsJZVhu07XyVDT9pr28J1Z3zj0mvqbNi3ClcWMDqpCH1kJgwgbuiw5vNB3W1HYGCvUnMR2knRsyELFe3xmVntmR0Ml9N7xlzfh3/+WcbDBhi6tPLu4e/dv+3I2L8YsWN9849gSOsuxK4oNj5YVYSxRdvKhrkec8dv13tqqpcNMSEoOJ7WHvvrH7OGl4xcd+BZxkFaeIwSDWXqiubwV93CUObmHeMnXzxaw3tAEDJL2sTTfwTNfGUGN5XARRx9hcp2tN8cWsdU0ejpTCu/j8ZPTd6tDrzDxliZmVV9MHrxR3ltctxuE13lmGsUxITdYozmgm+/CfuaFyT0IBaI9/yTIlick2pny95Ex4Nt/1cUP/nmCg9jQcSHCpsQ14kymHK+XjZQJg1VVSC0kQy8bsjNC4RuGYOWYMHpOJ0s2Ch8hadjWrs7vwhcmOti2KxyIlOdz60dS8fzgvPEib9zGbuvv7l7DoSfHGPmpWlmtKzjtwcWFXjXOs1x5nGTCJFvrKHlOsRbGgWH86OKcqZxkmHh0EkoQySXV4XG8yqHBvbta2Dkl4/Vg5DDm9+cdT6nk/SePckPOVL2bDz702Sa++fvDqG8YZeYLxz/heChbh1KRx+MkH87ElP6CYOKR0u4hiONJjonHqAFwMW5yJIMkDTeJB1nrf/OODmFDGOcXHGn1/Zzngf6jVR2J88Ht2y0OkVbv2EoWeG0CtZEG+wYXBDExEJM45NVhfpxOqLfitDlu6uMYMdBuPIlh4EoZUx0awOyJSXzj1gYh08UCx/gbNzmNof7J4XnHX/Ga+fzOjx2jAlMXnpP1/yTc9PSMxIfFJ+6p4Dfu7HByF4a2RbfQDkkSlMjv4sFGSEZGPSOFn01lEhEn1aTOrF3F6TfqePmpKv7rcYf50w6bthht3vvZr1+W/jPr+PmwuXvqpocZTPdpIpEpQZ+25an6uubkjhWDRTb+W5mdJy9OcNm1wJ4rqdxmh6Ex1u41E6bIjqc8rJvaqyzaWJedOcoDErakM0cSHPsf/n7SabW7YQJ6jlYeHQHnH7/2jz4vPKLl50fNH0zeeB3d+sPeGXHfue3Pw14/A8hkrLPKcrwjR0m836WKnCSOyKUMlya8SkapsdlJGUfVmnRiYVyox6fW9M4lfCwngggmkFyk6PKwvPxrgbC/v0i/vWfq5od5NnBfUSYqH2ecfVr/vL9x6FkG0q8k8ShUYJ7G46UQJjaumejELfxuje0dbocgNnF+2r9vaPZ1vBgR0Qf1rz84/fh+BRu3O0gMH05NEvNgSfP+PDf2w6o/qGTzXIdUIQY4CeIJYhZO2TW7h3O03Mv1XGdCPl5DzOQuFnQlZNEL9aKnzPofkrjDdVQPanzKfy8t/qT94Y17/43pm1Mse3UYnOK8aD8/Ftb/aOPCv4HQQ27JpElpufL0PemdQ2sxF1mqnFgrw8bEmJh+9oFO8kIO0Cz99QHbvvPg9BPrf+zR/7N/6uap3JmD9N2VXHKfFnsuTJh7R1F97/0e6o+Tdag5hY4QRJpUNCsnNhyb2pgvUAQFbYn7+Hyyvs40vfYdDoyffXD6iRf69/p/CbMWUUVYM2EAAAAASUVORK5CYII=" +} \ No newline at end of file diff --git a/agent/tools/exesql.py b/agent/tools/exesql.py index 2e1cc24..d937453 100644 --- a/agent/tools/exesql.py +++ b/agent/tools/exesql.py @@ -53,12 +53,13 @@ class ExeSQLParam(ToolParamBase): self.max_records = 1024 def check(self): - self.check_valid_value(self.db_type, "Choose DB type", ['mysql', 'postgres', 'mariadb', 'mssql', 'IBM DB2']) + self.check_valid_value(self.db_type, "Choose DB type", ['mysql', 'postgres', 'mariadb', 'mssql', 'IBM DB2', 'trino']) self.check_empty(self.database, "Database name") self.check_empty(self.username, "database username") self.check_empty(self.host, "IP Address") self.check_positive_integer(self.port, "IP Port") - self.check_empty(self.password, "Database password") + if self.db_type != "trino": + self.check_empty(self.password, "Database password") self.check_positive_integer(self.max_records, "Maximum number of records") if self.database == "rag_flow": if self.host == "ragflow-mysql": @@ -123,6 +124,45 @@ class ExeSQL(ToolBase, ABC): r'PWD=' + self._param.password ) db = pyodbc.connect(conn_str) + elif self._param.db_type == 'trino': + try: + import trino + from trino.auth import BasicAuthentication + except Exception: + raise Exception("Missing dependency 'trino'. Please install: pip install trino") + + def _parse_catalog_schema(db: str): + if not db: + return None, None + if "." in db: + c, s = db.split(".", 1) + elif "/" in db: + c, s = db.split("/", 1) + else: + c, s = db, "default" + return c, s + + catalog, schema = _parse_catalog_schema(self._param.database) + if not catalog: + raise Exception("For Trino, `database` must be 'catalog.schema' or at least 'catalog'.") + + http_scheme = "https" if os.environ.get("TRINO_USE_TLS", "0") == "1" else "http" + auth = None + if http_scheme == "https" and self._param.password: + auth = BasicAuthentication(self._param.username, self._param.password) + + try: + db = trino.dbapi.connect( + host=self._param.host, + port=int(self._param.port or 8080), + user=self._param.username or "ragflow", + catalog=catalog, + schema=schema or "default", + http_scheme=http_scheme, + auth=auth + ) + except Exception as e: + raise Exception("Database Connection Failed! \n" + str(e)) elif self._param.db_type == 'IBM DB2': import ibm_db conn_str = ( diff --git a/agent/tools/pubmed.py b/agent/tools/pubmed.py index 6dce92a..0920b3e 100644 --- a/agent/tools/pubmed.py +++ b/agent/tools/pubmed.py @@ -85,13 +85,7 @@ class PubMed(ToolBase, ABC): self._retrieve_chunks(pubmedcnt.findall("PubmedArticle"), get_title=lambda child: child.find("MedlineCitation").find("Article").find("ArticleTitle").text, get_url=lambda child: "https://pubmed.ncbi.nlm.nih.gov/" + child.find("MedlineCitation").find("PMID").text, - get_content=lambda child: child.find("MedlineCitation") \ - .find("Article") \ - .find("Abstract") \ - .find("AbstractText").text \ - if child.find("MedlineCitation")\ - .find("Article").find("Abstract") \ - else "No abstract available") + get_content=lambda child: self._format_pubmed_content(child),) return self.output("formalized_content") except Exception as e: last_e = e @@ -104,5 +98,50 @@ class PubMed(ToolBase, ABC): assert False, self.output() + def _format_pubmed_content(self, child): + """Extract structured reference info from PubMed XML""" + def safe_find(path): + node = child + for p in path.split("/"): + if node is None: + return None + node = node.find(p) + return node.text if node is not None and node.text else None + + title = safe_find("MedlineCitation/Article/ArticleTitle") or "No title" + abstract = safe_find("MedlineCitation/Article/Abstract/AbstractText") or "No abstract available" + journal = safe_find("MedlineCitation/Article/Journal/Title") or "Unknown Journal" + volume = safe_find("MedlineCitation/Article/Journal/JournalIssue/Volume") or "-" + issue = safe_find("MedlineCitation/Article/Journal/JournalIssue/Issue") or "-" + pages = safe_find("MedlineCitation/Article/Pagination/MedlinePgn") or "-" + + # Authors + authors = [] + for author in child.findall(".//AuthorList/Author"): + lastname = safe_find("LastName") or "" + forename = safe_find("ForeName") or "" + fullname = f"{forename} {lastname}".strip() + if fullname: + authors.append(fullname) + authors_str = ", ".join(authors) if authors else "Unknown Authors" + + # DOI + doi = None + for eid in child.findall(".//ArticleId"): + if eid.attrib.get("IdType") == "doi": + doi = eid.text + break + + return ( + f"Title: {title}\n" + f"Authors: {authors_str}\n" + f"Journal: {journal}\n" + f"Volume: {volume}\n" + f"Issue: {issue}\n" + f"Pages: {pages}\n" + f"DOI: {doi or '-'}\n" + f"Abstract: {abstract.strip()}" + ) + def thoughts(self) -> str: return "Looking for scholarly papers on `{}`,” prioritising reputable sources.".format(self.get_input().get("query", "-_-!")) diff --git a/agent/tools/retrieval.py b/agent/tools/retrieval.py index 24370f1..f0823be 100644 --- a/agent/tools/retrieval.py +++ b/agent/tools/retrieval.py @@ -18,12 +18,14 @@ import re from abc import ABC from agent.tools.base import ToolParamBase, ToolBase, ToolMeta from api.db import LLMType +from api.db.services.document_service import DocumentService +from api.db.services.dialog_service import meta_filter from api.db.services.knowledgebase_service import KnowledgebaseService from api.db.services.llm_service import LLMBundle from api import settings from api.utils.api_utils import timeout from rag.app.tag import label_question -from rag.prompts.generator import cross_languages, kb_prompt +from rag.prompts.generator import cross_languages, kb_prompt, gen_meta_filter class RetrievalParam(ToolParamBase): @@ -57,6 +59,8 @@ class RetrievalParam(ToolParamBase): self.empty_response = "" self.use_kg = False self.cross_languages = [] + self.toc_enhance = False + self.meta_data_filter={} def check(self): self.check_decimal_float(self.similarity_threshold, "[Retrieval] Similarity threshold") @@ -116,12 +120,27 @@ class Retrieval(ToolBase, ABC): vars = self.get_input_elements_from_text(kwargs["query"]) vars = {k:o["value"] for k,o in vars.items()} query = self.string_format(kwargs["query"], vars) + + doc_ids=[] + if self._param.meta_data_filter!={}: + metas = DocumentService.get_meta_by_kbs(kb_ids) + if self._param.meta_data_filter.get("method") == "auto": + chat_mdl = LLMBundle(self._canvas.get_tenant_id(), LLMType.CHAT) + filters = gen_meta_filter(chat_mdl, metas, query) + doc_ids.extend(meta_filter(metas, filters)) + if not doc_ids: + doc_ids = None + elif self._param.meta_data_filter.get("method") == "manual": + doc_ids.extend(meta_filter(metas, self._param.meta_data_filter["manual"])) + if not doc_ids: + doc_ids = None + if self._param.cross_languages: query = cross_languages(kbs[0].tenant_id, None, query, self._param.cross_languages) if kbs: query = re.sub(r"^user[::\s]*", "", query, flags=re.IGNORECASE) - kbinfos = settings.retrievaler.retrieval( + kbinfos = settings.retriever.retrieval( query, embd_mdl, [kb.tenant_id for kb in kbs], @@ -130,12 +149,18 @@ class Retrieval(ToolBase, ABC): self._param.top_n, self._param.similarity_threshold, 1 - self._param.keywords_similarity_weight, + doc_ids=doc_ids, aggs=False, rerank_mdl=rerank_mdl, rank_feature=label_question(query, kbs), ) + if self._param.toc_enhance: + chat_mdl = LLMBundle(self._canvas._tenant_id, LLMType.CHAT) + cks = settings.retriever.retrieval_by_toc(query, kbinfos["chunks"], [kb.tenant_id for kb in kbs], chat_mdl, self._param.top_n) + if cks: + kbinfos["chunks"] = cks if self._param.use_kg: - ck = settings.kg_retrievaler.retrieval(query, + ck = settings.kg_retriever.retrieval(query, [kb.tenant_id for kb in kbs], kb_ids, embd_mdl, @@ -146,7 +171,7 @@ class Retrieval(ToolBase, ABC): kbinfos = {"chunks": [], "doc_aggs": []} if self._param.use_kg and kbs: - ck = settings.kg_retrievaler.retrieval(query, [kb.tenant_id for kb in kbs], filtered_kb_ids, embd_mdl, LLMBundle(kbs[0].tenant_id, LLMType.CHAT)) + ck = settings.kg_retriever.retrieval(query, [kb.tenant_id for kb in kbs], filtered_kb_ids, embd_mdl, LLMBundle(kbs[0].tenant_id, LLMType.CHAT)) if ck["content_with_weight"]: ck["content"] = ck["content_with_weight"] del ck["content_with_weight"] diff --git a/api/apps/__init___fastapi.py b/api/apps/__init___fastapi.py index 65f7013..f60062e 100644 --- a/api/apps/__init___fastapi.py +++ b/api/apps/__init___fastapi.py @@ -52,6 +52,40 @@ def create_app() -> FastAPI: openapi_url="/apispec.json" ) + # 自定义 OpenAPI schema 以支持 Bearer Token 认证 + def custom_openapi(): + if app.openapi_schema: + return app.openapi_schema + from fastapi.openapi.utils import get_openapi + openapi_schema = get_openapi( + title=app.title, + version=app.version, + description=app.description, + routes=app.routes, + ) + # 添加安全方案定义(HTTPBearer 会自动注册为 "HTTPBearer") + # 如果 components 不存在,先创建它 + if "components" not in openapi_schema: + openapi_schema["components"] = {} + if "securitySchemes" not in openapi_schema["components"]: + openapi_schema["components"]["securitySchemes"] = {} + + # 移除 CustomHTTPBearer(如果存在),只保留 HTTPBearer + if "CustomHTTPBearer" in openapi_schema["components"]["securitySchemes"]: + del openapi_schema["components"]["securitySchemes"]["CustomHTTPBearer"] + + # 添加/更新 HTTPBearer 安全方案(FastAPI 默认名称) + openapi_schema["components"]["securitySchemes"]["HTTPBearer"] = { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT", + "description": "输入 token,可以不带 'Bearer ' 前缀,系统会自动添加" + } + app.openapi_schema = openapi_schema + return app.openapi_schema + + app.openapi = custom_openapi + # 添加CORS中间件 app.add_middleware( CORSMiddleware, @@ -124,22 +158,20 @@ def setup_routes(app: FastAPI): from api.apps.user_app_fastapi import router as user_router from api.apps.kb_app import router as kb_router from api.apps.document_app import router as document_router - from api.apps.file_app import router as file_router - from api.apps.file2document_app import router as file2document_router - from api.apps.mcp_server_app import router as mcp_router - from api.apps.tenant_app import router as tenant_router from api.apps.llm_app import router as llm_router from api.apps.chunk_app import router as chunk_router + from api.apps.mcp_server_app import router as mcp_router + from api.apps.canvas_app import router as canvas_router app.include_router(user_router, prefix=f"/{API_VERSION}/user", tags=["User"]) - app.include_router(kb_router, prefix=f"/{API_VERSION}/kb", tags=["KB"]) + app.include_router(kb_router, prefix=f"/{API_VERSION}/kb", tags=["KnowledgeBase"]) app.include_router(document_router, prefix=f"/{API_VERSION}/document", tags=["Document"]) - app.include_router(file_router, prefix=f"/{API_VERSION}/file", tags=["File"]) - app.include_router(file2document_router, prefix=f"/{API_VERSION}/file2document", tags=["File2Document"]) - app.include_router(mcp_router, prefix=f"/{API_VERSION}/mcp", tags=["MCP"]) - app.include_router(tenant_router, prefix=f"/{API_VERSION}/tenant", tags=["Tenant"]) app.include_router(llm_router, prefix=f"/{API_VERSION}/llm", tags=["LLM"]) app.include_router(chunk_router, prefix=f"/{API_VERSION}/chunk", tags=["Chunk"]) + app.include_router(mcp_router, prefix=f"/{API_VERSION}/mcp", tags=["MCP"]) + app.include_router(canvas_router, prefix=f"/{API_VERSION}/canvas", tags=["Canvas"]) + + def get_current_user_from_token(authorization: str): """从token获取当前用户""" diff --git a/api/apps/api_app.py b/api/apps/api_app.py index e71d123..4637009 100644 --- a/api/apps/api_app.py +++ b/api/apps/api_app.py @@ -490,7 +490,7 @@ def upload(): @manager.route('/document/upload_and_parse', methods=['POST']) # noqa: F821 @validate_request("conversation_id") -async def upload_parse(): +def upload_parse(): token = request.headers.get('Authorization').split()[1] objs = APIToken.query(token=token) if not objs: @@ -507,7 +507,7 @@ async def upload_parse(): return get_json_result( data=False, message='No file selected!', code=settings.RetCode.ARGUMENT_ERROR) - doc_ids = await doc_upload_and_parse(request.form.get("conversation_id"), file_objs, objs[0].tenant_id) + doc_ids = doc_upload_and_parse(request.form.get("conversation_id"), file_objs, objs[0].tenant_id) return get_json_result(data=doc_ids) @@ -536,7 +536,7 @@ def list_chunks(): ) kb_ids = KnowledgebaseService.get_kb_ids(tenant_id) - res = settings.retrievaler.chunk_list(doc_id, tenant_id, kb_ids) + res = settings.retriever.chunk_list(doc_id, tenant_id, kb_ids) res = [ { "content": res_item["content_with_weight"], @@ -884,7 +884,7 @@ def retrieval(): if req.get("keyword", False): chat_mdl = LLMBundle(kbs[0].tenant_id, LLMType.CHAT) question += keyword_extraction(chat_mdl, question) - ranks = settings.retrievaler.retrieval(question, embd_mdl, kbs[0].tenant_id, kb_ids, page, size, + ranks = settings.retriever.retrieval(question, embd_mdl, kbs[0].tenant_id, kb_ids, page, size, similarity_threshold, vector_similarity_weight, top, doc_ids, rerank_mdl=rerank_mdl, highlight= highlight, rank_feature=label_question(question, kbs)) diff --git a/api/apps/canvas_app.py b/api/apps/canvas_app.py index c3d4dd8..04be8d4 100644 --- a/api/apps/canvas_app.py +++ b/api/apps/canvas_app.py @@ -18,11 +18,12 @@ import logging import re import sys from functools import partial +from typing import Optional -import flask import trio -from flask import request, Response -from flask_login import login_required, current_user +from fastapi import APIRouter, Depends, Query, Header, UploadFile, File, Form +from fastapi.responses import StreamingResponse, Response +from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from agent.component import LLM from api import settings @@ -36,7 +37,24 @@ from api.db.services.user_service import TenantService from api.db.services.user_canvas_version import UserCanvasVersionService from api.settings import RetCode from api.utils import get_uuid -from api.utils.api_utils import get_json_result, server_error_response, validate_request, get_data_error_result +from api.utils.api_utils import get_json_result, server_error_response, get_data_error_result +from api.apps.models.auth_dependencies import get_current_user +from api.apps.models.canvas_models import ( + DeleteCanvasRequest, + SaveCanvasRequest, + CompletionRequest, + RerunRequest, + ResetCanvasRequest, + InputFormQuery, + DebugRequest, + TestDBConnectRequest, + ListCanvasQuery, + SettingRequest, + TraceQuery, + ListSessionsQuery, + DownloadQuery, + UploadQuery, +) from agent.canvas import Canvas from peewee import MySQLDatabase, PostgresqlDatabase from api.db.db_models import APIToken, Task @@ -47,18 +65,25 @@ from rag.flow.pipeline import Pipeline from rag.nlp import search from rag.utils.redis_conn import REDIS_CONN - -@manager.route('/templates', methods=['GET']) # noqa: F821 -@login_required -def templates(): - return get_json_result(data=[c.to_dict() for c in CanvasTemplateService.query(canvas_category=CanvasCategory.Agent)]) +# 创建路由器 +router = APIRouter() -@manager.route('/rm', methods=['POST']) # noqa: F821 -@validate_request("canvas_ids") -@login_required -def rm(): - for i in request.json["canvas_ids"]: +@router.get('/templates') +async def templates( + current_user = Depends(get_current_user) +): + """获取画布模板列表""" + return get_json_result(data=[c.to_dict() for c in CanvasTemplateService.get_all()]) + + +@router.post('/rm') +async def rm( + request: DeleteCanvasRequest, + current_user = Depends(get_current_user) +): + """删除画布""" + for i in request.canvas_ids: if not UserCanvasService.accessible(i, current_user.id): return get_json_result( data=False, message='Only owner of canvas authorized for this operation.', @@ -67,16 +92,18 @@ def rm(): return get_json_result(data=True) -@manager.route('/set', methods=['POST']) # noqa: F821 -@validate_request("dsl", "title") -@login_required -def save(): - req = request.json +@router.post('/set') +async def save( + request: SaveCanvasRequest, + current_user = Depends(get_current_user) +): + """保存画布""" + req = request.model_dump(exclude_unset=True) if not isinstance(req["dsl"], str): req["dsl"] = json.dumps(req["dsl"], ensure_ascii=False) req["dsl"] = json.loads(req["dsl"]) cate = req.get("canvas_category", CanvasCategory.Agent) - if "id" not in req: + if "id" not in req or not req.get("id"): req["user_id"] = current_user.id if UserCanvasService.query(user_id=current_user.id, title=req["title"].strip(), canvas_category=cate): return get_data_error_result(message=f"{req['title'].strip()} already exists.") @@ -95,21 +122,28 @@ def save(): return get_json_result(data=req) -@manager.route('/get/', methods=['GET']) # noqa: F821 -@login_required -def get(canvas_id): +@router.get('/get/{canvas_id}') +async def get( + canvas_id: str, + current_user = Depends(get_current_user) +): + """获取画布详情""" if not UserCanvasService.accessible(canvas_id, current_user.id): return get_data_error_result(message="canvas not found.") e, c = UserCanvasService.get_by_canvas_id(canvas_id) return get_json_result(data=c) -@manager.route('/getsse/', methods=['GET']) # type: ignore # noqa: F821 -def getsse(canvas_id): - token = request.headers.get('Authorization').split() - if len(token) != 2: +@router.get('/getsse/{canvas_id}') +async def getsse( + canvas_id: str, + authorization: str = Header(..., description="Authorization header") +): + """获取画布详情(SSE,通过API token认证)""" + token_parts = authorization.split() + if len(token_parts) != 2: return get_data_error_result(message='Authorization is not valid!"') - token = token[1] + token = token_parts[1] objs = APIToken.query(beta=token) if not objs: return get_data_error_result(message='Authentication error: API key is invalid!"') @@ -126,21 +160,23 @@ def getsse(canvas_id): return get_json_result(data=c.to_dict()) -@manager.route('/completion', methods=['POST']) # noqa: F821 -@validate_request("id") -@login_required -def run(): - req = request.json - query = req.get("query", "") - files = req.get("files", []) - inputs = req.get("inputs", {}) - user_id = req.get("user_id", current_user.id) - if not UserCanvasService.accessible(req["id"], current_user.id): +@router.post('/completion') +async def run( + request: CompletionRequest, + current_user = Depends(get_current_user) +): + """运行画布(完成/执行)""" + query = request.query or "" + files = request.files or [] + inputs = request.inputs or {} + user_id = request.user_id or current_user.id + + if not UserCanvasService.accessible(request.id, current_user.id): return get_json_result( data=False, message='Only owner of canvas authorized for this operation.', code=RetCode.OPERATING_ERROR) - e, cvs = UserCanvasService.get_by_id(req["id"]) + e, cvs = UserCanvasService.get_by_id(request.id) if not e: return get_data_error_result(message="canvas not found.") @@ -149,43 +185,48 @@ def run(): if cvs.canvas_category == CanvasCategory.DataFlow: task_id = get_uuid() - Pipeline(cvs.dsl, tenant_id=current_user.id, doc_id=CANVAS_DEBUG_DOC_ID, task_id=task_id, flow_id=req["id"]) - ok, error_message = queue_dataflow(tenant_id=user_id, flow_id=req["id"], task_id=task_id, file=files[0], priority=0) + Pipeline(cvs.dsl, tenant_id=current_user.id, doc_id=CANVAS_DEBUG_DOC_ID, task_id=task_id, flow_id=request.id) + ok, error_message = queue_dataflow(tenant_id=user_id, flow_id=request.id, task_id=task_id, file=files[0] if files else None, priority=0) if not ok: return get_data_error_result(message=error_message) return get_json_result(data={"message_id": task_id}) try: - canvas = Canvas(cvs.dsl, current_user.id, req["id"]) + canvas = Canvas(cvs.dsl, current_user.id, request.id) except Exception as e: return server_error_response(e) - def sse(): + async def sse(): nonlocal canvas, user_id try: for ans in canvas.run(query=query, files=files, user_id=user_id, inputs=inputs): yield "data:" + json.dumps(ans, ensure_ascii=False) + "\n\n" cvs.dsl = json.loads(str(canvas)) - UserCanvasService.update_by_id(req["id"], cvs.to_dict()) + UserCanvasService.update_by_id(request.id, cvs.to_dict()) except Exception as e: logging.exception(e) yield "data:" + json.dumps({"code": 500, "message": str(e), "data": False}, ensure_ascii=False) + "\n\n" - resp = Response(sse(), mimetype="text/event-stream") - resp.headers.add_header("Cache-control", "no-cache") - resp.headers.add_header("Connection", "keep-alive") - resp.headers.add_header("X-Accel-Buffering", "no") - resp.headers.add_header("Content-Type", "text/event-stream; charset=utf-8") - return resp + return StreamingResponse( + sse(), + media_type="text/event-stream", + headers={ + "Cache-control": "no-cache", + "Connection": "keep-alive", + "X-Accel-Buffering": "no", + "Content-Type": "text/event-stream; charset=utf-8" + } + ) -@manager.route('/rerun', methods=['POST']) # noqa: F821 -@validate_request("id", "dsl", "component_id") -@login_required -def rerun(): - req = request.json - doc = PipelineOperationLogService.get_documents_info(req["id"]) +@router.post('/rerun') +async def rerun( + request: RerunRequest, + current_user = Depends(get_current_user) +): + """重新运行流水线""" + doc = PipelineOperationLogService.get_documents_info(request.id) if not doc: return get_data_error_result(message="Document not found.") doc = doc[0] @@ -198,19 +239,22 @@ def rerun(): doc["chunk_num"] = 0 doc["token_num"] = 0 DocumentService.clear_chunk_num_when_rerun(doc["id"]) - DocumentService.update_by_id(id, doc) - TaskService.filter_delete([Task.doc_id == id]) + DocumentService.update_by_id(request.id, doc) + TaskService.filter_delete([Task.doc_id == request.id]) - dsl = req["dsl"] - dsl["path"] = [req["component_id"]] - PipelineOperationLogService.update_by_id(req["id"], {"dsl": dsl}) - queue_dataflow(tenant_id=current_user.id, flow_id=req["id"], task_id=get_uuid(), doc_id=doc["id"], priority=0, rerun=True) + dsl = request.dsl + dsl["path"] = [request.component_id] + PipelineOperationLogService.update_by_id(request.id, {"dsl": dsl}) + queue_dataflow(tenant_id=current_user.id, flow_id=request.id, task_id=get_uuid(), doc_id=doc["id"], priority=0, rerun=True) return get_json_result(data=True) -@manager.route('/cancel/', methods=['PUT']) # noqa: F821 -@login_required -def cancel(task_id): +@router.put('/cancel/{task_id}') +async def cancel( + task_id: str, + current_user = Depends(get_current_user) +): + """取消任务""" try: REDIS_CONN.set(f"{task_id}-cancel", "x") except Exception as e: @@ -218,36 +262,43 @@ def cancel(task_id): return get_json_result(data=True) -@manager.route('/reset', methods=['POST']) # noqa: F821 -@validate_request("id") -@login_required -def reset(): - req = request.json - if not UserCanvasService.accessible(req["id"], current_user.id): +@router.post('/reset') +async def reset( + request: ResetCanvasRequest, + current_user = Depends(get_current_user) +): + """重置画布""" + if not UserCanvasService.accessible(request.id, current_user.id): return get_json_result( data=False, message='Only owner of canvas authorized for this operation.', code=RetCode.OPERATING_ERROR) try: - e, user_canvas = UserCanvasService.get_by_id(req["id"]) + e, user_canvas = UserCanvasService.get_by_id(request.id) if not e: return get_data_error_result(message="canvas not found.") canvas = Canvas(json.dumps(user_canvas.dsl), current_user.id) canvas.reset() - req["dsl"] = json.loads(str(canvas)) - UserCanvasService.update_by_id(req["id"], {"dsl": req["dsl"]}) - return get_json_result(data=req["dsl"]) + dsl = json.loads(str(canvas)) + UserCanvasService.update_by_id(request.id, {"dsl": dsl}) + return get_json_result(data=dsl) except Exception as e: return server_error_response(e) -@manager.route("/upload/", methods=["POST"]) # noqa: F821 -def upload(canvas_id): +@router.post("/upload/{canvas_id}") +async def upload( + canvas_id: str, + url: Optional[str] = Query(None, description="URL(可选,用于从URL下载)"), + file: Optional[UploadFile] = File(None, description="上传的文件") +): + """上传文件到画布""" e, cvs = UserCanvasService.get_by_canvas_id(canvas_id) if not e: return get_data_error_result(message="canvas not found.") user_id = cvs["user_id"] + def structured(filename, filetype, blob, content_type): nonlocal user_id if filetype == FileType.PDF.value: @@ -267,7 +318,7 @@ def upload(canvas_id): "preview_url": None } - if request.args.get("url"): + if url: from crawl4ai import ( AsyncWebCrawler, BrowserConfig, @@ -277,7 +328,6 @@ def upload(canvas_id): CrawlResult ) try: - url = request.args.get("url") filename = re.sub(r"\?.*", "", url.split("/")[-1]) async def adownload(): browser_config = BrowserConfig( @@ -301,61 +351,67 @@ def upload(canvas_id): if page.pdf: if filename.split(".")[-1].lower() != "pdf": filename += ".pdf" - return get_json_result(data=structured(filename, "pdf", page.pdf, page.response_headers["content-type"])) + return get_json_result(data=structured(filename, "pdf", page.pdf, page.response_headers.get("content-type", "application/pdf"))) - return get_json_result(data=structured(filename, "html", str(page.markdown).encode("utf-8"), page.response_headers["content-type"], user_id)) + return get_json_result(data=structured(filename, "html", str(page.markdown).encode("utf-8"), page.response_headers.get("content-type", "text/html"), user_id)) except Exception as e: - return server_error_response(e) + return server_error_response(e) - file = request.files['file'] + if not file: + return get_data_error_result(message="No file provided.") + try: DocumentService.check_doc_health(user_id, file.filename) - return get_json_result(data=structured(file.filename, filename_type(file.filename), file.read(), file.content_type)) + blob = await file.read() + return get_json_result(data=structured(file.filename, filename_type(file.filename), blob, file.content_type or "application/octet-stream")) except Exception as e: - return server_error_response(e) + return server_error_response(e) -@manager.route('/input_form', methods=['GET']) # noqa: F821 -@login_required -def input_form(): - cvs_id = request.args.get("id") - cpn_id = request.args.get("component_id") +@router.get('/input_form') +async def input_form( + id: str = Query(..., description="画布ID"), + component_id: str = Query(..., description="组件ID"), + current_user = Depends(get_current_user) +): + """获取组件输入表单""" try: - e, user_canvas = UserCanvasService.get_by_id(cvs_id) + e, user_canvas = UserCanvasService.get_by_id(id) if not e: return get_data_error_result(message="canvas not found.") - if not UserCanvasService.query(user_id=current_user.id, id=cvs_id): + if not UserCanvasService.query(user_id=current_user.id, id=id): return get_json_result( data=False, message='Only owner of canvas authorized for this operation.', code=RetCode.OPERATING_ERROR) canvas = Canvas(json.dumps(user_canvas.dsl), current_user.id) - return get_json_result(data=canvas.get_component_input_form(cpn_id)) + return get_json_result(data=canvas.get_component_input_form(component_id)) except Exception as e: return server_error_response(e) -@manager.route('/debug', methods=['POST']) # noqa: F821 -@validate_request("id", "component_id", "params") -@login_required -def debug(): - req = request.json - if not UserCanvasService.accessible(req["id"], current_user.id): +@router.post('/debug') +async def debug( + request: DebugRequest, + current_user = Depends(get_current_user) +): + """调试组件""" + if not UserCanvasService.accessible(request.id, current_user.id): return get_json_result( data=False, message='Only owner of canvas authorized for this operation.', code=RetCode.OPERATING_ERROR) try: - e, user_canvas = UserCanvasService.get_by_id(req["id"]) + e, user_canvas = UserCanvasService.get_by_id(request.id) canvas = Canvas(json.dumps(user_canvas.dsl), current_user.id) canvas.reset() canvas.message_id = get_uuid() - component = canvas.get_component(req["component_id"])["obj"] + component = canvas.get_component(request.component_id)["obj"] component.reset() if isinstance(component, LLM): - component.set_debug_inputs(req["params"]) - component.invoke(**{k: o["value"] for k,o in req["params"].items()}) + component.set_debug_inputs(request.params) + component.invoke(**{k: o["value"] for k,o in request.params.items()}) outputs = component.output() for k in outputs.keys(): if isinstance(outputs[k], partial): @@ -368,11 +424,13 @@ def debug(): return server_error_response(e) -@manager.route('/test_db_connect', methods=['POST']) # noqa: F821 -@validate_request("db_type", "database", "username", "host", "port", "password") -@login_required -def test_db_connect(): - req = request.json +@router.post('/test_db_connect') +async def test_db_connect( + request: TestDBConnectRequest, + current_user = Depends(get_current_user) +): + """测试数据库连接""" + req = request.model_dump() try: if req["db_type"] in ["mysql", "mariadb"]: db = MySQLDatabase(req["database"], user=req["username"], host=req["host"], port=req["port"], @@ -409,6 +467,49 @@ def test_db_connect(): ibm_db.fetch_assoc(stmt) ibm_db.close(conn) return get_json_result(data="Database Connection Successful!") + elif req["db_type"] == 'trino': + def _parse_catalog_schema(db: str): + if not db: + return None, None + if "." in db: + c, s = db.split(".", 1) + elif "/" in db: + c, s = db.split("/", 1) + else: + c, s = db, "default" + return c, s + try: + import trino + import os + from trino.auth import BasicAuthentication + except Exception: + return server_error_response("Missing dependency 'trino'. Please install: pip install trino") + + catalog, schema = _parse_catalog_schema(req["database"]) + if not catalog: + return server_error_response("For Trino, 'database' must be 'catalog.schema' or at least 'catalog'.") + + http_scheme = "https" if os.environ.get("TRINO_USE_TLS", "0") == "1" else "http" + + auth = None + if http_scheme == "https" and req.get("password"): + auth = BasicAuthentication(req.get("username") or "ragflow", req["password"]) + + conn = trino.dbapi.connect( + host=req["host"], + port=int(req["port"] or 8080), + user=req["username"] or "ragflow", + catalog=catalog, + schema=schema or "default", + http_scheme=http_scheme, + auth=auth + ) + cur = conn.cursor() + cur.execute("SELECT 1") + cur.fetchall() + cur.close() + conn.close() + return get_json_result(data="Database Connection Successful!") else: return server_error_response("Unsupported database type.") if req["db_type"] != 'mssql': @@ -421,42 +522,48 @@ def test_db_connect(): #api get list version dsl of canvas -@manager.route('/getlistversion/', methods=['GET']) # noqa: F821 -@login_required -def getlistversion(canvas_id): +@router.get('/getlistversion/{canvas_id}') +async def getlistversion( + canvas_id: str, + current_user = Depends(get_current_user) +): + """获取画布版本列表""" try: - list =sorted([c.to_dict() for c in UserCanvasVersionService.list_by_canvas_id(canvas_id)], key=lambda x: x["update_time"]*-1) + list = sorted([c.to_dict() for c in UserCanvasVersionService.list_by_canvas_id(canvas_id)], key=lambda x: x["update_time"]*-1) return get_json_result(data=list) except Exception as e: return get_data_error_result(message=f"Error getting history files: {e}") -#api get version dsl of canvas -@manager.route('/getversion/', methods=['GET']) # noqa: F821 -@login_required -def getversion( version_id): +@router.get('/getversion/{version_id}') +async def getversion( + version_id: str, + current_user = Depends(get_current_user) +): + """获取画布版本详情""" try: - e, version = UserCanvasVersionService.get_by_id(version_id) if version: return get_json_result(data=version.to_dict()) + return get_data_error_result(message="Version not found.") except Exception as e: - return get_json_result(data=f"Error getting history file: {e}") + return get_data_error_result(message=f"Error getting history file: {e}") -@manager.route('/list', methods=['GET']) # noqa: F821 -@login_required -def list_canvas(): - keywords = request.args.get("keywords", "") - page_number = int(request.args.get("page", 0)) - items_per_page = int(request.args.get("page_size", 0)) - orderby = request.args.get("orderby", "create_time") - canvas_category = request.args.get("canvas_category") - if request.args.get("desc", "true").lower() == "false": - desc = False - else: - desc = True - owner_ids = [id for id in request.args.get("owner_ids", "").strip().split(",") if id] +@router.get('/list') +async def list_canvas( + query: ListCanvasQuery = Depends(), + current_user = Depends(get_current_user) +): + """列出画布""" + keywords = query.keywords or "" + page_number = int(query.page or 0) + items_per_page = int(query.page_size or 0) + orderby = query.orderby or "create_time" + canvas_category = query.canvas_category + desc = query.desc.lower() == "false" if query.desc else True + owner_ids = [id for id in (query.owner_ids or "").strip().split(",") if id] + if not owner_ids: tenants = TenantService.get_joined_tenants_by_user_id(current_user.id) tenants = [m["tenant_id"] for m in tenants] @@ -472,68 +579,73 @@ def list_canvas(): return get_json_result(data={"canvas": canvas, "total": total}) -@manager.route('/setting', methods=['POST']) # noqa: F821 -@validate_request("id", "title", "permission") -@login_required -def setting(): - req = request.json - req["user_id"] = current_user.id - - if not UserCanvasService.accessible(req["id"], current_user.id): +@router.post('/setting') +async def setting( + request: SettingRequest, + current_user = Depends(get_current_user) +): + """设置画布""" + if not UserCanvasService.accessible(request.id, current_user.id): return get_json_result( data=False, message='Only owner of canvas authorized for this operation.', code=RetCode.OPERATING_ERROR) - e,flow = UserCanvasService.get_by_id(req["id"]) + e, flow = UserCanvasService.get_by_id(request.id) if not e: return get_data_error_result(message="canvas not found.") flow = flow.to_dict() - flow["title"] = req["title"] + flow["title"] = request.title for key in ["description", "permission", "avatar"]: - if value := req.get(key): + value = getattr(request, key, None) + if value: flow[key] = value - num= UserCanvasService.update_by_id(req["id"], flow) + num = UserCanvasService.update_by_id(request.id, flow) return get_json_result(data=num) -@manager.route('/trace', methods=['GET']) # noqa: F821 -def trace(): - cvs_id = request.args.get("canvas_id") - msg_id = request.args.get("message_id") +@router.get('/trace') +async def trace( + canvas_id: str = Query(..., description="画布ID"), + message_id: str = Query(..., description="消息ID") +): + """追踪日志""" try: - bin = REDIS_CONN.get(f"{cvs_id}-{msg_id}-logs") + bin = REDIS_CONN.get(f"{canvas_id}-{message_id}-logs") if not bin: return get_json_result(data={}) return get_json_result(data=json.loads(bin.encode("utf-8"))) except Exception as e: logging.exception(e) + return server_error_response(e) -@manager.route('//sessions', methods=['GET']) # noqa: F821 -@login_required -def sessions(canvas_id): +@router.get('/{canvas_id}/sessions') +async def sessions( + canvas_id: str, + query: ListSessionsQuery = Depends(), + current_user = Depends(get_current_user) +): + """列出画布会话""" tenant_id = current_user.id if not UserCanvasService.accessible(canvas_id, tenant_id): return get_json_result( data=False, message='Only owner of canvas authorized for this operation.', code=RetCode.OPERATING_ERROR) - user_id = request.args.get("user_id") - page_number = int(request.args.get("page", 1)) - items_per_page = int(request.args.get("page_size", 30)) - keywords = request.args.get("keywords") - from_date = request.args.get("from_date") - to_date = request.args.get("to_date") - orderby = request.args.get("orderby", "update_time") - if request.args.get("desc") == "False" or request.args.get("desc") == "false": - desc = False - else: - desc = True + user_id = query.user_id + page_number = int(query.page or 1) + items_per_page = int(query.page_size or 30) + keywords = query.keywords + from_date = query.from_date + to_date = query.to_date + orderby = query.orderby or "update_time" + desc = query.desc.lower() in ["false", "False"] if query.desc else True # dsl defaults to True in all cases except for False and false - include_dsl = request.args.get("dsl") != "False" and request.args.get("dsl") != "false" + include_dsl = query.dsl.lower() not in ["false", "False"] if query.dsl else True + total, sess = API4ConversationService.get_list(canvas_id, tenant_id, page_number, items_per_page, orderby, desc, None, user_id, include_dsl, keywords, from_date, to_date) try: @@ -542,9 +654,11 @@ def sessions(canvas_id): return server_error_response(e) -@manager.route('/prompts', methods=['GET']) # noqa: F821 -@login_required -def prompts(): +@router.get('/prompts') +async def prompts( + current_user = Depends(get_current_user) +): + """获取提示词模板""" from rag.prompts.generator import ANALYZE_TASK_SYSTEM, ANALYZE_TASK_USER, NEXT_STEP, REFLECT, CITATION_PROMPT_TEMPLATE return get_json_result(data={ "task_analysis": ANALYZE_TASK_SYSTEM +"\n\n"+ ANALYZE_TASK_USER, @@ -556,9 +670,11 @@ def prompts(): }) -@manager.route('/download', methods=['GET']) # noqa: F821 -def download(): - id = request.args.get("id") - created_by = request.args.get("created_by") +@router.get('/download') +async def download( + id: str = Query(..., description="文件ID"), + created_by: str = Query(..., description="创建者ID") +): + """下载文件""" blob = FileService.get_blob(created_by, id) - return flask.make_response(blob) \ No newline at end of file + return Response(content=blob) \ No newline at end of file diff --git a/api/apps/chunk_app.py b/api/apps/chunk_app.py index 21d24dc..41eac02 100644 --- a/api/apps/chunk_app.py +++ b/api/apps/chunk_app.py @@ -16,10 +16,19 @@ import datetime import json import re -from typing import Optional, List import xxhash -from fastapi import APIRouter, Depends, Query, HTTPException +from fastapi import APIRouter, Depends, Query + +from api.apps.models.auth_dependencies import get_current_user +from api.apps.models.chunk_models import ( + ListChunksRequest, + SetChunkRequest, + SwitchChunksRequest, + DeleteChunksRequest, + CreateChunkRequest, + RetrievalTestRequest, +) from api import settings from api.db import LLMType, ParserType @@ -29,17 +38,7 @@ from api.db.services.knowledgebase_service import KnowledgebaseService from api.db.services.llm_service import LLMBundle from api.db.services.search_service import SearchService from api.db.services.user_service import UserTenantService -from api.models.chunk_models import ( - ListChunkRequest, - GetChunkRequest, - SetChunkRequest, - SwitchChunkRequest, - RemoveChunkRequest, - CreateChunkRequest, - RetrievalTestRequest, - KnowledgeGraphRequest -) -from api.utils.api_utils import get_data_error_result, get_json_result, server_error_response, get_current_user +from api.utils.api_utils import get_data_error_result, get_json_result, server_error_response from rag.app.qa import beAdoc, rmPrefix from rag.app.tag import label_question from rag.nlp import rag_tokenizer, search @@ -47,19 +46,20 @@ from rag.prompts.generator import gen_meta_filter, cross_languages, keyword_extr from rag.settings import PAGERANK_FLD from rag.utils import rmSpace -# 创建 FastAPI 路由器 +# 创建路由器 router = APIRouter() @router.post('/list') async def list_chunk( - request: ListChunkRequest, + request: ListChunksRequest, current_user = Depends(get_current_user) ): + """列出文档块""" doc_id = request.doc_id - page = request.page - size = request.size - question = request.keywords + page = request.page or 1 + size = request.size or 30 + question = request.keywords or "" try: tenant_id = DocumentService.get_tenant_id(doc_id) if not tenant_id: @@ -73,7 +73,7 @@ async def list_chunk( } if request.available_int is not None: query["available_int"] = int(request.available_int) - sres = settings.retrievaler.search(query, search.index_name(tenant_id), kb_ids, highlight=True) + sres = settings.retriever.search(query, search.index_name(tenant_id), kb_ids, highlight=["content_ltks"]) res = {"total": sres.total, "chunks": [], "doc": doc.to_dict()} for id in sres.ids: d = { @@ -105,6 +105,7 @@ async def get( chunk_id: str = Query(..., description="块ID"), current_user = Depends(get_current_user) ): + """获取文档块""" try: chunk = None tenants = UserTenantService.query(user_id=current_user.id) @@ -138,6 +139,7 @@ async def set( request: SetChunkRequest, current_user = Depends(get_current_user) ): + """设置文档块""" d = { "id": request.chunk_id, "content_with_weight": request.content_with_weight} @@ -192,9 +194,10 @@ async def set( @router.post('/switch') async def switch( - request: SwitchChunkRequest, + request: SwitchChunksRequest, current_user = Depends(get_current_user) ): + """切换文档块状态""" try: e, doc = DocumentService.get_by_id(request.doc_id) if not e: @@ -212,9 +215,10 @@ async def switch( @router.post('/rm') async def rm( - request: RemoveChunkRequest, + request: DeleteChunksRequest, current_user = Depends(get_current_user) ): + """删除文档块""" from rag.utils.storage_factory import STORAGE_IMPL try: e, doc = DocumentService.get_by_id(request.doc_id) @@ -240,15 +244,16 @@ async def create( request: CreateChunkRequest, current_user = Depends(get_current_user) ): + """创建文档块""" chunck_id = xxhash.xxh64((request.content_with_weight + request.doc_id).encode("utf-8")).hexdigest() d = {"id": chunck_id, "content_ltks": rag_tokenizer.tokenize(request.content_with_weight), "content_with_weight": request.content_with_weight} d["content_sm_ltks"] = rag_tokenizer.fine_grained_tokenize(d["content_ltks"]) - d["important_kwd"] = request.important_kwd + d["important_kwd"] = request.important_kwd or [] if not isinstance(d["important_kwd"], list): return get_data_error_result(message="`important_kwd` is required to be a list") d["important_tks"] = rag_tokenizer.tokenize(" ".join(d["important_kwd"])) - d["question_kwd"] = request.question_kwd + d["question_kwd"] = request.question_kwd or [] if not isinstance(d["question_kwd"], list): return get_data_error_result(message="`question_kwd` is required to be a list") d["question_tks"] = rag_tokenizer.tokenize("\n".join(d["question_kwd"])) @@ -296,8 +301,9 @@ async def retrieval_test( request: RetrievalTestRequest, current_user = Depends(get_current_user) ): - page = request.page - size = request.size + """检索测试""" + page = request.page or 1 + size = request.size or 30 question = request.question kb_ids = request.kb_id if isinstance(kb_ids, str): @@ -306,10 +312,10 @@ async def retrieval_test( return get_json_result(data=False, message='Please specify dataset firstly.', code=settings.RetCode.DATA_ERROR) - doc_ids = request.doc_ids - use_kg = request.use_kg - top = request.top_k - langs = request.cross_languages + doc_ids = request.doc_ids or [] + use_kg = request.use_kg or False + top = request.top_k or 1024 + langs = request.cross_languages or [] tenant_ids = [] if request.search_id: @@ -358,15 +364,16 @@ async def retrieval_test( question += keyword_extraction(chat_mdl, question) labels = label_question(question, [kb]) - ranks = settings.retrievaler.retrieval(question, embd_mdl, tenant_ids, kb_ids, page, size, - float(request.similarity_threshold), - float(request.vector_similarity_weight), + ranks = settings.retriever.retrieval(question, embd_mdl, tenant_ids, kb_ids, page, size, + float(request.similarity_threshold or 0.0), + float(request.vector_similarity_weight or 0.3), top, - doc_ids, rerank_mdl=rerank_mdl, highlight=request.highlight, + doc_ids, rerank_mdl=rerank_mdl, + highlight=request.highlight or False, rank_feature=labels ) if use_kg: - ck = settings.kg_retrievaler.retrieval(question, + ck = settings.kg_retriever.retrieval(question, tenant_ids, kb_ids, embd_mdl, @@ -391,13 +398,14 @@ async def knowledge_graph( doc_id: str = Query(..., description="文档ID"), current_user = Depends(get_current_user) ): + """获取知识图谱""" tenant_id = DocumentService.get_tenant_id(doc_id) kb_ids = KnowledgebaseService.get_kb_ids(tenant_id) req = { "doc_ids": [doc_id], "knowledge_graph_kwd": ["graph", "mind_map"] } - sres = settings.retrievaler.search(req, search.index_name(tenant_id), kb_ids) + sres = settings.retriever.search(req, search.index_name(tenant_id), kb_ids) obj = {"graph": {}, "mind_map": {}} for id in sres.ids[:2]: ty = sres.field[id]["knowledge_graph_kwd"] diff --git a/api/apps/document_app.py b/api/apps/document_app.py index 53a7dd0..b54ce76 100644 --- a/api/apps/document_app.py +++ b/api/apps/document_app.py @@ -17,15 +17,30 @@ import json import os.path import pathlib import re -import traceback from pathlib import Path -from typing import List, Optional +from typing import Optional, List -from fastapi import APIRouter, Depends, File, Form, HTTPException, UploadFile, Query -from fastapi.responses import StreamingResponse -from fastapi.security import HTTPAuthorizationCredentials -from api.utils.api_utils import security +from fastapi import APIRouter, Depends, Query, UploadFile, File, Form +from fastapi.responses import Response +from api.apps.models.auth_dependencies import get_current_user +from api.apps.models.document_models import ( + CreateDocumentRequest, + WebCrawlRequest, + ListDocumentsQuery, + ListDocumentsBody, + FilterDocumentsRequest, + GetDocumentInfosRequest, + ChangeStatusRequest, + DeleteDocumentRequest, + RunDocumentRequest, + RenameDocumentRequest, + ChangeParserRequest, + ChangeParserSimpleRequest, + UploadAndParseRequest, + ParseRequest, + SetMetaRequest, +) from api import settings from api.common.check_team_permission import check_kb_team_permission from api.constants import FILE_NAME_LEN_LIMIT, IMG_BASE64_PREFIX @@ -43,159 +58,29 @@ from api.utils.api_utils import ( get_data_error_result, get_json_result, server_error_response, - validate_request, ) from api.utils.file_utils import filename_type, get_project_base_directory, thumbnail from api.utils.web_utils import CONTENT_TYPE_MAP, html2pdf, is_valid_url from deepdoc.parser.html_parser import RAGFlowHtmlParser -from rag.nlp import search +from rag.nlp import search, rag_tokenizer from rag.utils.storage_factory import STORAGE_IMPL -from pydantic import BaseModel -from api.db.db_models import User -# Security - -# Pydantic models for request/response -class WebCrawlRequest(BaseModel): - kb_id: str - name: str - url: str - -class CreateDocumentRequest(BaseModel): - name: str - kb_id: str - -class DocumentListRequest(BaseModel): - run_status: List[str] = [] - types: List[str] = [] - suffix: List[str] = [] - -class DocumentFilterRequest(BaseModel): - kb_id: str - keywords: str = "" - run_status: List[str] = [] - types: List[str] = [] - suffix: List[str] = [] - -class DocumentInfosRequest(BaseModel): - doc_ids: List[str] - -class ChangeStatusRequest(BaseModel): - doc_ids: List[str] - status: str - -class RemoveDocumentRequest(BaseModel): - doc_id: List[str] - -class RunDocumentRequest(BaseModel): - doc_ids: List[str] - run: int - delete: bool = False - -class RenameDocumentRequest(BaseModel): - doc_id: str - name: str - -class ChangeParserRequest(BaseModel): - doc_id: str - parser_id: str - pipeline_id: Optional[str] = None - parser_config: Optional[dict] = None - -class UploadAndParseRequest(BaseModel): - conversation_id: str - -class ParseRequest(BaseModel): - url: Optional[str] = None - -class SetMetaRequest(BaseModel): - doc_id: str - meta: str - - -# Dependency injection -async def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(security)): - """获取当前用户""" - from api.db import StatusEnum - from api.db.services.user_service import UserService - from fastapi import HTTPException, status - import logging - - try: - from itsdangerous.url_safe import URLSafeTimedSerializer as Serializer - except ImportError: - # 如果没有itsdangerous,使用jwt作为替代 - import jwt - Serializer = jwt - - jwt = Serializer(secret_key=settings.SECRET_KEY) - authorization = credentials.credentials - - if authorization: - try: - access_token = str(jwt.loads(authorization)) - - if not access_token or not access_token.strip(): - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Authentication attempt with empty access token" - ) - - # Access tokens should be UUIDs (32 hex characters) - if len(access_token.strip()) < 32: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail=f"Authentication attempt with invalid token format: {len(access_token)} chars" - ) - - user = UserService.query( - access_token=access_token, status=StatusEnum.VALID.value - ) - if user: - if not user[0].access_token or not user[0].access_token.strip(): - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail=f"User {user[0].email} has empty access_token in database" - ) - return user[0] - else: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Invalid access token" - ) - except Exception as e: - logging.warning(f"load_user got exception {e}") - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Invalid access token" - ) - else: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Authorization header required" - ) - -# Create router +# 创建路由器 router = APIRouter() @router.post("/upload") async def upload( kb_id: str = Form(...), - file: List[UploadFile] = File(...), + files: List[UploadFile] = File(...), current_user = Depends(get_current_user) ): - if not kb_id: - return get_json_result(data=False, message='Lack of "KB ID"', code=settings.RetCode.ARGUMENT_ERROR) - - if not file: + """上传文档""" + if not files: return get_json_result(data=False, message="No file part!", code=settings.RetCode.ARGUMENT_ERROR) - - # Use UploadFile directly - file is already a list from multiple file fields - file_objs = file - for file_obj in file_objs: - if file_obj.filename == "": + for file_obj in files: + if not file_obj.filename or file_obj.filename == "": return get_json_result(data=False, message="No file selected!", code=settings.RetCode.ARGUMENT_ERROR) if len(file_obj.filename.encode("utf-8")) > FILE_NAME_LEN_LIMIT: return get_json_result(data=False, message=f"File name must be {FILE_NAME_LEN_LIMIT} bytes or less.", code=settings.RetCode.ARGUMENT_ERROR) @@ -206,29 +91,30 @@ async def upload( if not check_kb_team_permission(kb, current_user.id): return get_json_result(data=False, message="No authorization.", code=settings.RetCode.AUTHENTICATION_ERROR) - err, files = await FileService.upload_document(kb, file_objs, current_user.id) + err, uploaded_files = FileService.upload_document(kb, files, current_user.id) if err: - return get_json_result(data=files, message="\n".join(err), code=settings.RetCode.SERVER_ERROR) + return get_json_result(data=uploaded_files, message="\n".join(err), code=settings.RetCode.SERVER_ERROR) - if not files: - return get_json_result(data=files, message="There seems to be an issue with your file format. Please verify it is correct and not corrupted.", code=settings.RetCode.DATA_ERROR) - files = [f[0] for f in files] # remove the blob + if not uploaded_files: + return get_json_result(data=uploaded_files, message="There seems to be an issue with your file format. Please verify it is correct and not corrupted.", code=settings.RetCode.DATA_ERROR) + files_result = [f[0] for f in uploaded_files] # remove the blob - return get_json_result(data=files) + return get_json_result(data=files_result) @router.post("/web_crawl") async def web_crawl( - req: WebCrawlRequest, + request: WebCrawlRequest, current_user = Depends(get_current_user) ): - kb_id = req.kb_id - if not kb_id: - return get_json_result(data=False, message='Lack of "KB ID"', code=settings.RetCode.ARGUMENT_ERROR) - name = req.name - url = req.url + """网页爬取""" + kb_id = request.kb_id + name = request.name + url = request.url + if not is_valid_url(url): return get_json_result(data=False, message="The URL format is invalid", code=settings.RetCode.ARGUMENT_ERROR) + e, kb = KnowledgebaseService.get_by_id(kb_id) if not e: raise LookupError("Can't find this knowledgebase!") @@ -259,6 +145,7 @@ async def web_crawl( "id": get_uuid(), "kb_id": kb.id, "parser_id": kb.parser_id, + "pipeline_id": kb.pipeline_id, "parser_config": kb.parser_config, "created_by": current_user.id, "type": filetype, @@ -285,25 +172,26 @@ async def web_crawl( @router.post("/create") async def create( - req: CreateDocumentRequest, + request: CreateDocumentRequest, current_user = Depends(get_current_user) ): - kb_id = req.kb_id - if not kb_id: - return get_json_result(data=False, message='Lack of "KB ID"', code=settings.RetCode.ARGUMENT_ERROR) - if len(req.name.encode("utf-8")) > FILE_NAME_LEN_LIMIT: + """创建文档""" + req = request.model_dump(exclude_unset=True) + kb_id = req["kb_id"] + + if len(req["name"].encode("utf-8")) > FILE_NAME_LEN_LIMIT: return get_json_result(data=False, message=f"File name must be {FILE_NAME_LEN_LIMIT} bytes or less.", code=settings.RetCode.ARGUMENT_ERROR) - if req.name.strip() == "": + if req["name"].strip() == "": return get_json_result(data=False, message="File name can't be empty.", code=settings.RetCode.ARGUMENT_ERROR) - req.name = req.name.strip() + req["name"] = req["name"].strip() try: e, kb = KnowledgebaseService.get_by_id(kb_id) if not e: return get_data_error_result(message="Can't find this knowledgebase!") - if DocumentService.query(name=req.name, kb_id=kb_id): + if DocumentService.query(name=req["name"], kb_id=kb_id): return get_data_error_result(message="Duplicated document name in the same knowledgebase.") kb_root_folder = FileService.get_kb_folder(kb.tenant_id) @@ -326,8 +214,8 @@ async def create( "parser_config": kb.parser_config, "created_by": current_user.id, "type": FileType.VIRTUAL, - "name": req.name, - "suffix": Path(req.name).suffix.lstrip("."), + "name": req["name"], + "suffix": Path(req["name"]).suffix.lstrip("."), "location": "", "size": 0, } @@ -342,47 +230,46 @@ async def create( @router.post("/list") async def list_docs( - kb_id: str = Query(...), - keywords: str = Query(""), - page: int = Query(0), - page_size: int = Query(0), - orderby: str = Query("create_time"), - desc: str = Query("true"), - create_time_from: int = Query(0), - create_time_to: int = Query(0), - req: DocumentListRequest = None, + query: ListDocumentsQuery = Depends(), + body: Optional[ListDocumentsBody] = None, current_user = Depends(get_current_user) ): - if not kb_id: - return get_json_result(data=False, message='Lack of "KB ID"', code=settings.RetCode.ARGUMENT_ERROR) + """列出文档""" + if body is None: + body = ListDocumentsBody() + + kb_id = query.kb_id tenants = UserTenantService.query(user_id=current_user.id) for tenant in tenants: if KnowledgebaseService.query(tenant_id=tenant.tenant_id, id=kb_id): break else: return get_json_result(data=False, message="Only owner of knowledgebase authorized for this operation.", code=settings.RetCode.OPERATING_ERROR) + + keywords = query.keywords or "" + page_number = int(query.page or 0) + items_per_page = int(query.page_size or 0) + orderby = query.orderby or "create_time" + desc = query.desc.lower() == "true" if query.desc else True + create_time_from = int(query.create_time_from or 0) + create_time_to = int(query.create_time_to or 0) - if desc.lower() == "false": - desc_bool = False - else: - desc_bool = True - - run_status = req.run_status if req else [] + run_status = body.run_status or [] if run_status: invalid_status = {s for s in run_status if s not in VALID_TASK_STATUS} if invalid_status: return get_data_error_result(message=f"Invalid filter run status conditions: {', '.join(invalid_status)}") - types = req.types if req else [] + types = body.types or [] if types: invalid_types = {t for t in types if t not in VALID_FILE_TYPES} if invalid_types: return get_data_error_result(message=f"Invalid filter conditions: {', '.join(invalid_types)} type{'s' if len(invalid_types) > 1 else ''}") - suffix = req.suffix if req else [] + suffix = body.suffix or [] try: - docs, tol = DocumentService.get_by_kb_id(kb_id, page, page_size, orderby, desc_bool, keywords, run_status, types, suffix) + docs, tol = DocumentService.get_by_kb_id(kb_id, page_number, items_per_page, orderby, desc, keywords, run_status, types, suffix) if create_time_from or create_time_to: filtered_docs = [] @@ -403,12 +290,11 @@ async def list_docs( @router.post("/filter") async def get_filter( - req: DocumentFilterRequest, + request: FilterDocumentsRequest, current_user = Depends(get_current_user) ): - kb_id = req.kb_id - if not kb_id: - return get_json_result(data=False, message='Lack of "KB ID"', code=settings.RetCode.ARGUMENT_ERROR) + """过滤文档""" + kb_id = request.kb_id tenants = UserTenantService.query(user_id=current_user.id) for tenant in tenants: if KnowledgebaseService.query(tenant_id=tenant.tenant_id, id=kb_id): @@ -416,15 +302,16 @@ async def get_filter( else: return get_json_result(data=False, message="Only owner of knowledgebase authorized for this operation.", code=settings.RetCode.OPERATING_ERROR) - keywords = req.keywords - suffix = req.suffix - run_status = req.run_status + keywords = request.keywords or "" + suffix = request.suffix or [] + run_status = request.run_status or [] + if run_status: invalid_status = {s for s in run_status if s not in VALID_TASK_STATUS} if invalid_status: return get_data_error_result(message=f"Invalid filter run status conditions: {', '.join(invalid_status)}") - types = req.types + types = request.types or [] if types: invalid_types = {t for t in types if t not in VALID_FILE_TYPES} if invalid_types: @@ -439,10 +326,11 @@ async def get_filter( @router.post("/infos") async def docinfos( - req: DocumentInfosRequest, + request: GetDocumentInfosRequest, current_user = Depends(get_current_user) ): - doc_ids = req.doc_ids + """获取文档信息""" + doc_ids = request.doc_ids for doc_id in doc_ids: if not DocumentService.accessible(doc_id, current_user.id): return get_json_result(data=False, message="No authorization.", code=settings.RetCode.AUTHENTICATION_ERROR) @@ -452,8 +340,9 @@ async def docinfos( @router.get("/thumbnails") async def thumbnails( - doc_ids: List[str] = Query(...) + doc_ids: List[str] = Query(..., description="文档ID列表"), ): + """获取文档缩略图""" if not doc_ids: return get_json_result(data=False, message='Lack of "Document ID"', code=settings.RetCode.ARGUMENT_ERROR) @@ -471,14 +360,12 @@ async def thumbnails( @router.post("/change_status") async def change_status( - req: ChangeStatusRequest, + request: ChangeStatusRequest, current_user = Depends(get_current_user) ): - doc_ids = req.doc_ids - status = str(req.status) - - if status not in ["0", "1"]: - return get_json_result(data=False, message='"Status" must be either 0 or 1!', code=settings.RetCode.ARGUMENT_ERROR) + """修改文档状态""" + doc_ids = request.doc_ids + status = request.status result = {} for doc_id in doc_ids: @@ -511,10 +398,11 @@ async def change_status( @router.post("/rm") async def rm( - req: RemoveDocumentRequest, + request: DeleteDocumentRequest, current_user = Depends(get_current_user) ): - doc_ids = req.doc_id + """删除文档""" + doc_ids = request.doc_id if isinstance(doc_ids, str): doc_ids = [doc_ids] @@ -570,17 +458,19 @@ async def rm( @router.post("/run") async def run( - req: RunDocumentRequest, + request: RunDocumentRequest, current_user = Depends(get_current_user) ): - for doc_id in req.doc_ids: + """运行文档解析""" + for doc_id in request.doc_ids: if not DocumentService.accessible(doc_id, current_user.id): return get_json_result(data=False, message="No authorization.", code=settings.RetCode.AUTHENTICATION_ERROR) + try: kb_table_num_map = {} - for id in req.doc_ids: - info = {"run": str(req.run), "progress": 0} - if req.run == int(TaskStatus.RUNNING.value) and req.delete: + for id in request.doc_ids: + info = {"run": str(request.run), "progress": 0} + if str(request.run) == TaskStatus.RUNNING.value and request.delete: info["progress_msg"] = "" info["chunk_num"] = 0 info["token_num"] = 0 @@ -592,21 +482,21 @@ async def run( if not e: return get_data_error_result(message="Document not found!") - if req.run == int(TaskStatus.CANCEL.value): + if str(request.run) == TaskStatus.CANCEL.value: if str(doc.run) == TaskStatus.RUNNING.value: cancel_all_task_of(id) else: return get_data_error_result(message="Cannot cancel a task that is not in RUNNING status") - if all([req.delete, req.run == int(TaskStatus.RUNNING.value), str(doc.run) == TaskStatus.DONE.value]): + if all([not request.delete, str(request.run) == TaskStatus.RUNNING.value, str(doc.run) == TaskStatus.DONE.value]): DocumentService.clear_chunk_num_when_rerun(doc.id) DocumentService.update_by_id(id, info) - if req.delete: + if request.delete: TaskService.filter_delete([Task.doc_id == id]) if settings.docStoreConn.indexExist(search.index_name(tenant_id), doc.kb_id): settings.docStoreConn.delete({"doc_id": id}, search.index_name(tenant_id), doc.kb_id) - if req.run == int(TaskStatus.RUNNING.value): + if str(request.run) == TaskStatus.RUNNING.value: doc = doc.to_dict() doc["tenant_id"] = tenant_id @@ -628,37 +518,53 @@ async def run( return get_json_result(data=True) except Exception as e: - traceback.print_exc() return server_error_response(e) @router.post("/rename") async def rename( - req: RenameDocumentRequest, + request: RenameDocumentRequest, current_user = Depends(get_current_user) ): - if not DocumentService.accessible(req.doc_id, current_user.id): + """重命名文档""" + if not DocumentService.accessible(request.doc_id, current_user.id): return get_json_result(data=False, message="No authorization.", code=settings.RetCode.AUTHENTICATION_ERROR) + try: - e, doc = DocumentService.get_by_id(req.doc_id) + e, doc = DocumentService.get_by_id(request.doc_id) if not e: return get_data_error_result(message="Document not found!") - if pathlib.Path(req.name.lower()).suffix != pathlib.Path(doc.name.lower()).suffix: + if pathlib.Path(request.name.lower()).suffix != pathlib.Path(doc.name.lower()).suffix: return get_json_result(data=False, message="The extension of file can't be changed", code=settings.RetCode.ARGUMENT_ERROR) - if len(req.name.encode("utf-8")) > FILE_NAME_LEN_LIMIT: + if len(request.name.encode("utf-8")) > FILE_NAME_LEN_LIMIT: return get_json_result(data=False, message=f"File name must be {FILE_NAME_LEN_LIMIT} bytes or less.", code=settings.RetCode.ARGUMENT_ERROR) - for d in DocumentService.query(name=req.name, kb_id=doc.kb_id): - if d.name == req.name: + for d in DocumentService.query(name=request.name, kb_id=doc.kb_id): + if d.name == request.name: return get_data_error_result(message="Duplicated document name in the same knowledgebase.") - if not DocumentService.update_by_id(req.doc_id, {"name": req.name}): + if not DocumentService.update_by_id(request.doc_id, {"name": request.name}): return get_data_error_result(message="Database error (Document rename)!") - informs = File2DocumentService.get_by_document_id(req.doc_id) + informs = File2DocumentService.get_by_document_id(request.doc_id) if informs: e, file = FileService.get_by_id(informs[0].file_id) - FileService.update_by_id(file.id, {"name": req.name}) + FileService.update_by_id(file.id, {"name": request.name}) + + tenant_id = DocumentService.get_tenant_id(request.doc_id) + title_tks = rag_tokenizer.tokenize(request.name) + es_body = { + "docnm_kwd": request.name, + "title_tks": title_tks, + "title_sm_tks": rag_tokenizer.fine_grained_tokenize(title_tks), + } + if settings.docStoreConn.indexExist(search.index_name(tenant_id), doc.kb_id): + settings.docStoreConn.update( + {"doc_id": request.doc_id}, + es_body, + search.index_name(tenant_id), + doc.kb_id, + ) return get_json_result(data=True) except Exception as e: @@ -667,6 +573,7 @@ async def rename( @router.get("/get/{doc_id}") async def get(doc_id: str): + """获取文档文件""" try: e, doc = DocumentService.get_by_id(doc_id) if not e: @@ -677,70 +584,79 @@ async def get(doc_id: str): ext = re.search(r"\.([^.]+)$", doc.name.lower()) ext = ext.group(1) if ext else None - + content_type = "application/octet-stream" if ext: if doc.type == FileType.VISUAL.value: - media_type = CONTENT_TYPE_MAP.get(ext, f"image/{ext}") + content_type = CONTENT_TYPE_MAP.get(ext, f"image/{ext}") else: - media_type = CONTENT_TYPE_MAP.get(ext, f"application/{ext}") - else: - media_type = "application/octet-stream" - - return StreamingResponse( - iter([content]), - media_type=media_type, - headers={"Content-Disposition": f"attachment; filename={doc.name}"} - ) + content_type = CONTENT_TYPE_MAP.get(ext, f"application/{ext}") + + return Response(content=content, media_type=content_type) except Exception as e: return server_error_response(e) @router.post("/change_parser") async def change_parser( - req: ChangeParserRequest, + request: ChangeParserSimpleRequest, current_user = Depends(get_current_user) ): - if not DocumentService.accessible(req.doc_id, current_user.id): + """修改文档解析器""" + if not DocumentService.accessible(request.doc_id, current_user.id): return get_json_result(data=False, message="No authorization.", code=settings.RetCode.AUTHENTICATION_ERROR) - e, doc = DocumentService.get_by_id(req.doc_id) + e, doc = DocumentService.get_by_id(request.doc_id) if not e: return get_data_error_result(message="Document not found!") - def reset_doc(): + def reset_doc(update_data_override=None): nonlocal doc - e = DocumentService.update_by_id(doc.id, {"parser_id": req.parser_id, "progress": 0, "progress_msg": "", "run": TaskStatus.UNSTART.value}) + update_data = update_data_override or {} + if request.pipeline_id is not None: + update_data["pipeline_id"] = request.pipeline_id + if request.parser_id is not None: + update_data["parser_id"] = request.parser_id + update_data.update({ + "progress": 0, + "progress_msg": "", + "run": TaskStatus.UNSTART.value + }) + e = DocumentService.update_by_id(doc.id, update_data) if not e: return get_data_error_result(message="Document not found!") if doc.token_num > 0: e = DocumentService.increment_chunk_num(doc.id, doc.kb_id, doc.token_num * -1, doc.chunk_num * -1, doc.process_duration * -1) if not e: return get_data_error_result(message="Document not found!") - tenant_id = DocumentService.get_tenant_id(req.doc_id) + tenant_id = DocumentService.get_tenant_id(request.doc_id) if not tenant_id: return get_data_error_result(message="Tenant not found!") if settings.docStoreConn.indexExist(search.index_name(tenant_id), doc.kb_id): settings.docStoreConn.delete({"doc_id": doc.id}, search.index_name(tenant_id), doc.kb_id) try: - if req.pipeline_id: - if doc.pipeline_id == req.pipeline_id: + if request.pipeline_id is not None and request.pipeline_id != "": + if doc.pipeline_id == request.pipeline_id: return get_json_result(data=True) - DocumentService.update_by_id(doc.id, {"pipeline_id": req.pipeline_id}) - reset_doc() + reset_doc({"pipeline_id": request.pipeline_id}) return get_json_result(data=True) - if doc.parser_id.lower() == req.parser_id.lower(): - if req.parser_config: - if req.parser_config == doc.parser_config: + if request.parser_id is None: + return get_json_result(data=False, message="缺少 parser_id 或 pipeline_id", code=settings.RetCode.ARGUMENT_ERROR) + + if doc.parser_id.lower() == request.parser_id.lower(): + if request.parser_config is not None: + if request.parser_config == doc.parser_config: return get_json_result(data=True) else: return get_json_result(data=True) - if (doc.type == FileType.VISUAL and req.parser_id != "picture") or (re.search(r"\.(ppt|pptx|pages)$", doc.name) and req.parser_id != "presentation"): + if (doc.type == FileType.VISUAL and request.parser_id != "picture") or (re.search(r"\.(ppt|pptx|pages)$", doc.name) and request.parser_id != "presentation"): return get_data_error_result(message="Not supported yet!") - if req.parser_config: - DocumentService.update_parser_config(doc.id, req.parser_config) + + if request.parser_config is not None: + DocumentService.update_parser_config(doc.id, request.parser_config) + reset_doc() return get_json_result(data=True) except Exception as e: @@ -749,16 +665,14 @@ async def change_parser( @router.get("/image/{image_id}") async def get_image(image_id: str): + """获取图片""" try: arr = image_id.split("-") if len(arr) != 2: return get_data_error_result(message="Image not found.") bkt, nm = image_id.split("-") content = STORAGE_IMPL.get(bkt, nm) - return StreamingResponse( - iter([content]), - media_type="image/JPEG" - ) + return Response(content=content, media_type="image/JPEG") except Exception as e: return server_error_response(e) @@ -769,28 +683,28 @@ async def upload_and_parse( files: List[UploadFile] = File(...), current_user = Depends(get_current_user) ): + """上传并解析""" if not files: return get_json_result(data=False, message="No file part!", code=settings.RetCode.ARGUMENT_ERROR) - # Use UploadFile directly - file_objs = files - - for file_obj in file_objs: - if file_obj.filename == "": + for file_obj in files: + if not file_obj.filename or file_obj.filename == "": return get_json_result(data=False, message="No file selected!", code=settings.RetCode.ARGUMENT_ERROR) - doc_ids = await doc_upload_and_parse(conversation_id, file_objs, current_user.id) + doc_ids = doc_upload_and_parse(conversation_id, files, current_user.id) return get_json_result(data=doc_ids) @router.post("/parse") async def parse( - req: ParseRequest = None, - files: List[UploadFile] = File(None), + request: Optional[ParseRequest] = None, + files: Optional[List[UploadFile]] = File(None), current_user = Depends(get_current_user) ): - url = req.url if req else "" + """解析文档""" + url = request.url if request else None + if url: if not is_valid_url(url): return get_json_result(data=False, message="The URL format is invalid", code=settings.RetCode.ARGUMENT_ERROR) @@ -828,46 +742,37 @@ async def parse( if not r or not r.group(1): return get_json_result(data=False, message="Can't not identify downloaded file", code=settings.RetCode.ARGUMENT_ERROR) f = File(r.group(1), os.path.join(download_path, r.group(1))) - txt = await FileService.parse_docs([f], current_user.id) + txt = FileService.parse_docs([f], current_user.id) return get_json_result(data=txt) if not files: return get_json_result(data=False, message="No file part!", code=settings.RetCode.ARGUMENT_ERROR) - # Use UploadFile directly - file_objs = files - txt = await FileService.parse_docs(file_objs, current_user.id) + txt = FileService.parse_docs(files, current_user.id) return get_json_result(data=txt) @router.post("/set_meta") async def set_meta( - req: SetMetaRequest, + request: SetMetaRequest, current_user = Depends(get_current_user) ): - if not DocumentService.accessible(req.doc_id, current_user.id): + """设置元数据""" + if not DocumentService.accessible(request.doc_id, current_user.id): return get_json_result(data=False, message="No authorization.", code=settings.RetCode.AUTHENTICATION_ERROR) + try: - meta = json.loads(req.meta) - if not isinstance(meta, dict): - return get_json_result(data=False, message="Only dictionary type supported.", code=settings.RetCode.ARGUMENT_ERROR) - for k, v in meta.items(): - if not isinstance(v, str) and not isinstance(v, int) and not isinstance(v, float): - return get_json_result(data=False, message=f"The type is not supported: {v}", code=settings.RetCode.ARGUMENT_ERROR) - except Exception as e: - return get_json_result(data=False, message=f"Json syntax error: {e}", code=settings.RetCode.ARGUMENT_ERROR) - if not isinstance(meta, dict): - return get_json_result(data=False, message='Meta data should be in Json map format, like {"key": "value"}', code=settings.RetCode.ARGUMENT_ERROR) - - try: - e, doc = DocumentService.get_by_id(req.doc_id) + meta = json.loads(request.meta) + + e, doc = DocumentService.get_by_id(request.doc_id) if not e: return get_data_error_result(message="Document not found!") - if not DocumentService.update_by_id(req.doc_id, {"meta_fields": meta}): + if not DocumentService.update_by_id(request.doc_id, {"meta_fields": meta}): return get_data_error_result(message="Database error (meta updates)!") return get_json_result(data=True) except Exception as e: return server_error_response(e) + diff --git a/api/apps/file2document_app.py b/api/apps/file2document_app.py index 1866592..862b7e7 100644 --- a/api/apps/file2document_app.py +++ b/api/apps/file2document_app.py @@ -15,15 +15,12 @@ # from pathlib import Path -from typing import List - -from fastapi import APIRouter, Depends -from fastapi.security import HTTPAuthorizationCredentials -from api.utils.api_utils import security from api.db.services.file2document_service import File2DocumentService from api.db.services.file_service import FileService +from flask import request +from flask_login import login_required, current_user from api.db.services.knowledgebase_service import KnowledgebaseService from api.utils.api_utils import server_error_response, get_data_error_result, validate_request from api.utils import get_uuid @@ -31,91 +28,15 @@ from api.db import FileType from api.db.services.document_service import DocumentService from api import settings from api.utils.api_utils import get_json_result -from pydantic import BaseModel - -# Security - -# Pydantic models for request/response -class ConvertRequest(BaseModel): - file_ids: List[str] - kb_ids: List[str] - -class RemoveFile2DocumentRequest(BaseModel): - file_ids: List[str] - -# Dependency injection -async def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(security)): - """获取当前用户""" - from api.db import StatusEnum - from api.db.services.user_service import UserService - from fastapi import HTTPException, status - import logging - - try: - from itsdangerous.url_safe import URLSafeTimedSerializer as Serializer - except ImportError: - # 如果没有itsdangerous,使用jwt作为替代 - import jwt - Serializer = jwt - - jwt = Serializer(secret_key=settings.SECRET_KEY) - authorization = credentials.credentials - - if authorization: - try: - access_token = str(jwt.loads(authorization)) - - if not access_token or not access_token.strip(): - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Authentication attempt with empty access token" - ) - - # Access tokens should be UUIDs (32 hex characters) - if len(access_token.strip()) < 32: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail=f"Authentication attempt with invalid token format: {len(access_token)} chars" - ) - - user = UserService.query( - access_token=access_token, status=StatusEnum.VALID.value - ) - if user: - if not user[0].access_token or not user[0].access_token.strip(): - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail=f"User {user[0].email} has empty access_token in database" - ) - return user[0] - else: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Invalid access token" - ) - except Exception as e: - logging.warning(f"load_user got exception {e}") - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Invalid access token" - ) - else: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Authorization header required" - ) - -# Create router -router = APIRouter() -@router.post('/convert') -async def convert( - req: ConvertRequest, - current_user = Depends(get_current_user) -): - kb_ids = req.kb_ids - file_ids = req.file_ids +@manager.route('/convert', methods=['POST']) # noqa: F821 +@login_required +@validate_request("file_ids", "kb_ids") +def convert(): + req = request.json + kb_ids = req["kb_ids"] + file_ids = req["file_ids"] file2documents = [] try: @@ -179,12 +100,12 @@ async def convert( return server_error_response(e) -@router.post('/rm') -async def rm( - req: RemoveFile2DocumentRequest, - current_user = Depends(get_current_user) -): - file_ids = req.file_ids +@manager.route('/rm', methods=['POST']) # noqa: F821 +@login_required +@validate_request("file_ids") +def rm(): + req = request.json + file_ids = req["file_ids"] if not file_ids: return get_json_result( data=False, message='Lack of "Files ID"', code=settings.RetCode.ARGUMENT_ERROR) diff --git a/api/apps/file_app.py b/api/apps/file_app.py index f31c885..7828a82 100644 --- a/api/apps/file_app.py +++ b/api/apps/file_app.py @@ -13,15 +13,14 @@ # See the License for the specific language governing permissions and # limitations under the License # +import logging import os import pathlib import re -from typing import List, Optional -from fastapi import APIRouter, Depends, File, Form, HTTPException, UploadFile, Query -from fastapi.responses import StreamingResponse -from fastapi.security import HTTPAuthorizationCredentials -from api.utils.api_utils import security +import flask +from flask import request +from flask_login import login_required, current_user from api.common.check_team_permission import check_file_team_permission from api.db.services.document_service import DocumentService @@ -36,110 +35,22 @@ from api.utils.api_utils import get_json_result from api.utils.file_utils import filename_type from api.utils.web_utils import CONTENT_TYPE_MAP from rag.utils.storage_factory import STORAGE_IMPL -from pydantic import BaseModel - -# Security - -# Pydantic models for request/response -class CreateFileRequest(BaseModel): - name: str - parent_id: Optional[str] = None - type: Optional[str] = None - -class RemoveFileRequest(BaseModel): - file_ids: List[str] - -class RenameFileRequest(BaseModel): - file_id: str - name: str - -class MoveFileRequest(BaseModel): - src_file_ids: List[str] - dest_file_id: str - -# Dependency injection -async def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(security)): - """获取当前用户""" - from api.db import StatusEnum - from api.db.services.user_service import UserService - from fastapi import HTTPException, status - import logging - - try: - from itsdangerous.url_safe import URLSafeTimedSerializer as Serializer - except ImportError: - # 如果没有itsdangerous,使用jwt作为替代 - import jwt - Serializer = jwt - - jwt = Serializer(secret_key=settings.SECRET_KEY) - authorization = credentials.credentials - - if authorization: - try: - access_token = str(jwt.loads(authorization)) - - if not access_token or not access_token.strip(): - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Authentication attempt with empty access token" - ) - - # Access tokens should be UUIDs (32 hex characters) - if len(access_token.strip()) < 32: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail=f"Authentication attempt with invalid token format: {len(access_token)} chars" - ) - - user = UserService.query( - access_token=access_token, status=StatusEnum.VALID.value - ) - if user: - if not user[0].access_token or not user[0].access_token.strip(): - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail=f"User {user[0].email} has empty access_token in database" - ) - return user[0] - else: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Invalid access token" - ) - except Exception as e: - logging.warning(f"load_user got exception {e}") - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Invalid access token" - ) - else: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Authorization header required" - ) - -# Create router -router = APIRouter() -@router.post('/upload') -async def upload( - parent_id: Optional[str] = Form(None), - files: List[UploadFile] = File(...), - current_user = Depends(get_current_user) -): - pf_id = parent_id +@manager.route('/upload', methods=['POST']) # noqa: F821 +@login_required +# @validate_request("parent_id") +def upload(): + pf_id = request.form.get("parent_id") if not pf_id: root_folder = FileService.get_root_folder(current_user.id) pf_id = root_folder["id"] - if not files: + if 'file' not in request.files: return get_json_result( data=False, message='No file part!', code=settings.RetCode.ARGUMENT_ERROR) - - file_objs = files + file_objs = request.files.getlist('file') for file_obj in file_objs: if file_obj.filename == '': @@ -186,7 +97,7 @@ async def upload( location = file_obj_names[file_len - 1] while STORAGE_IMPL.obj_exist(last_folder.id, location): location += "_" - blob = await file_obj.read() + blob = file_obj.read() filename = duplicate_name( FileService.query, name=file_obj_names[file_len - 1], @@ -209,13 +120,13 @@ async def upload( return server_error_response(e) -@router.post('/create') -async def create( - req: CreateFileRequest, - current_user = Depends(get_current_user) -): - pf_id = req.parent_id - input_file_type = req.type +@manager.route('/create', methods=['POST']) # noqa: F821 +@login_required +@validate_request("name") +def create(): + req = request.json + pf_id = request.json.get("parent_id") + input_file_type = request.json.get("type") if not pf_id: root_folder = FileService.get_root_folder(current_user.id) pf_id = root_folder["id"] @@ -224,7 +135,7 @@ async def create( if not FileService.is_parent_folder_exist(pf_id): return get_json_result( data=False, message="Parent Folder Doesn't Exist!", code=settings.RetCode.OPERATING_ERROR) - if FileService.query(name=req.name, parent_id=pf_id): + if FileService.query(name=req["name"], parent_id=pf_id): return get_data_error_result( message="Duplicated folder name in the same folder.") @@ -238,7 +149,7 @@ async def create( "parent_id": pf_id, "tenant_id": current_user.id, "created_by": current_user.id, - "name": req.name, + "name": req["name"], "location": "", "size": 0, "type": file_type @@ -249,18 +160,17 @@ async def create( return server_error_response(e) -@router.get('/list') -async def list_files( - parent_id: Optional[str] = Query(None), - keywords: str = Query(""), - page: int = Query(1), - page_size: int = Query(15), - orderby: str = Query("create_time"), - desc: bool = Query(True), - current_user = Depends(get_current_user) -): - pf_id = parent_id +@manager.route('/list', methods=['GET']) # noqa: F821 +@login_required +def list_files(): + pf_id = request.args.get("parent_id") + keywords = request.args.get("keywords", "") + + page_number = int(request.args.get("page", 1)) + items_per_page = int(request.args.get("page_size", 15)) + orderby = request.args.get("orderby", "create_time") + desc = request.args.get("desc", True) if not pf_id: root_folder = FileService.get_root_folder(current_user.id) pf_id = root_folder["id"] @@ -271,7 +181,7 @@ async def list_files( return get_data_error_result(message="Folder not found!") files, total = FileService.get_by_pf_id( - current_user.id, pf_id, page, page_size, orderby, desc, keywords) + current_user.id, pf_id, page_number, items_per_page, orderby, desc, keywords) parent_folder = FileService.get_parent_folder(pf_id) if not parent_folder: @@ -282,8 +192,9 @@ async def list_files( return server_error_response(e) -@router.get('/root_folder') -async def get_root_folder(current_user = Depends(get_current_user)): +@manager.route('/root_folder', methods=['GET']) # noqa: F821 +@login_required +def get_root_folder(): try: root_folder = FileService.get_root_folder(current_user.id) return get_json_result(data={"root_folder": root_folder}) @@ -291,11 +202,10 @@ async def get_root_folder(current_user = Depends(get_current_user)): return server_error_response(e) -@router.get('/parent_folder') -async def get_parent_folder( - file_id: str = Query(...), - current_user = Depends(get_current_user) -): +@manager.route('/parent_folder', methods=['GET']) # noqa: F821 +@login_required +def get_parent_folder(): + file_id = request.args.get("file_id") try: e, file = FileService.get_by_id(file_id) if not e: @@ -307,11 +217,10 @@ async def get_parent_folder( return server_error_response(e) -@router.get('/all_parent_folder') -async def get_all_parent_folders( - file_id: str = Query(...), - current_user = Depends(get_current_user) -): +@manager.route('/all_parent_folder', methods=['GET']) # noqa: F821 +@login_required +def get_all_parent_folders(): + file_id = request.args.get("file_id") try: e, file = FileService.get_by_id(file_id) if not e: @@ -326,90 +235,99 @@ async def get_all_parent_folders( return server_error_response(e) -@router.post('/rm') -async def rm( - req: RemoveFileRequest, - current_user = Depends(get_current_user) -): - file_ids = req.file_ids +@manager.route("/rm", methods=["POST"]) # noqa: F821 +@login_required +@validate_request("file_ids") +def rm(): + req = request.json + file_ids = req["file_ids"] + + def _delete_single_file(file): + try: + if file.location: + STORAGE_IMPL.rm(file.parent_id, file.location) + except Exception: + logging.exception(f"Fail to remove object: {file.parent_id}/{file.location}") + + informs = File2DocumentService.get_by_file_id(file.id) + for inform in informs: + doc_id = inform.document_id + e, doc = DocumentService.get_by_id(doc_id) + if e and doc: + tenant_id = DocumentService.get_tenant_id(doc_id) + if tenant_id: + DocumentService.remove_document(doc, tenant_id) + File2DocumentService.delete_by_file_id(file.id) + + FileService.delete(file) + + def _delete_folder_recursive(folder, tenant_id): + sub_files = FileService.list_all_files_by_parent_id(folder.id) + for sub_file in sub_files: + if sub_file.type == FileType.FOLDER.value: + _delete_folder_recursive(sub_file, tenant_id) + else: + _delete_single_file(sub_file) + + FileService.delete(folder) + try: for file_id in file_ids: e, file = FileService.get_by_id(file_id) - if not e: + if not e or not file: return get_data_error_result(message="File or Folder not found!") if not file.tenant_id: return get_data_error_result(message="Tenant not found!") if not check_file_team_permission(file, current_user.id): - return get_json_result(data=False, message='No authorization.', code=settings.RetCode.AUTHENTICATION_ERROR) + return get_json_result(data=False, message="No authorization.", code=settings.RetCode.AUTHENTICATION_ERROR) + if file.source_type == FileSource.KNOWLEDGEBASE: continue if file.type == FileType.FOLDER.value: - file_id_list = FileService.get_all_innermost_file_ids(file_id, []) - for inner_file_id in file_id_list: - e, file = FileService.get_by_id(inner_file_id) - if not e: - return get_data_error_result(message="File not found!") - STORAGE_IMPL.rm(file.parent_id, file.location) - FileService.delete_folder_by_pf_id(current_user.id, file_id) - else: - STORAGE_IMPL.rm(file.parent_id, file.location) - if not FileService.delete(file): - return get_data_error_result( - message="Database error (File removal)!") + _delete_folder_recursive(file, current_user.id) + continue - # delete file2document - informs = File2DocumentService.get_by_file_id(file_id) - for inform in informs: - doc_id = inform.document_id - e, doc = DocumentService.get_by_id(doc_id) - if not e: - return get_data_error_result(message="Document not found!") - tenant_id = DocumentService.get_tenant_id(doc_id) - if not tenant_id: - return get_data_error_result(message="Tenant not found!") - if not DocumentService.remove_document(doc, tenant_id): - return get_data_error_result( - message="Database error (Document removal)!") - File2DocumentService.delete_by_file_id(file_id) + _delete_single_file(file) return get_json_result(data=True) + except Exception as e: return server_error_response(e) -@router.post('/rename') -async def rename( - req: RenameFileRequest, - current_user = Depends(get_current_user) -): +@manager.route('/rename', methods=['POST']) # noqa: F821 +@login_required +@validate_request("file_id", "name") +def rename(): + req = request.json try: - e, file = FileService.get_by_id(req.file_id) + e, file = FileService.get_by_id(req["file_id"]) if not e: return get_data_error_result(message="File not found!") if not check_file_team_permission(file, current_user.id): return get_json_result(data=False, message='No authorization.', code=settings.RetCode.AUTHENTICATION_ERROR) if file.type != FileType.FOLDER.value \ - and pathlib.Path(req.name.lower()).suffix != pathlib.Path( + and pathlib.Path(req["name"].lower()).suffix != pathlib.Path( file.name.lower()).suffix: return get_json_result( data=False, message="The extension of file can't be changed", code=settings.RetCode.ARGUMENT_ERROR) - for file in FileService.query(name=req.name, pf_id=file.parent_id): - if file.name == req.name: + for file in FileService.query(name=req["name"], pf_id=file.parent_id): + if file.name == req["name"]: return get_data_error_result( message="Duplicated file name in the same folder.") if not FileService.update_by_id( - req.file_id, {"name": req.name}): + req["file_id"], {"name": req["name"]}): return get_data_error_result( message="Database error (File rename)!") - informs = File2DocumentService.get_by_file_id(req.file_id) + informs = File2DocumentService.get_by_file_id(req["file_id"]) if informs: if not DocumentService.update_by_id( - informs[0].document_id, {"name": req.name}): + informs[0].document_id, {"name": req["name"]}): return get_data_error_result( message="Database error (Document rename)!") @@ -418,8 +336,9 @@ async def rename( return server_error_response(e) -@router.get('/get/{file_id}') -async def get(file_id: str, current_user = Depends(get_current_user)): +@manager.route('/get/', methods=['GET']) # noqa: F821 +@login_required +def get(file_id): try: e, file = FileService.get_by_id(file_id) if not e: @@ -432,6 +351,7 @@ async def get(file_id: str, current_user = Depends(get_current_user)): b, n = File2DocumentService.get_storage_address(file_id=file_id) blob = STORAGE_IMPL.get(b, n) + response = flask.make_response(blob) ext = re.search(r"\.([^.]+)$", file.name.lower()) ext = ext.group(1) if ext else None if ext: @@ -439,43 +359,95 @@ async def get(file_id: str, current_user = Depends(get_current_user)): content_type = CONTENT_TYPE_MAP.get(ext, f"image/{ext}") else: content_type = CONTENT_TYPE_MAP.get(ext, f"application/{ext}") - else: - content_type = "application/octet-stream" - - return StreamingResponse( - iter([blob]), - media_type=content_type, - headers={"Content-Disposition": f"attachment; filename={file.name}"} - ) + response.headers.set("Content-Type", content_type) + return response except Exception as e: return server_error_response(e) -@router.post('/mv') -async def move( - req: MoveFileRequest, - current_user = Depends(get_current_user) -): +@manager.route("/mv", methods=["POST"]) # noqa: F821 +@login_required +@validate_request("src_file_ids", "dest_file_id") +def move(): + req = request.json try: - file_ids = req.src_file_ids - parent_id = req.dest_file_id + file_ids = req["src_file_ids"] + dest_parent_id = req["dest_file_id"] + + ok, dest_folder = FileService.get_by_id(dest_parent_id) + if not ok or not dest_folder: + return get_data_error_result(message="Parent Folder not found!") + files = FileService.get_by_ids(file_ids) - files_dict = {} - for file in files: - files_dict[file.id] = file + if not files: + return get_data_error_result(message="Source files not found!") + + files_dict = {f.id: f for f in files} for file_id in file_ids: - file = files_dict[file_id] + file = files_dict.get(file_id) if not file: return get_data_error_result(message="File or Folder not found!") if not file.tenant_id: return get_data_error_result(message="Tenant not found!") if not check_file_team_permission(file, current_user.id): - return get_json_result(data=False, message='No authorization.', code=settings.RetCode.AUTHENTICATION_ERROR) - fe, _ = FileService.get_by_id(parent_id) - if not fe: - return get_data_error_result(message="Parent Folder not found!") - FileService.move_file(file_ids, parent_id) + return get_json_result( + data=False, + message="No authorization.", + code=settings.RetCode.AUTHENTICATION_ERROR, + ) + + def _move_entry_recursive(source_file_entry, dest_folder): + if source_file_entry.type == FileType.FOLDER.value: + existing_folder = FileService.query(name=source_file_entry.name, parent_id=dest_folder.id) + if existing_folder: + new_folder = existing_folder[0] + else: + new_folder = FileService.insert( + { + "id": get_uuid(), + "parent_id": dest_folder.id, + "tenant_id": source_file_entry.tenant_id, + "created_by": current_user.id, + "name": source_file_entry.name, + "location": "", + "size": 0, + "type": FileType.FOLDER.value, + } + ) + + sub_files = FileService.list_all_files_by_parent_id(source_file_entry.id) + for sub_file in sub_files: + _move_entry_recursive(sub_file, new_folder) + + FileService.delete_by_id(source_file_entry.id) + return + + old_parent_id = source_file_entry.parent_id + old_location = source_file_entry.location + filename = source_file_entry.name + + new_location = filename + while STORAGE_IMPL.obj_exist(dest_folder.id, new_location): + new_location += "_" + + try: + STORAGE_IMPL.move(old_parent_id, old_location, dest_folder.id, new_location) + except Exception as storage_err: + raise RuntimeError(f"Move file failed at storage layer: {str(storage_err)}") + + FileService.update_by_id( + source_file_entry.id, + { + "parent_id": dest_folder.id, + "location": new_location, + }, + ) + + for file in files: + _move_entry_recursive(file, dest_folder) + return get_json_result(data=True) + except Exception as e: return server_error_response(e) diff --git a/api/apps/kb_app.py b/api/apps/kb_app.py index ae8f33d..355a0f5 100644 --- a/api/apps/kb_app.py +++ b/api/apps/kb_app.py @@ -3,7 +3,7 @@ # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. -# You may obtain a copy of the License at +# you may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # @@ -15,27 +15,29 @@ # import json import logging -from typing import Optional, List +from typing import Optional -from fastapi import APIRouter, Depends, HTTPException, Query, status -from fastapi.responses import JSONResponse +from fastapi import APIRouter, Depends, Query -from api.models.kb_models import ( +from api.apps.models.auth_dependencies import get_current_user +from api.apps.models.kb_models import ( CreateKnowledgeBaseRequest, UpdateKnowledgeBaseRequest, DeleteKnowledgeBaseRequest, - ListKnowledgeBasesRequest, + ListKnowledgeBasesQuery, + ListKnowledgeBasesBody, RemoveTagsRequest, RenameTagRequest, - RunGraphRAGRequest, + ListPipelineLogsQuery, + ListPipelineLogsBody, + ListPipelineDatasetLogsQuery, + ListPipelineDatasetLogsBody, + DeletePipelineLogsQuery, + DeletePipelineLogsBody, + RunGraphragRequest, RunRaptorRequest, RunMindmapRequest, - ListPipelineLogsRequest, - ListPipelineDatasetLogsRequest, - DeletePipelineLogsRequest, - UnbindTaskRequest ) -from api.utils.api_utils import get_current_user from api.db.services import duplicate_name from api.db.services.document_service import DocumentService, queue_raptor_o_graphrag_tasks @@ -44,7 +46,12 @@ from api.db.services.file_service import FileService from api.db.services.pipeline_operation_log_service import PipelineOperationLogService from api.db.services.task_service import TaskService, GRAPH_RAPTOR_FAKE_DOC_ID from api.db.services.user_service import TenantService, UserTenantService -from api.utils.api_utils import get_error_data_result, server_error_response, get_data_error_result, get_json_result +from api.utils.api_utils import ( + get_error_data_result, + server_error_response, + get_data_error_result, + get_json_result, +) from api.utils import get_uuid from api.db import PipelineTaskType, StatusEnum, FileSource, VALID_FILE_TYPES, VALID_TASK_STATUS from api.db.services.knowledgebase_service import KnowledgebaseService @@ -53,9 +60,10 @@ from api import settings from rag.nlp import search from api.constants import DATASET_NAME_LIMIT from rag.settings import PAGERANK_FLD +from rag.utils.redis_conn import REDIS_CONN from rag.utils.storage_factory import STORAGE_IMPL -# 创建 FastAPI 路由器 +# 创建路由器 router = APIRouter() @@ -64,7 +72,14 @@ async def create( request: CreateKnowledgeBaseRequest, current_user = Depends(get_current_user) ): - dataset_name = request.name + """创建知识库 + + 支持两种解析类型: + - parse_type=1: 使用内置解析器,需要 parser_id + - parse_type=2: 使用自定义 pipeline,需要 pipeline_id + """ + req = request.model_dump(exclude_unset=True) + dataset_name = req["name"] if not isinstance(dataset_name, str): return get_data_error_result(message="Dataset name must be string.") if dataset_name.strip() == "": @@ -80,55 +95,66 @@ async def create( tenant_id=current_user.id, status=StatusEnum.VALID.value) try: - req = { - "id": get_uuid(), - "name": dataset_name, - "tenant_id": current_user.id, - "created_by": current_user.id, - "parser_id": request.parser_id or "naive", - "description": request.description - } + # 根据 parse_type 处理 parser_id 和 pipeline_id + parse_type = req.pop("parse_type", 1) # 移除 parse_type,不需要存储到数据库 + + if parse_type == 1: + # 使用内置解析器,需要 parser_id + # 验证器已经确保 parser_id 不为空,但保留默认值逻辑以防万一 + if not req.get("parser_id") or req["parser_id"].strip() == "": + req["parser_id"] = "naive" + # 清空 pipeline_id(设置为 None,数据库字段允许为 null) + req["pipeline_id"] = None + elif parse_type == 2: + # 使用自定义 pipeline,需要 pipeline_id + # 验证器已经确保 pipeline_id 不为空 + # parser_id 应该为空字符串,但数据库字段不允许 null,所以不设置 parser_id + # 让数据库使用默认值 "naive"(虽然用户传入的是空字符串,但数据库会处理) + # 如果用户明确传入了空字符串,我们也不设置它,让数据库使用默认值 + if "parser_id" in req and (not req["parser_id"] or req["parser_id"].strip() == ""): + # 移除空字符串的 parser_id,让数据库使用默认值 + req.pop("parser_id") + # pipeline_id 保留在 req 中,会被保存到数据库 + + req["id"] = get_uuid() + req["name"] = dataset_name + req["tenant_id"] = current_user.id + req["created_by"] = current_user.id + + # embd_id 已经在模型中定义为必需字段,直接使用 + e, t = TenantService.get_by_id(current_user.id) if not e: return get_data_error_result(message="Tenant not found.") - - # 设置 embd_id 默认值 - if not request.embd_id: - req["embd_id"] = t.embd_id - else: - req["embd_id"] = request.embd_id - - if request.parser_config: - req["parser_config"] = request.parser_config - else: - req["parser_config"] = { - "layout_recognize": "DeepDOC", - "chunk_token_num": 512, - "delimiter": "\n", - "auto_keywords": 0, - "auto_questions": 0, - "html4excel": False, - "topn_tags": 3, - "raptor": { - "use_raptor": True, - "prompt": "Please summarize the following paragraphs. Be careful with the numbers, do not make things up. Paragraphs as following:\n {cluster_content}\nThe above is the content you need to summarize.", - "max_token": 256, - "threshold": 0.1, - "max_cluster": 64, - "random_seed": 0 - }, - "graphrag": { - "use_graphrag": True, - "entity_types": [ - "organization", - "person", - "geo", - "event", - "category" - ], - "method": "light" - } + + req["parser_config"] = { + "layout_recognize": "DeepDOC", + "chunk_token_num": 512, + "delimiter": "\n", + "auto_keywords": 0, + "auto_questions": 0, + "html4excel": False, + "topn_tags": 3, + "raptor": { + "use_raptor": True, + "prompt": "Please summarize the following paragraphs. Be careful with the numbers, do not make things up. Paragraphs as following:\n {cluster_content}\nThe above is the content you need to summarize.", + "max_token": 256, + "threshold": 0.1, + "max_cluster": 64, + "random_seed": 0 + }, + "graphrag": { + "use_graphrag": True, + "entity_types": [ + "organization", + "person", + "geo", + "event", + "category" + ], + "method": "light" } + } if not KnowledgebaseService.save(**req): return get_data_error_result() return get_json_result(data={"kb_id": req["id"]}) @@ -141,16 +167,24 @@ async def update( request: UpdateKnowledgeBaseRequest, current_user = Depends(get_current_user) ): - if not isinstance(request.name, str): + """更新知识库""" + req = request.model_dump(exclude_unset=True) + if not isinstance(req["name"], str): return get_data_error_result(message="Dataset name must be string.") - if request.name.strip() == "": + if req["name"].strip() == "": return get_data_error_result(message="Dataset name can't be empty.") - if len(request.name.encode("utf-8")) > DATASET_NAME_LIMIT: + if len(req["name"].encode("utf-8")) > DATASET_NAME_LIMIT: return get_data_error_result( - message=f"Dataset name length is {len(request.name)} which is large than {DATASET_NAME_LIMIT}") - name = request.name.strip() + message=f"Dataset name length is {len(req['name'])} which is large than {DATASET_NAME_LIMIT}") + req["name"] = req["name"].strip() - if not KnowledgebaseService.accessible4deletion(request.kb_id, current_user.id): + # 验证不允许的参数 + not_allowed = ["id", "tenant_id", "created_by", "create_time", "update_time", "create_date", "update_date"] + for key in not_allowed: + if key in req: + del req[key] + + if not KnowledgebaseService.accessible4deletion(req["kb_id"], current_user.id): return get_json_result( data=False, message='No authorization.', @@ -158,48 +192,29 @@ async def update( ) try: if not KnowledgebaseService.query( - created_by=current_user.id, id=request.kb_id): + created_by=current_user.id, id=req["kb_id"]): return get_json_result( data=False, message='Only owner of knowledgebase authorized for this operation.', code=settings.RetCode.OPERATING_ERROR) - e, kb = KnowledgebaseService.get_by_id(request.kb_id) + e, kb = KnowledgebaseService.get_by_id(req["kb_id"]) if not e: return get_data_error_result( message="Can't find this knowledgebase!") - if name.lower() != kb.name.lower() \ + if req["name"].lower() != kb.name.lower() \ and len( - KnowledgebaseService.query(name=name, tenant_id=current_user.id, status=StatusEnum.VALID.value)) >= 1: + KnowledgebaseService.query(name=req["name"], tenant_id=current_user.id, status=StatusEnum.VALID.value)) >= 1: return get_data_error_result( message="Duplicated knowledgebase name.") - # 构建更新数据,包含所有可更新的字段 - update_data = { - "name": name, - "pagerank": request.pagerank - } - - # 添加可选字段(如果提供了的话) - if request.description is not None: - update_data["description"] = request.description - if request.permission is not None: - update_data["permission"] = request.permission - if request.avatar is not None: - update_data["avatar"] = request.avatar - if request.parser_id is not None: - update_data["parser_id"] = request.parser_id - if request.embd_id is not None: - update_data["embd_id"] = request.embd_id - if request.parser_config is not None: - update_data["parser_config"] = request.parser_config - - if not KnowledgebaseService.update_by_id(kb.id, update_data): + kb_id = req.pop("kb_id") + if not KnowledgebaseService.update_by_id(kb.id, req): return get_data_error_result() - if kb.pagerank != request.pagerank: - if request.pagerank > 0: - settings.docStoreConn.update({"kb_id": kb.id}, {PAGERANK_FLD: request.pagerank}, + if kb.pagerank != req.get("pagerank", 0): + if req.get("pagerank", 0) > 0: + settings.docStoreConn.update({"kb_id": kb.id}, {PAGERANK_FLD: req["pagerank"]}, search.index_name(kb.tenant_id), kb.id) else: # Elasticsearch requires PAGERANK_FLD be non-zero! @@ -211,26 +226,7 @@ async def update( return get_data_error_result( message="Database error (Knowledgebase rename)!") kb = kb.to_dict() - - # 使用完整的请求数据更新返回结果,保持与原来代码的一致性 - request_data = { - "name": name, - "pagerank": request.pagerank - } - if request.description is not None: - request_data["description"] = request.description - if request.permission is not None: - request_data["permission"] = request.permission - if request.avatar is not None: - request_data["avatar"] = request.avatar - if request.parser_id is not None: - request_data["parser_id"] = request.parser_id - if request.embd_id is not None: - request_data["embd_id"] = request.embd_id - if request.parser_config is not None: - request_data["parser_config"] = request.parser_config - - kb.update(request_data) + kb.update(req) return get_json_result(data=kb) except Exception as e: @@ -242,6 +238,7 @@ async def detail( kb_id: str = Query(..., description="知识库ID"), current_user = Depends(get_current_user) ): + """获取知识库详情""" try: tenants = UserTenantService.query(user_id=current_user.id) for tenant in tenants: @@ -257,6 +254,9 @@ async def detail( return get_data_error_result( message="Can't find this knowledgebase!") kb["size"] = DocumentService.get_total_size_by_kb_id(kb_id=kb["id"],keywords="", run_status=[], types=[]) + for key in ["graphrag_task_finish_at", "raptor_task_finish_at", "mindmap_task_finish_at"]: + if finish_at := kb.get(key): + kb[key] = finish_at.strftime("%Y-%m-%d %H:%M:%S") return get_json_result(data=kb) except Exception as e: return server_error_response(e) @@ -264,18 +264,22 @@ async def detail( @router.post('/list') async def list_kbs( - request: ListKnowledgeBasesRequest, - keywords: str = Query("", description="关键词"), - page: int = Query(0, description="页码"), - page_size: int = Query(0, description="每页大小"), - parser_id: Optional[str] = Query(None, description="解析器ID"), - orderby: str = Query("create_time", description="排序字段"), - desc: bool = Query(True, description="是否降序"), + query: ListKnowledgeBasesQuery = Depends(), + body: Optional[ListKnowledgeBasesBody] = None, current_user = Depends(get_current_user) ): - page_number = page - items_per_page = page_size - owner_ids = request.owner_ids + """列出知识库""" + if body is None: + body = ListKnowledgeBasesBody() + + keywords = query.keywords or "" + page_number = int(query.page or 0) + items_per_page = int(query.page_size or 0) + parser_id = query.parser_id + orderby = query.orderby or "create_time" + desc = query.desc.lower() == "true" if query.desc else True + + owner_ids = body.owner_ids or [] if body else [] try: if not owner_ids: tenants = TenantService.get_joined_tenants_by_user_id(current_user.id) @@ -296,11 +300,13 @@ async def list_kbs( except Exception as e: return server_error_response(e) + @router.post('/rm') async def rm( request: DeleteKnowledgeBaseRequest, current_user = Depends(get_current_user) ): + """删除知识库""" if not KnowledgebaseService.accessible4deletion(request.kb_id, current_user.id): return get_json_result( data=False, @@ -343,6 +349,7 @@ async def list_tags( kb_id: str, current_user = Depends(get_current_user) ): + """列出知识库标签""" if not KnowledgebaseService.accessible(kb_id, current_user.id): return get_json_result( data=False, @@ -353,17 +360,18 @@ async def list_tags( tenants = UserTenantService.get_tenants_by_user_id(current_user.id) tags = [] for tenant in tenants: - tags += settings.retrievaler.all_tags(tenant["tenant_id"], [kb_id]) + tags += settings.retriever.all_tags(tenant["tenant_id"], [kb_id]) return get_json_result(data=tags) @router.get('/tags') async def list_tags_from_kbs( - kb_ids: str = Query(..., description="知识库ID列表,用逗号分隔"), + kb_ids: str = Query(..., description="知识库ID列表,逗号分隔"), current_user = Depends(get_current_user) ): - kb_ids = kb_ids.split(",") - for kb_id in kb_ids: + """从多个知识库列出标签""" + kb_id_list = kb_ids.split(",") + for kb_id in kb_id_list: if not KnowledgebaseService.accessible(kb_id, current_user.id): return get_json_result( data=False, @@ -374,7 +382,7 @@ async def list_tags_from_kbs( tenants = UserTenantService.get_tenants_by_user_id(current_user.id) tags = [] for tenant in tenants: - tags += settings.retrievaler.all_tags(tenant["tenant_id"], kb_ids) + tags += settings.retriever.all_tags(tenant["tenant_id"], kb_id_list) return get_json_result(data=tags) @@ -384,6 +392,7 @@ async def rm_tags( request: RemoveTagsRequest, current_user = Depends(get_current_user) ): + """删除知识库标签""" if not KnowledgebaseService.accessible(kb_id, current_user.id): return get_json_result( data=False, @@ -406,6 +415,7 @@ async def rename_tags( request: RenameTagRequest, current_user = Depends(get_current_user) ): + """重命名知识库标签""" if not KnowledgebaseService.accessible(kb_id, current_user.id): return get_json_result( data=False, @@ -426,6 +436,7 @@ async def knowledge_graph( kb_id: str, current_user = Depends(get_current_user) ): + """获取知识图谱""" if not KnowledgebaseService.accessible(kb_id, current_user.id): return get_json_result( data=False, @@ -441,7 +452,7 @@ async def knowledge_graph( obj = {"graph": {}, "mind_map": {}} if not settings.docStoreConn.indexExist(search.index_name(kb.tenant_id), kb_id): return get_json_result(data=obj) - sres = settings.retrievaler.search(req, search.index_name(kb.tenant_id), [kb_id]) + sres = settings.retriever.search(req, search.index_name(kb.tenant_id), [kb_id]) if not len(sres.ids): return get_json_result(data=obj) @@ -468,6 +479,7 @@ async def delete_knowledge_graph( kb_id: str, current_user = Depends(get_current_user) ): + """删除知识图谱""" if not KnowledgebaseService.accessible(kb_id, current_user.id): return get_json_result( data=False, @@ -482,18 +494,19 @@ async def delete_knowledge_graph( @router.get("/get_meta") async def get_meta( - kb_ids: str = Query(..., description="知识库ID列表,用逗号分隔"), + kb_ids: str = Query(..., description="知识库ID列表,逗号分隔"), current_user = Depends(get_current_user) ): - kb_ids = kb_ids.split(",") - for kb_id in kb_ids: + """获取知识库元数据""" + kb_id_list = kb_ids.split(",") + for kb_id in kb_id_list: if not KnowledgebaseService.accessible(kb_id, current_user.id): return get_json_result( data=False, message='No authorization.', code=settings.RetCode.AUTHENTICATION_ERROR ) - return get_json_result(data=DocumentService.get_meta_by_kbs(kb_ids)) + return get_json_result(data=DocumentService.get_meta_by_kbs(kb_id_list)) @router.get("/basic_info") @@ -501,6 +514,7 @@ async def get_basic_info( kb_id: str = Query(..., description="知识库ID"), current_user = Depends(get_current_user) ): + """获取知识库基本信息""" if not KnowledgebaseService.accessible(kb_id, current_user.id): return get_json_result( data=False, @@ -515,42 +529,45 @@ async def get_basic_info( @router.post("/list_pipeline_logs") async def list_pipeline_logs( - request: ListPipelineLogsRequest, - kb_id: str = Query(..., description="知识库ID"), - keywords: str = Query("", description="关键词"), - page: int = Query(0, description="页码"), - page_size: int = Query(0, description="每页大小"), - orderby: str = Query("create_time", description="排序字段"), - desc: bool = Query(True, description="是否降序"), - create_date_from: str = Query("", description="创建日期开始"), - create_date_to: str = Query("", description="创建日期结束"), + query: ListPipelineLogsQuery = Depends(), + body: Optional[ListPipelineLogsBody] = None, current_user = Depends(get_current_user) ): - if not kb_id: + """列出流水线日志""" + if not query.kb_id: return get_json_result(data=False, message='Lack of "KB ID"', code=settings.RetCode.ARGUMENT_ERROR) - page_number = page - items_per_page = page_size - + if body is None: + body = ListPipelineLogsBody() + + keywords = query.keywords or "" + page_number = int(query.page or 0) + items_per_page = int(query.page_size or 0) + orderby = query.orderby or "create_time" + desc = query.desc.lower() == "true" if query.desc else True + create_date_from = query.create_date_from or "" + create_date_to = query.create_date_to or "" if create_date_to > create_date_from: return get_data_error_result(message="Create data filter is abnormal.") - operation_status = request.operation_status + operation_status = body.operation_status or [] if operation_status: invalid_status = {s for s in operation_status if s not in VALID_TASK_STATUS} if invalid_status: return get_data_error_result(message=f"Invalid filter operation_status status conditions: {', '.join(invalid_status)}") - types = request.types + types = body.types or [] if types: invalid_types = {t for t in types if t not in VALID_FILE_TYPES} if invalid_types: return get_data_error_result(message=f"Invalid filter conditions: {', '.join(invalid_types)} type{'s' if len(invalid_types) > 1 else ''}") - suffix = request.suffix + suffix = body.suffix or [] try: - logs, tol = PipelineOperationLogService.get_file_logs_by_kb_id(kb_id, page_number, items_per_page, orderby, desc, keywords, operation_status, types, suffix, create_date_from, create_date_to) + logs, tol = PipelineOperationLogService.get_file_logs_by_kb_id( + query.kb_id, page_number, items_per_page, orderby, desc, keywords, + operation_status, types, suffix, create_date_from, create_date_to) return get_json_result(data={"total": tol, "logs": logs}) except Exception as e: return server_error_response(e) @@ -558,33 +575,36 @@ async def list_pipeline_logs( @router.post("/list_pipeline_dataset_logs") async def list_pipeline_dataset_logs( - request: ListPipelineDatasetLogsRequest, - kb_id: str = Query(..., description="知识库ID"), - page: int = Query(0, description="页码"), - page_size: int = Query(0, description="每页大小"), - orderby: str = Query("create_time", description="排序字段"), - desc: bool = Query(True, description="是否降序"), - create_date_from: str = Query("", description="创建日期开始"), - create_date_to: str = Query("", description="创建日期结束"), + query: ListPipelineDatasetLogsQuery = Depends(), + body: Optional[ListPipelineDatasetLogsBody] = None, current_user = Depends(get_current_user) ): - if not kb_id: + """列出流水线数据集日志""" + if not query.kb_id: return get_json_result(data=False, message='Lack of "KB ID"', code=settings.RetCode.ARGUMENT_ERROR) - page_number = page - items_per_page = page_size - + if body is None: + body = ListPipelineDatasetLogsBody() + + page_number = int(query.page or 0) + items_per_page = int(query.page_size or 0) + orderby = query.orderby or "create_time" + desc = query.desc.lower() == "true" if query.desc else True + create_date_from = query.create_date_from or "" + create_date_to = query.create_date_to or "" if create_date_to > create_date_from: return get_data_error_result(message="Create data filter is abnormal.") - operation_status = request.operation_status + operation_status = body.operation_status or [] if operation_status: invalid_status = {s for s in operation_status if s not in VALID_TASK_STATUS} if invalid_status: return get_data_error_result(message=f"Invalid filter operation_status status conditions: {', '.join(invalid_status)}") try: - logs, tol = PipelineOperationLogService.get_dataset_logs_by_kb_id(kb_id, page_number, items_per_page, orderby, desc, operation_status, create_date_from, create_date_to) + logs, tol = PipelineOperationLogService.get_dataset_logs_by_kb_id( + query.kb_id, page_number, items_per_page, orderby, desc, + operation_status, create_date_from, create_date_to) return get_json_result(data={"total": tol, "logs": logs}) except Exception as e: return server_error_response(e) @@ -592,14 +612,18 @@ async def list_pipeline_dataset_logs( @router.post("/delete_pipeline_logs") async def delete_pipeline_logs( - request: DeletePipelineLogsRequest, - kb_id: str = Query(..., description="知识库ID"), + query: DeletePipelineLogsQuery = Depends(), + body: Optional[DeletePipelineLogsBody] = None, current_user = Depends(get_current_user) ): - if not kb_id: + """删除流水线日志""" + if not query.kb_id: return get_json_result(data=False, message='Lack of "KB ID"', code=settings.RetCode.ARGUMENT_ERROR) - log_ids = request.log_ids + if body is None: + body = DeletePipelineLogsBody(log_ids=[]) + + log_ids = body.log_ids or [] PipelineOperationLogService.delete_by_ids(log_ids) @@ -608,9 +632,10 @@ async def delete_pipeline_logs( @router.get("/pipeline_log_detail") async def pipeline_log_detail( - log_id: str = Query(..., description="日志ID"), + log_id: str = Query(..., description="流水线日志ID"), current_user = Depends(get_current_user) ): + """获取流水线日志详情""" if not log_id: return get_json_result(data=False, message='Lack of "Pipeline log ID"', code=settings.RetCode.ARGUMENT_ERROR) @@ -623,9 +648,10 @@ async def pipeline_log_detail( @router.post("/run_graphrag") async def run_graphrag( - request: RunGraphRAGRequest, + request: RunGraphragRequest, current_user = Depends(get_current_user) ): + """运行 GraphRAG""" kb_id = request.kb_id if not kb_id: return get_error_data_result(message='Lack of "KB ID"') @@ -660,7 +686,7 @@ async def run_graphrag( sample_document = documents[0] document_ids = [document["id"] for document in documents] - task_id = queue_raptor_o_graphrag_tasks(doc=sample_document, ty="graphrag", priority=0, fake_doc_id=GRAPH_RAPTOR_FAKE_DOC_ID, doc_ids=list(document_ids)) + task_id = queue_raptor_o_graphrag_tasks(sample_doc_id=sample_document, ty="graphrag", priority=0, fake_doc_id=GRAPH_RAPTOR_FAKE_DOC_ID, doc_ids=list(document_ids)) if not KnowledgebaseService.update_by_id(kb.id, {"graphrag_task_id": task_id}): logging.warning(f"Cannot save graphrag_task_id for kb {kb_id}") @@ -673,6 +699,7 @@ async def trace_graphrag( kb_id: str = Query(..., description="知识库ID"), current_user = Depends(get_current_user) ): + """追踪 GraphRAG 任务""" if not kb_id: return get_error_data_result(message='Lack of "KB ID"') @@ -696,6 +723,7 @@ async def run_raptor( request: RunRaptorRequest, current_user = Depends(get_current_user) ): + """运行 RAPTOR""" kb_id = request.kb_id if not kb_id: return get_error_data_result(message='Lack of "KB ID"') @@ -730,7 +758,7 @@ async def run_raptor( sample_document = documents[0] document_ids = [document["id"] for document in documents] - task_id = queue_raptor_o_graphrag_tasks(doc=sample_document, ty="raptor", priority=0, fake_doc_id=GRAPH_RAPTOR_FAKE_DOC_ID, doc_ids=list(document_ids)) + task_id = queue_raptor_o_graphrag_tasks(sample_doc_id=sample_document, ty="raptor", priority=0, fake_doc_id=GRAPH_RAPTOR_FAKE_DOC_ID, doc_ids=list(document_ids)) if not KnowledgebaseService.update_by_id(kb.id, {"raptor_task_id": task_id}): logging.warning(f"Cannot save raptor_task_id for kb {kb_id}") @@ -743,6 +771,7 @@ async def trace_raptor( kb_id: str = Query(..., description="知识库ID"), current_user = Depends(get_current_user) ): + """追踪 RAPTOR 任务""" if not kb_id: return get_error_data_result(message='Lack of "KB ID"') @@ -766,6 +795,7 @@ async def run_mindmap( request: RunMindmapRequest, current_user = Depends(get_current_user) ): + """运行 Mindmap""" kb_id = request.kb_id if not kb_id: return get_error_data_result(message='Lack of "KB ID"') @@ -800,7 +830,7 @@ async def run_mindmap( sample_document = documents[0] document_ids = [document["id"] for document in documents] - task_id = queue_raptor_o_graphrag_tasks(doc=sample_document, ty="mindmap", priority=0, fake_doc_id=GRAPH_RAPTOR_FAKE_DOC_ID, doc_ids=list(document_ids)) + task_id = queue_raptor_o_graphrag_tasks(sample_doc_id=sample_document, ty="mindmap", priority=0, fake_doc_id=GRAPH_RAPTOR_FAKE_DOC_ID, doc_ids=list(document_ids)) if not KnowledgebaseService.update_by_id(kb.id, {"mindmap_task_id": task_id}): logging.warning(f"Cannot save mindmap_task_id for kb {kb_id}") @@ -813,6 +843,7 @@ async def trace_mindmap( kb_id: str = Query(..., description="知识库ID"), current_user = Depends(get_current_user) ): + """追踪 Mindmap 任务""" if not kb_id: return get_error_data_result(message='Lack of "KB ID"') @@ -834,33 +865,43 @@ async def trace_mindmap( @router.delete("/unbind_task") async def delete_kb_task( kb_id: str = Query(..., description="知识库ID"), - pipeline_task_type: str = Query(..., description="管道任务类型"), + pipeline_task_type: str = Query(..., description="流水线任务类型"), current_user = Depends(get_current_user) ): + """解绑任务""" if not kb_id: return get_error_data_result(message='Lack of "KB ID"') ok, kb = KnowledgebaseService.get_by_id(kb_id) if not ok: return get_json_result(data=True) + if not pipeline_task_type or pipeline_task_type not in [PipelineTaskType.GRAPH_RAG, PipelineTaskType.RAPTOR, PipelineTaskType.MINDMAP]: return get_error_data_result(message="Invalid task type") match pipeline_task_type: case PipelineTaskType.GRAPH_RAG: settings.docStoreConn.delete({"knowledge_graph_kwd": ["graph", "subgraph", "entity", "relation"]}, search.index_name(kb.tenant_id), kb_id) - kb_task_id = "graphrag_task_id" + kb_task_id_field = "graphrag_task_id" + task_id = kb.graphrag_task_id kb_task_finish_at = "graphrag_task_finish_at" case PipelineTaskType.RAPTOR: - kb_task_id = "raptor_task_id" + kb_task_id_field = "raptor_task_id" + task_id = kb.raptor_task_id kb_task_finish_at = "raptor_task_finish_at" case PipelineTaskType.MINDMAP: - kb_task_id = "mindmap_task_id" + kb_task_id_field = "mindmap_task_id" + task_id = kb.mindmap_task_id kb_task_finish_at = "mindmap_task_finish_at" case _: return get_error_data_result(message="Internal Error: Invalid task type") - ok = KnowledgebaseService.update_by_id(kb_id, {kb_task_id: "", kb_task_finish_at: None}) + def cancel_task(task_id): + REDIS_CONN.set(f"{task_id}-cancel", "x") + cancel_task(task_id) + + ok = KnowledgebaseService.update_by_id(kb_id, {kb_task_id_field: "", kb_task_finish_at: None}) if not ok: return server_error_response(f"Internal error: cannot delete task {pipeline_task_type}") return get_json_result(data=True) + diff --git a/api/apps/llm_app.py b/api/apps/llm_app.py index a332345..81dee11 100644 --- a/api/apps/llm_app.py +++ b/api/apps/llm_app.py @@ -15,26 +15,36 @@ # import logging import json -from typing import Optional from fastapi import APIRouter, Depends, Query -from fastapi.responses import JSONResponse + +from api.apps.models.auth_dependencies import get_current_user +from api.apps.models.llm_models import ( + SetApiKeyRequest, + AddLLMRequest, + DeleteLLMRequest, + DeleteFactoryRequest, + MyLLMsQuery, + ListLLMsQuery, +) from api.db.services.tenant_llm_service import LLMFactoriesService, TenantLLMService from api.db.services.llm_service import LLMService from api import settings -from api.utils.api_utils import server_error_response, get_data_error_result, get_json_result +from api.utils.api_utils import server_error_response, get_data_error_result from api.db import StatusEnum, LLMType from api.db.db_models import TenantLLM +from api.utils.api_utils import get_json_result from api.utils.base64_image import test_image from rag.llm import EmbeddingModel, ChatModel, RerankModel, CvModel, TTSModel -from api.models.llm_models import SetApiKeyRequest, AddLLMRequest, DeleteLLMRequest, DeleteFactoryRequest -from api.utils.api_utils import get_current_user -# 创建 FastAPI 路由器 +# 创建路由器 router = APIRouter() @router.get('/factories') -async def factories(current_user = Depends(get_current_user)): +async def factories( + current_user = Depends(get_current_user) +): + """获取 LLM 工厂列表""" try: fac = LLMFactoriesService.get_all() fac = [f.to_dict() for f in fac if f.name not in ["Youdao", "FastEmbed", "BAAI"]] @@ -55,17 +65,22 @@ async def factories(current_user = Depends(get_current_user)): @router.post('/set_api_key') -async def set_api_key(request: SetApiKeyRequest, current_user = Depends(get_current_user)): +async def set_api_key( + request: SetApiKeyRequest, + current_user = Depends(get_current_user) +): + """设置 API Key""" + req = request.model_dump(exclude_unset=True) # test if api key works chat_passed, embd_passed, rerank_passed = False, False, False - factory = request.llm_factory + factory = req["llm_factory"] extra = {"provider": factory} msg = "" for llm in LLMService.query(fid=factory): if not embd_passed and llm.model_type == LLMType.EMBEDDING.value: assert factory in EmbeddingModel, f"Embedding model from {factory} is not supported yet." mdl = EmbeddingModel[factory]( - request.api_key, llm.llm_name, base_url=request.base_url) + req["api_key"], llm.llm_name, base_url=req.get("base_url", "")) try: arr, tc = mdl.encode(["Test if the api key is available"]) if len(arr[0]) == 0: @@ -76,7 +91,7 @@ async def set_api_key(request: SetApiKeyRequest, current_user = Depends(get_curr elif not chat_passed and llm.model_type == LLMType.CHAT.value: assert factory in ChatModel, f"Chat model from {factory} is not supported yet." mdl = ChatModel[factory]( - request.api_key, llm.llm_name, base_url=request.base_url, **extra) + req["api_key"], llm.llm_name, base_url=req.get("base_url", ""), **extra) try: m, tc = mdl.chat(None, [{"role": "user", "content": "Hello! How are you doing!"}], {"temperature": 0.9, 'max_tokens': 50}) @@ -89,7 +104,7 @@ async def set_api_key(request: SetApiKeyRequest, current_user = Depends(get_curr elif not rerank_passed and llm.model_type == LLMType.RERANK: assert factory in RerankModel, f"Re-rank model from {factory} is not supported yet." mdl = RerankModel[factory]( - request.api_key, llm.llm_name, base_url=request.base_url) + req["api_key"], llm.llm_name, base_url=req.get("base_url", "")) try: arr, tc = mdl.similarity("What's the weather?", ["Is it sunny today?"]) if len(arr) == 0 or tc == 0: @@ -107,9 +122,12 @@ async def set_api_key(request: SetApiKeyRequest, current_user = Depends(get_curr return get_data_error_result(message=msg) llm_config = { - "api_key": request.api_key, - "api_base": request.base_url or "" + "api_key": req["api_key"], + "api_base": req.get("base_url", "") } + for n in ["model_type", "llm_name"]: + if n in req: + llm_config[n] = req[n] for llm in LLMService.query(fid=factory): llm_config["max_tokens"]=llm.max_tokens @@ -132,14 +150,19 @@ async def set_api_key(request: SetApiKeyRequest, current_user = Depends(get_curr @router.post('/add_llm') -async def add_llm(request: AddLLMRequest, current_user = Depends(get_current_user)): - factory = request.llm_factory - api_key = request.api_key or "x" - llm_name = request.llm_name +async def add_llm( + request: AddLLMRequest, + current_user = Depends(get_current_user) +): + """添加 LLM""" + req = request.model_dump(exclude_unset=True) + factory = req["llm_factory"] + api_key = req.get("api_key", "x") + llm_name = req.get("llm_name") def apikey_json(keys): - nonlocal request - return json.dumps({k: getattr(request, k, "") for k in keys}) + nonlocal req + return json.dumps({k: req.get(k, "") for k in keys}) if factory == "VolcEngine": # For VolcEngine, due to its special authentication method @@ -147,21 +170,28 @@ async def add_llm(request: AddLLMRequest, current_user = Depends(get_current_use api_key = apikey_json(["ark_api_key", "endpoint_id"]) elif factory == "Tencent Hunyuan": - # Create a temporary request object for set_api_key - temp_request = SetApiKeyRequest( - llm_factory=factory, - api_key=apikey_json(["hunyuan_sid", "hunyuan_sk"]), - base_url=request.api_base + req["api_key"] = apikey_json(["hunyuan_sid", "hunyuan_sk"]) + # 创建 SetApiKeyRequest 并调用 set_api_key 逻辑 + set_api_key_req = SetApiKeyRequest( + llm_factory=req["llm_factory"], + api_key=req["api_key"], + base_url=req.get("api_base", req.get("base_url", "")), + model_type=req.get("model_type"), + llm_name=req.get("llm_name") ) - return await set_api_key(temp_request, current_user) + return await set_api_key(set_api_key_req, current_user) elif factory == "Tencent Cloud": - temp_request = SetApiKeyRequest( - llm_factory=factory, - api_key=apikey_json(["tencent_cloud_sid", "tencent_cloud_sk"]), - base_url=request.api_base + req["api_key"] = apikey_json(["tencent_cloud_sid", "tencent_cloud_sk"]) + # 创建 SetApiKeyRequest 并调用 set_api_key 逻辑 + set_api_key_req = SetApiKeyRequest( + llm_factory=req["llm_factory"], + api_key=req["api_key"], + base_url=req.get("api_base", req.get("base_url", "")), + model_type=req.get("model_type"), + llm_name=req.get("llm_name") ) - return await set_api_key(temp_request, current_user) + return await set_api_key(set_api_key_req, current_user) elif factory == "Bedrock": # For Bedrock, due to its special authentication method @@ -181,9 +211,9 @@ async def add_llm(request: AddLLMRequest, current_user = Depends(get_current_use llm_name += "___VLLM" elif factory == "XunFei Spark": - if request.model_type == "chat": - api_key = request.spark_api_password or "" - elif request.model_type == "tts": + if req["model_type"] == "chat": + api_key = req.get("spark_api_password", "") + elif req["model_type"] == "tts": api_key = apikey_json(["spark_app_id", "spark_api_secret", "spark_api_key"]) elif factory == "BaiduYiyan": @@ -198,14 +228,17 @@ async def add_llm(request: AddLLMRequest, current_user = Depends(get_current_use elif factory == "Azure-OpenAI": api_key = apikey_json(["api_key", "api_version"]) + elif factory == "OpenRouter": + api_key = apikey_json(["api_key", "provider_order"]) + llm = { "tenant_id": current_user.id, "llm_factory": factory, - "model_type": request.model_type, + "model_type": req["model_type"], "llm_name": llm_name, - "api_base": request.api_base or "", + "api_base": req.get("api_base", ""), "api_key": api_key, - "max_tokens": request.max_tokens + "max_tokens": req.get("max_tokens") } msg = "" @@ -295,7 +328,11 @@ async def add_llm(request: AddLLMRequest, current_user = Depends(get_current_use @router.post('/delete_llm') -async def delete_llm(request: DeleteLLMRequest, current_user = Depends(get_current_user)): +async def delete_llm( + request: DeleteLLMRequest, + current_user = Depends(get_current_user) +): + """删除 LLM""" TenantLLMService.filter_delete( [TenantLLM.tenant_id == current_user.id, TenantLLM.llm_factory == request.llm_factory, TenantLLM.llm_name == request.llm_name]) @@ -303,7 +340,11 @@ async def delete_llm(request: DeleteLLMRequest, current_user = Depends(get_curre @router.post('/delete_factory') -async def delete_factory(request: DeleteFactoryRequest, current_user = Depends(get_current_user)): +async def delete_factory( + request: DeleteFactoryRequest, + current_user = Depends(get_current_user) +): + """删除工厂""" TenantLLMService.filter_delete( [TenantLLM.tenant_id == current_user.id, TenantLLM.llm_factory == request.llm_factory]) return get_json_result(data=True) @@ -311,10 +352,13 @@ async def delete_factory(request: DeleteFactoryRequest, current_user = Depends(g @router.get('/my_llms') async def my_llms( - include_details: bool = Query(False, description="是否包含详细信息"), + query: MyLLMsQuery = Depends(), current_user = Depends(get_current_user) ): + """获取我的 LLMs""" try: + include_details = query.include_details.lower() == 'true' + if include_details: res = {} objs = TenantLLMService.query(tenant_id=current_user.id) @@ -362,11 +406,13 @@ async def my_llms( @router.get('/list') async def list_app( - model_type: Optional[str] = Query(None, description="模型类型"), + query: ListLLMsQuery = Depends(), current_user = Depends(get_current_user) ): + """列出 LLMs""" self_deployed = ["Youdao", "FastEmbed", "BAAI", "Ollama", "Xinference", "LocalAI", "LM-Studio", "GPUStack"] weighted = ["Youdao", "FastEmbed", "BAAI"] if settings.LIGHTEN != 0 else [] + model_type = query.model_type try: objs = TenantLLMService.query(tenant_id=current_user.id) facts = set([o.to_dict()["llm_factory"] for o in objs if o.api_key]) diff --git a/api/apps/mcp_server_app.py b/api/apps/mcp_server_app.py index 55e5e34..e017fc1 100644 --- a/api/apps/mcp_server_app.py +++ b/api/apps/mcp_server_app.py @@ -13,164 +13,61 @@ # See the License for the specific language governing permissions and # limitations under the License. # -from typing import List, Optional, Dict, Any -from fastapi import APIRouter, Depends, HTTPException, Query -from fastapi.security import HTTPAuthorizationCredentials -from api.utils.api_utils import security +from fastapi import APIRouter, Depends, Query + +from api.apps.models.auth_dependencies import get_current_user +from api.apps.models.mcp_models import ( + ListMCPServersQuery, + ListMCPServersBody, + CreateMCPServerRequest, + UpdateMCPServerRequest, + DeleteMCPServersRequest, + ImportMCPServersRequest, + ExportMCPServersRequest, + ListMCPToolsRequest, + TestMCPToolRequest, + CacheMCPToolsRequest, + TestMCPRequest, +) -from api import settings from api.db import VALID_MCP_SERVER_TYPES -from api.db.db_models import MCPServer, User +from api.db.db_models import MCPServer from api.db.services.mcp_server_service import MCPServerService from api.db.services.user_service import TenantService from api.settings import RetCode from api.utils import get_uuid from api.utils.api_utils import get_data_error_result, get_json_result, server_error_response, get_mcp_tools -from api.utils.web_utils import get_float, safe_json_parse +from api.utils.web_utils import safe_json_parse from rag.utils.mcp_tool_call_conn import MCPToolCallSession, close_multiple_mcp_toolcall_sessions -from pydantic import BaseModel -# Security - -# Pydantic models for request/response -class ListMCPRequest(BaseModel): - mcp_ids: List[str] = [] - -class CreateMCPRequest(BaseModel): - name: str - url: str - server_type: str - headers: Optional[Dict[str, Any]] = {} - variables: Optional[Dict[str, Any]] = {} - timeout: Optional[float] = 10 - -class UpdateMCPRequest(BaseModel): - mcp_id: str - name: Optional[str] = None - url: Optional[str] = None - server_type: Optional[str] = None - headers: Optional[Dict[str, Any]] = None - variables: Optional[Dict[str, Any]] = None - timeout: Optional[float] = 10 - -class RemoveMCPRequest(BaseModel): - mcp_ids: List[str] - -class ImportMCPRequest(BaseModel): - mcpServers: Dict[str, Dict[str, Any]] - timeout: Optional[float] = 10 - -class ExportMCPRequest(BaseModel): - mcp_ids: List[str] - - -class ListToolsRequest(BaseModel): - mcp_ids: List[str] - timeout: Optional[float] = 10 - - -class TestToolRequest(BaseModel): - mcp_id: str - tool_name: str - arguments: Dict[str, Any] - timeout: Optional[float] = 10 - - -class CacheToolsRequest(BaseModel): - mcp_id: str - tools: List[Dict[str, Any]] - - -class TestMCPRequest(BaseModel): - url: str - server_type: str - timeout: Optional[float] = 10 - headers: Optional[Dict[str, Any]] = {} - variables: Optional[Dict[str, Any]] = {} - -# Dependency injection -async def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(security)): - """获取当前用户""" - from api.db import StatusEnum - from api.db.services.user_service import UserService - from fastapi import HTTPException, status - import logging - - try: - from itsdangerous.url_safe import URLSafeTimedSerializer as Serializer - except ImportError: - # 如果没有itsdangerous,使用jwt作为替代 - import jwt - Serializer = jwt - - jwt = Serializer(secret_key=settings.SECRET_KEY) - authorization = credentials.credentials - - if authorization: - try: - access_token = str(jwt.loads(authorization)) - - if not access_token or not access_token.strip(): - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Authentication attempt with empty access token" - ) - - # Access tokens should be UUIDs (32 hex characters) - if len(access_token.strip()) < 32: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail=f"Authentication attempt with invalid token format: {len(access_token)} chars" - ) - - user = UserService.query( - access_token=access_token, status=StatusEnum.VALID.value - ) - if user: - if not user[0].access_token or not user[0].access_token.strip(): - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail=f"User {user[0].email} has empty access_token in database" - ) - return user[0] - else: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Invalid access token" - ) - except Exception as e: - logging.warning(f"load_user got exception {e}") - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Invalid access token" - ) - else: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Authorization header required" - ) - -# Create router +# 创建路由器 router = APIRouter() @router.post("/list") async def list_mcp( - request: ListMCPRequest, - keywords: str = Query("", description="Search keywords"), - page: int = Query(0, description="Page number"), - page_size: int = Query(0, description="Items per page"), - orderby: str = Query("create_time", description="Order by field"), - desc: bool = Query(True, description="Sort descending"), - current_user: User = Depends(get_current_user) + query: ListMCPServersQuery = Depends(), + body: ListMCPServersBody = None, + current_user = Depends(get_current_user) ): + """列出MCP服务器""" + if body is None: + body = ListMCPServersBody() + + keywords = query.keywords or "" + page_number = int(query.page or 0) + items_per_page = int(query.page_size or 0) + orderby = query.orderby or "create_time" + desc = query.desc.lower() == "true" if query.desc else True + + mcp_ids = body.mcp_ids or [] try: - servers = MCPServerService.get_servers(current_user.id, request.mcp_ids, 0, 0, orderby, desc, keywords) or [] + servers = MCPServerService.get_servers(current_user.id, mcp_ids, 0, 0, orderby, desc, keywords) or [] total = len(servers) - if page and page_size: - servers = servers[(page - 1) * page_size : page * page_size] + if page_number and items_per_page: + servers = servers[(page_number - 1) * items_per_page : page_number * items_per_page] return get_json_result(data={"mcp_servers": servers, "total": total}) except Exception as e: @@ -179,9 +76,10 @@ async def list_mcp( @router.get("/detail") async def detail( - mcp_id: str = Query(..., description="MCP server ID"), - current_user: User = Depends(get_current_user) + mcp_id: str = Query(..., description="MCP服务器ID"), + current_user = Depends(get_current_user) ): + """获取MCP服务器详情""" try: mcp_server = MCPServerService.get_or_none(id=mcp_id, tenant_id=current_user.id) @@ -195,9 +93,10 @@ async def detail( @router.post("/create") async def create( - request: CreateMCPRequest, - current_user: User = Depends(get_current_user) + request: CreateMCPServerRequest, + current_user = Depends(get_current_user) ): + """创建MCP服务器""" server_type = request.server_type if server_type not in VALID_MCP_SERVER_TYPES: return get_data_error_result(message="Unsupported MCP server type.") @@ -218,10 +117,10 @@ async def create( variables = safe_json_parse(request.variables or {}) variables.pop("tools", None) - timeout = request.timeout or 10 + timeout = request.timeout or 10.0 try: - req_data = { + req = { "id": get_uuid(), "tenant_id": current_user.id, "name": server_name, @@ -229,7 +128,6 @@ async def create( "server_type": server_type, "headers": headers, "variables": variables, - "timeout": timeout } e, _ = TenantService.get_by_id(current_user.id) @@ -244,46 +142,45 @@ async def create( tools = server_tools[server_name] tools = {tool["name"]: tool for tool in tools if isinstance(tool, dict) and "name" in tool} variables["tools"] = tools - req_data["variables"] = variables + req["variables"] = variables - if not MCPServerService.insert(**req_data): + if not MCPServerService.insert(**req): return get_data_error_result("Failed to create MCP server.") - return get_json_result(data=req_data) + return get_json_result(data=req) except Exception as e: return server_error_response(e) @router.post("/update") async def update( - request: UpdateMCPRequest, - current_user: User = Depends(get_current_user) + request: UpdateMCPServerRequest, + current_user = Depends(get_current_user) ): + """更新MCP服务器""" mcp_id = request.mcp_id e, mcp_server = MCPServerService.get_by_id(mcp_id) if not e or mcp_server.tenant_id != current_user.id: return get_data_error_result(message=f"Cannot find MCP server {mcp_id} for user {current_user.id}") - server_type = request.server_type or mcp_server.server_type + server_type = request.server_type if request.server_type is not None else mcp_server.server_type if server_type and server_type not in VALID_MCP_SERVER_TYPES: return get_data_error_result(message="Unsupported MCP server type.") - - server_name = request.name or mcp_server.name + server_name = request.name if request.name is not None else mcp_server.name if server_name and len(server_name.encode("utf-8")) > 255: return get_data_error_result(message=f"Invalid MCP name or length is {len(server_name)} which is large than 255.") - - url = request.url or mcp_server.url + url = request.url if request.url is not None else mcp_server.url if not url: return get_data_error_result(message="Invalid url.") - headers = safe_json_parse(request.headers or mcp_server.headers) - variables = safe_json_parse(request.variables or mcp_server.variables) + headers = safe_json_parse(request.headers if request.headers is not None else mcp_server.headers) + variables = safe_json_parse(request.variables if request.variables is not None else mcp_server.variables) variables.pop("tools", None) - timeout = request.timeout or 10 + timeout = request.timeout or 10.0 try: - req_data = { + req = { "tenant_id": current_user.id, "id": mcp_id, "name": server_name, @@ -291,7 +188,6 @@ async def update( "server_type": server_type, "headers": headers, "variables": variables, - "timeout": timeout } mcp_server = MCPServer(id=server_name, name=server_name, url=url, server_type=server_type, variables=variables, headers=headers) @@ -302,12 +198,12 @@ async def update( tools = server_tools[server_name] tools = {tool["name"]: tool for tool in tools if isinstance(tool, dict) and "name" in tool} variables["tools"] = tools - req_data["variables"] = variables + req["variables"] = variables - if not MCPServerService.filter_update([MCPServer.id == mcp_id, MCPServer.tenant_id == current_user.id], req_data): + if not MCPServerService.filter_update([MCPServer.id == mcp_id, MCPServer.tenant_id == current_user.id], req): return get_data_error_result(message="Failed to updated MCP server.") - e, updated_mcp = MCPServerService.get_by_id(req_data["id"]) + e, updated_mcp = MCPServerService.get_by_id(req["id"]) if not e: return get_data_error_result(message="Failed to fetch updated MCP server.") @@ -318,9 +214,10 @@ async def update( @router.post("/rm") async def rm( - request: RemoveMCPRequest, - current_user: User = Depends(get_current_user) + request: DeleteMCPServersRequest, + current_user = Depends(get_current_user) ): + """删除MCP服务器""" mcp_ids = request.mcp_ids try: @@ -334,14 +231,15 @@ async def rm( @router.post("/import") async def import_multiple( - request: ImportMCPRequest, - current_user: User = Depends(get_current_user) + request: ImportMCPServersRequest, + current_user = Depends(get_current_user) ): + """批量导入MCP服务器""" servers = request.mcpServers if not servers: return get_data_error_result(message="No MCP servers provided.") - timeout = request.timeout or 10 + timeout = request.timeout or 10.0 results = [] try: @@ -401,9 +299,10 @@ async def import_multiple( @router.post("/export") async def export_multiple( - request: ExportMCPRequest, - current_user: User = Depends(get_current_user) + request: ExportMCPServersRequest, + current_user = Depends(get_current_user) ): + """批量导出MCP服务器""" mcp_ids = request.mcp_ids if not mcp_ids: @@ -432,12 +331,16 @@ async def export_multiple( @router.post("/list_tools") -async def list_tools(req: ListToolsRequest, current_user: User = Depends(get_current_user)): - mcp_ids = req.mcp_ids +async def list_tools( + request: ListMCPToolsRequest, + current_user = Depends(get_current_user) +): + """列出MCP工具""" + mcp_ids = request.mcp_ids if not mcp_ids: return get_data_error_result(message="No MCP server IDs provided.") - timeout = req.timeout + timeout = request.timeout or 10.0 results = {} tool_call_sessions = [] @@ -476,15 +379,19 @@ async def list_tools(req: ListToolsRequest, current_user: User = Depends(get_cur @router.post("/test_tool") -async def test_tool(req: TestToolRequest, current_user: User = Depends(get_current_user)): - mcp_id = req.mcp_id +async def test_tool( + request: TestMCPToolRequest, + current_user = Depends(get_current_user) +): + """测试MCP工具""" + mcp_id = request.mcp_id if not mcp_id: return get_data_error_result(message="No MCP server ID provided.") - timeout = req.timeout + timeout = request.timeout or 10.0 - tool_name = req.tool_name - arguments = req.arguments + tool_name = request.tool_name + arguments = request.arguments if not all([tool_name, arguments]): return get_data_error_result(message="Require provide tool name and arguments.") @@ -506,11 +413,15 @@ async def test_tool(req: TestToolRequest, current_user: User = Depends(get_curre @router.post("/cache_tools") -async def cache_tool(req: CacheToolsRequest, current_user: User = Depends(get_current_user)): - mcp_id = req.mcp_id +async def cache_tool( + request: CacheMCPToolsRequest, + current_user = Depends(get_current_user) +): + """缓存MCP工具""" + mcp_id = request.mcp_id if not mcp_id: return get_data_error_result(message="No MCP server ID provided.") - tools = req.tools + tools = request.tools e, mcp_server = MCPServerService.get_by_id(mcp_id) if not e or mcp_server.tenant_id != current_user.id: @@ -527,18 +438,21 @@ async def cache_tool(req: CacheToolsRequest, current_user: User = Depends(get_cu @router.post("/test_mcp") -async def test_mcp(req: TestMCPRequest): - url = req.url +async def test_mcp( + request: TestMCPRequest +): + """测试MCP服务器(不需要登录)""" + url = request.url if not url: return get_data_error_result(message="Invalid MCP url.") - server_type = req.server_type + server_type = request.server_type if server_type not in VALID_MCP_SERVER_TYPES: return get_data_error_result(message="Unsupported MCP server type.") - timeout = req.timeout - headers = req.headers - variables = req.variables + timeout = request.timeout or 10.0 + headers = safe_json_parse(request.headers or {}) + variables = safe_json_parse(request.variables or {}) mcp_server = MCPServer(id=f"{server_type}: {url}", server_type=server_type, url=url, headers=headers, variables=variables) diff --git a/api/apps/models/auth_dependencies.py b/api/apps/models/auth_dependencies.py new file mode 100644 index 0000000..67931a6 --- /dev/null +++ b/api/apps/models/auth_dependencies.py @@ -0,0 +1,53 @@ +# +# Copyright 2024 The InfiniFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from typing import Optional +from fastapi import Depends, Header, Security, HTTPException, status +from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials +from api import settings +from api.utils.api_utils import get_json_result + +# 创建 HTTPBearer 安全方案(auto_error=False 允许我们自定义错误处理) +http_bearer = HTTPBearer(auto_error=False) + + +def get_current_user(credentials: Optional[HTTPAuthorizationCredentials] = Security(http_bearer)): + """FastAPI 依赖注入:获取当前用户(替代 Flask 的 login_required 和 current_user) + + 使用 Security(http_bearer) 可以让 FastAPI 自动在 OpenAPI schema 中添加安全要求, + 这样 Swagger UI 就会显示授权输入框并自动在请求中添加 Authorization 头。 + """ + # 延迟导入以避免循环导入 + from api.apps.__init___fastapi import get_current_user_from_token + + if not credentials: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Authorization header is required" + ) + + # HTTPBearer 已经提取了 Bearer token,credentials.credentials 就是 token 本身 + authorization = credentials.credentials + + user = get_current_user_from_token(authorization) + if not user: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Invalid or expired token" + ) + + return user + diff --git a/api/apps/models/canvas_models.py b/api/apps/models/canvas_models.py new file mode 100644 index 0000000..8f26e62 --- /dev/null +++ b/api/apps/models/canvas_models.py @@ -0,0 +1,129 @@ +# +# Copyright 2024 The InfiniFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from typing import Optional, List, Dict, Any, Union +from pydantic import BaseModel, Field + + +class DeleteCanvasRequest(BaseModel): + """删除画布请求""" + canvas_ids: List[str] = Field(..., description="画布ID列表") + + +class SaveCanvasRequest(BaseModel): + """保存画布请求""" + dsl: Union[str, Dict[str, Any]] = Field(..., description="DSL配置") + title: str = Field(..., description="画布标题") + id: Optional[str] = Field(default=None, description="画布ID(更新时提供)") + canvas_category: Optional[str] = Field(default=None, description="画布类别") + description: Optional[str] = Field(default=None, description="描述") + permission: Optional[str] = Field(default=None, description="权限") + avatar: Optional[str] = Field(default=None, description="头像") + + +class CompletionRequest(BaseModel): + """完成/运行画布请求""" + id: str = Field(..., description="画布ID") + query: Optional[str] = Field(default="", description="查询内容") + files: Optional[List[str]] = Field(default=[], description="文件列表") + inputs: Optional[Dict[str, Any]] = Field(default={}, description="输入参数") + user_id: Optional[str] = Field(default=None, description="用户ID") + + +class RerunRequest(BaseModel): + """重新运行请求""" + id: str = Field(..., description="流水线ID") + dsl: Dict[str, Any] = Field(..., description="DSL配置") + component_id: str = Field(..., description="组件ID") + + +class ResetCanvasRequest(BaseModel): + """重置画布请求""" + id: str = Field(..., description="画布ID") + + +class InputFormQuery(BaseModel): + """获取输入表单查询参数""" + id: str = Field(..., description="画布ID") + component_id: str = Field(..., description="组件ID") + + +class DebugRequest(BaseModel): + """调试请求""" + id: str = Field(..., description="画布ID") + component_id: str = Field(..., description="组件ID") + params: Dict[str, Any] = Field(..., description="参数") + + +class TestDBConnectRequest(BaseModel): + """测试数据库连接请求""" + db_type: str = Field(..., description="数据库类型") + database: str = Field(..., description="数据库名") + username: str = Field(..., description="用户名") + host: str = Field(..., description="主机") + port: str = Field(..., description="端口") + password: str = Field(..., description="密码") + + +class ListCanvasQuery(BaseModel): + """列出画布查询参数""" + keywords: Optional[str] = Field(default="", description="关键词") + page: Optional[int] = Field(default=0, description="页码") + page_size: Optional[int] = Field(default=0, description="每页大小") + orderby: Optional[str] = Field(default="create_time", description="排序字段") + desc: Optional[str] = Field(default="true", description="是否降序") + canvas_category: Optional[str] = Field(default=None, description="画布类别") + owner_ids: Optional[str] = Field(default="", description="所有者ID列表(逗号分隔)") + + +class SettingRequest(BaseModel): + """画布设置请求""" + id: str = Field(..., description="画布ID") + title: str = Field(..., description="标题") + permission: str = Field(..., description="权限") + description: Optional[str] = Field(default=None, description="描述") + avatar: Optional[str] = Field(default=None, description="头像") + + +class TraceQuery(BaseModel): + """追踪查询参数""" + canvas_id: str = Field(..., description="画布ID") + message_id: str = Field(..., description="消息ID") + + +class ListSessionsQuery(BaseModel): + """列出会话查询参数""" + user_id: Optional[str] = Field(default=None, description="用户ID") + page: Optional[int] = Field(default=1, description="页码") + page_size: Optional[int] = Field(default=30, description="每页大小") + keywords: Optional[str] = Field(default=None, description="关键词") + from_date: Optional[str] = Field(default=None, description="开始日期") + to_date: Optional[str] = Field(default=None, description="结束日期") + orderby: Optional[str] = Field(default="update_time", description="排序字段") + desc: Optional[str] = Field(default="true", description="是否降序") + dsl: Optional[str] = Field(default="true", description="是否包含DSL") + + +class DownloadQuery(BaseModel): + """下载查询参数""" + id: str = Field(..., description="文件ID") + created_by: str = Field(..., description="创建者ID") + + +class UploadQuery(BaseModel): + """上传查询参数""" + url: Optional[str] = Field(default=None, description="URL(可选,用于从URL下载)") + diff --git a/api/apps/models/chunk_models.py b/api/apps/models/chunk_models.py new file mode 100644 index 0000000..9fc521d --- /dev/null +++ b/api/apps/models/chunk_models.py @@ -0,0 +1,80 @@ +# +# Copyright 2024 The InfiniFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from typing import Optional, List, Any, Union +from pydantic import BaseModel, Field, field_validator + + +class ListChunksRequest(BaseModel): + """列出文档块请求""" + doc_id: str = Field(..., description="文档ID") + page: Optional[int] = Field(default=1, description="页码") + size: Optional[int] = Field(default=30, description="每页大小") + keywords: Optional[str] = Field(default="", description="关键词") + available_int: Optional[int] = Field(default=None, description="可用状态") + + +class SetChunkRequest(BaseModel): + """设置文档块请求""" + doc_id: str = Field(..., description="文档ID") + chunk_id: str = Field(..., description="块ID") + content_with_weight: str = Field(..., description="内容") + important_kwd: Optional[List[str]] = Field(default=None, description="重要关键词列表") + question_kwd: Optional[List[str]] = Field(default=None, description="问题关键词列表") + tag_kwd: Optional[str] = Field(default=None, description="标签关键词") + tag_feas: Optional[Any] = Field(default=None, description="标签特征") + available_int: Optional[int] = Field(default=None, description="可用状态") + + +class SwitchChunksRequest(BaseModel): + """切换文档块状态请求""" + doc_id: str = Field(..., description="文档ID") + chunk_ids: List[str] = Field(..., description="块ID列表") + available_int: int = Field(..., description="可用状态") + + +class DeleteChunksRequest(BaseModel): + """删除文档块请求""" + doc_id: str = Field(..., description="文档ID") + chunk_ids: List[str] = Field(..., description="块ID列表") + + +class CreateChunkRequest(BaseModel): + """创建文档块请求""" + doc_id: str = Field(..., description="文档ID") + content_with_weight: str = Field(..., description="内容") + important_kwd: Optional[List[str]] = Field(default=[], description="重要关键词列表") + question_kwd: Optional[List[str]] = Field(default=[], description="问题关键词列表") + tag_feas: Optional[Any] = Field(default=None, description="标签特征") + + +class RetrievalTestRequest(BaseModel): + """检索测试请求""" + kb_id: Union[str, List[str]] = Field(..., description="知识库ID,可以是字符串或列表") + question: str = Field(..., description="问题") + page: Optional[int] = Field(default=1, description="页码") + size: Optional[int] = Field(default=30, description="每页大小") + doc_ids: Optional[List[str]] = Field(default=[], description="文档ID列表") + use_kg: Optional[bool] = Field(default=False, description="是否使用知识图谱") + top_k: Optional[int] = Field(default=1024, description="Top K") + cross_languages: Optional[List[str]] = Field(default=[], description="跨语言列表") + search_id: Optional[str] = Field(default="", description="搜索ID") + rerank_id: Optional[str] = Field(default=None, description="重排序模型ID") + keyword: Optional[bool] = Field(default=False, description="是否使用关键词") + similarity_threshold: Optional[float] = Field(default=0.0, description="相似度阈值") + vector_similarity_weight: Optional[float] = Field(default=0.3, description="向量相似度权重") + highlight: Optional[bool] = Field(default=False, description="是否高亮") + diff --git a/api/apps/models/document_models.py b/api/apps/models/document_models.py new file mode 100644 index 0000000..a9fc161 --- /dev/null +++ b/api/apps/models/document_models.py @@ -0,0 +1,204 @@ +# +# Copyright 2024 The InfiniFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from typing import Optional, Literal, List +from pydantic import BaseModel, Field, model_validator + + +class CreateDocumentRequest(BaseModel): + """创建文档请求 + + 支持两种解析类型: + - parse_type=1: 使用内置解析器,需要 parser_id,pipeline_id 为空 + - parse_type=2: 使用自定义 pipeline,需要 pipeline_id,parser_id 为空 + 如果不提供 parse_type,则从知识库继承解析配置 + """ + name: str + kb_id: str + parse_type: Optional[Literal[1, 2]] = Field(default=None, description="解析类型:1=内置解析器,2=自定义pipeline,None=从知识库继承") + parser_id: Optional[str] = Field(default="", description="解析器ID,parse_type=1时必需") + pipeline_id: Optional[str] = Field(default="", description="流水线ID,parse_type=2时必需") + parser_config: Optional[dict] = None + + @model_validator(mode='after') + def validate_parse_type_fields(self): + """根据 parse_type 验证相应字段""" + if self.parse_type is not None: + if self.parse_type == 1: + # parse_type=1: 需要 parser_id,pipeline_id 必须为空 + parser_id_val = self.parser_id or "" + pipeline_id_val = self.pipeline_id or "" + + if parser_id_val.strip() == "": + raise ValueError("parse_type=1时,parser_id不能为空") + if pipeline_id_val.strip() != "": + raise ValueError("parse_type=1时,pipeline_id必须为空") + elif self.parse_type == 2: + # parse_type=2: 需要 pipeline_id,parser_id 必须为空 + parser_id_val = self.parser_id or "" + pipeline_id_val = self.pipeline_id or "" + + if pipeline_id_val.strip() == "": + raise ValueError("parse_type=2时,pipeline_id不能为空") + if parser_id_val.strip() != "": + raise ValueError("parse_type=2时,parser_id必须为空") + return self + + +class ChangeParserRequest(BaseModel): + """修改文档解析器请求 + + 支持两种解析类型: + - parse_type=1: 使用内置解析器,需要 parser_id,pipeline_id 为空 + - parse_type=2: 使用自定义 pipeline,需要 pipeline_id,parser_id 为空 + """ + doc_id: str + parse_type: Literal[1, 2] = Field(..., description="解析类型:1=内置解析器,2=自定义pipeline") + parser_id: Optional[str] = Field(default="", description="解析器ID,parse_type=1时必需") + pipeline_id: Optional[str] = Field(default="", description="流水线ID,parse_type=2时必需") + parser_config: Optional[dict] = None + + @model_validator(mode='after') + def validate_parse_type_fields(self): + """根据 parse_type 验证相应字段""" + if self.parse_type == 1: + # parse_type=1: 需要 parser_id,pipeline_id 必须为空 + parser_id_val = self.parser_id or "" + pipeline_id_val = self.pipeline_id or "" + + if parser_id_val.strip() == "": + raise ValueError("parse_type=1时,parser_id不能为空") + if pipeline_id_val.strip() != "": + raise ValueError("parse_type=1时,pipeline_id必须为空") + elif self.parse_type == 2: + # parse_type=2: 需要 pipeline_id,parser_id 必须为空 + parser_id_val = self.parser_id or "" + pipeline_id_val = self.pipeline_id or "" + + if pipeline_id_val.strip() == "": + raise ValueError("parse_type=2时,pipeline_id不能为空") + if parser_id_val.strip() != "": + raise ValueError("parse_type=2时,parser_id必须为空") + return self + + +class WebCrawlRequest(BaseModel): + """网页爬取请求""" + kb_id: str + name: str + url: str + + +class ListDocumentsQuery(BaseModel): + """列出文档查询参数""" + kb_id: str + keywords: Optional[str] = "" + page: Optional[int] = 0 + page_size: Optional[int] = 0 + orderby: Optional[str] = "create_time" + desc: Optional[str] = "true" + create_time_from: Optional[int] = 0 + create_time_to: Optional[int] = 0 + + +class ListDocumentsBody(BaseModel): + """列出文档请求体""" + run_status: Optional[List[str]] = [] + types: Optional[List[str]] = [] + suffix: Optional[List[str]] = [] + + +class FilterDocumentsRequest(BaseModel): + """过滤文档请求""" + kb_id: str + keywords: Optional[str] = "" + suffix: Optional[List[str]] = [] + run_status: Optional[List[str]] = [] + types: Optional[List[str]] = [] + + +class GetDocumentInfosRequest(BaseModel): + """获取文档信息请求""" + doc_ids: List[str] + + +class ChangeStatusRequest(BaseModel): + """修改文档状态请求""" + doc_ids: List[str] + status: str # "0" 或 "1" + + @model_validator(mode='after') + def validate_status(self): + if self.status not in ["0", "1"]: + raise ValueError('Status must be either 0 or 1!') + return self + + +class DeleteDocumentRequest(BaseModel): + """删除文档请求""" + doc_id: str | List[str] # 支持单个或列表 + + +class RunDocumentRequest(BaseModel): + """运行文档解析请求""" + doc_ids: List[str] + run: str # TaskStatus 值 + delete: Optional[bool] = False + + +class RenameDocumentRequest(BaseModel): + """重命名文档请求""" + doc_id: str + name: str + + +class ChangeParserSimpleRequest(BaseModel): + """简单修改解析器请求(兼容旧逻辑)""" + doc_id: str + parser_id: Optional[str] = None + pipeline_id: Optional[str] = None + parser_config: Optional[dict] = None + + +class UploadAndParseRequest(BaseModel): + """上传并解析请求(仅用于验证 conversation_id)""" + conversation_id: str + + +class ParseRequest(BaseModel): + """解析请求""" + url: Optional[str] = None + + +class SetMetaRequest(BaseModel): + """设置元数据请求""" + doc_id: str + meta: str # JSON 字符串 + + @model_validator(mode='after') + def validate_meta(self): + import json + try: + meta_dict = json.loads(self.meta) + if not isinstance(meta_dict, dict): + raise ValueError("Only dictionary type supported.") + for k, v in meta_dict.items(): + if not isinstance(v, (str, int, float)): + raise ValueError(f"The type is not supported: {v}") + except json.JSONDecodeError as e: + raise ValueError(f"Json syntax error: {e}") + return self + diff --git a/api/apps/models/kb_models.py b/api/apps/models/kb_models.py new file mode 100644 index 0000000..ca58398 --- /dev/null +++ b/api/apps/models/kb_models.py @@ -0,0 +1,159 @@ +# +# Copyright 2024 The InfiniFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from typing import Optional, List, Dict, Any, Literal +from pydantic import BaseModel, Field, model_validator + + +class CreateKnowledgeBaseRequest(BaseModel): + """创建知识库请求 + + 支持两种解析类型: + - parse_type=1: 使用内置解析器,需要 parser_id,pipeline_id 为空 + - parse_type=2: 使用自定义 pipeline,需要 pipeline_id,parser_id 为空 + """ + name: str + parse_type: Literal[1, 2] = Field(..., description="解析类型:1=内置解析器,2=自定义pipeline") + embd_id: str = Field(..., description="嵌入模型ID") + parser_id: Optional[str] = Field(default="", description="解析器ID,parse_type=1时必需") + pipeline_id: Optional[str] = Field(default="", description="流水线ID,parse_type=2时必需") + description: Optional[str] = None + pagerank: Optional[int] = None + + @model_validator(mode='after') + def validate_parse_type_fields(self): + """根据 parse_type 验证相应字段""" + if self.parse_type == 1: + # parse_type=1: 需要 parser_id,pipeline_id 必须为空 + parser_id_val = self.parser_id or "" + pipeline_id_val = self.pipeline_id or "" + + if parser_id_val.strip() == "": + raise ValueError("parse_type=1时,parser_id不能为空") + if pipeline_id_val.strip() != "": + raise ValueError("parse_type=1时,pipeline_id必须为空") + elif self.parse_type == 2: + # parse_type=2: 需要 pipeline_id,parser_id 必须为空 + parser_id_val = self.parser_id or "" + pipeline_id_val = self.pipeline_id or "" + + if pipeline_id_val.strip() == "": + raise ValueError("parse_type=2时,pipeline_id不能为空") + if parser_id_val.strip() != "": + raise ValueError("parse_type=2时,parser_id必须为空") + return self + + +class UpdateKnowledgeBaseRequest(BaseModel): + """更新知识库请求""" + kb_id: str + name: str + description: str + parser_id: str + pagerank: Optional[int] = None + # 其他可选字段,但排除 id, tenant_id, created_by, create_time, update_time, create_date, update_date + + +class DeleteKnowledgeBaseRequest(BaseModel): + """删除知识库请求""" + kb_id: str + + +class ListKnowledgeBasesQuery(BaseModel): + """列出知识库查询参数""" + keywords: Optional[str] = "" + page: Optional[int] = 0 + page_size: Optional[int] = 0 + parser_id: Optional[str] = None + orderby: Optional[str] = "create_time" + desc: Optional[str] = "true" + + +class ListKnowledgeBasesBody(BaseModel): + """列出知识库请求体""" + owner_ids: Optional[List[str]] = [] + + +class RemoveTagsRequest(BaseModel): + """删除标签请求""" + tags: List[str] + + +class RenameTagRequest(BaseModel): + """重命名标签请求""" + from_tag: str + to_tag: str + + +class ListPipelineLogsQuery(BaseModel): + """列出流水线日志查询参数""" + kb_id: str + keywords: Optional[str] = "" + page: Optional[int] = 0 + page_size: Optional[int] = 0 + orderby: Optional[str] = "create_time" + desc: Optional[str] = "true" + create_date_from: Optional[str] = "" + create_date_to: Optional[str] = "" + + +class ListPipelineLogsBody(BaseModel): + """列出流水线日志请求体""" + operation_status: Optional[List[str]] = [] + types: Optional[List[str]] = [] + suffix: Optional[List[str]] = [] + + +class ListPipelineDatasetLogsQuery(BaseModel): + """列出流水线数据集日志查询参数""" + kb_id: str + page: Optional[int] = 0 + page_size: Optional[int] = 0 + orderby: Optional[str] = "create_time" + desc: Optional[str] = "true" + create_date_from: Optional[str] = "" + create_date_to: Optional[str] = "" + + +class ListPipelineDatasetLogsBody(BaseModel): + """列出流水线数据集日志请求体""" + operation_status: Optional[List[str]] = [] + + +class DeletePipelineLogsQuery(BaseModel): + """删除流水线日志查询参数""" + kb_id: str + + +class DeletePipelineLogsBody(BaseModel): + """删除流水线日志请求体""" + log_ids: List[str] + + +class RunGraphragRequest(BaseModel): + """运行 GraphRAG 请求""" + kb_id: str + + +class RunRaptorRequest(BaseModel): + """运行 RAPTOR 请求""" + kb_id: str + + +class RunMindmapRequest(BaseModel): + """运行 Mindmap 请求""" + kb_id: str + diff --git a/api/apps/models/llm_models.py b/api/apps/models/llm_models.py new file mode 100644 index 0000000..914c3b2 --- /dev/null +++ b/api/apps/models/llm_models.py @@ -0,0 +1,101 @@ +# +# Copyright 2024 The InfiniFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from typing import Optional +from pydantic import BaseModel, Field + + +class SetApiKeyRequest(BaseModel): + """设置 API Key 请求""" + llm_factory: str = Field(..., description="LLM 工厂名称") + api_key: str = Field(..., description="API Key") + base_url: Optional[str] = Field(default="", description="API Base URL") + model_type: Optional[str] = Field(default=None, description="模型类型") + llm_name: Optional[str] = Field(default=None, description="LLM 名称") + + +class AddLLMRequest(BaseModel): + """添加 LLM 请求""" + llm_factory: str = Field(..., description="LLM 工厂名称") + model_type: str = Field(..., description="模型类型") + llm_name: str = Field(..., description="LLM 名称") + api_key: Optional[str] = Field(default="x", description="API Key") + api_base: Optional[str] = Field(default="", description="API Base URL") + max_tokens: Optional[int] = Field(default=None, description="最大 Token 数") + + # VolcEngine 特殊字段 + ark_api_key: Optional[str] = None + endpoint_id: Optional[str] = None + + # Tencent Hunyuan 特殊字段 + hunyuan_sid: Optional[str] = None + hunyuan_sk: Optional[str] = None + + # Tencent Cloud 特殊字段 + tencent_cloud_sid: Optional[str] = None + tencent_cloud_sk: Optional[str] = None + + # Bedrock 特殊字段 + bedrock_ak: Optional[str] = None + bedrock_sk: Optional[str] = None + bedrock_region: Optional[str] = None + + # XunFei Spark 特殊字段 + spark_api_password: Optional[str] = None + spark_app_id: Optional[str] = None + spark_api_secret: Optional[str] = None + spark_api_key: Optional[str] = None + + # BaiduYiyan 特殊字段 + yiyan_ak: Optional[str] = None + yiyan_sk: Optional[str] = None + + # Fish Audio 特殊字段 + fish_audio_ak: Optional[str] = None + fish_audio_refid: Optional[str] = None + + # Google Cloud 特殊字段 + google_project_id: Optional[str] = None + google_region: Optional[str] = None + google_service_account_key: Optional[str] = None + + # Azure-OpenAI 特殊字段 + api_version: Optional[str] = None + + # OpenRouter 特殊字段 + provider_order: Optional[str] = None + + +class DeleteLLMRequest(BaseModel): + """删除 LLM 请求""" + llm_factory: str = Field(..., description="LLM 工厂名称") + llm_name: str = Field(..., description="LLM 名称") + + +class DeleteFactoryRequest(BaseModel): + """删除工厂请求""" + llm_factory: str = Field(..., description="LLM 工厂名称") + + +class MyLLMsQuery(BaseModel): + """获取我的 LLMs 查询参数""" + include_details: Optional[str] = Field(default="false", description="是否包含详细信息") + + +class ListLLMsQuery(BaseModel): + """列出 LLMs 查询参数""" + model_type: Optional[str] = Field(default=None, description="模型类型过滤") + diff --git a/api/apps/models/mcp_models.py b/api/apps/models/mcp_models.py new file mode 100644 index 0000000..0efa7da --- /dev/null +++ b/api/apps/models/mcp_models.py @@ -0,0 +1,99 @@ +# +# Copyright 2024 The InfiniFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from typing import Optional, List, Dict, Any +from pydantic import BaseModel, Field + + +class ListMCPServersQuery(BaseModel): + """列出MCP服务器查询参数""" + keywords: Optional[str] = Field(default="", description="关键词") + page: Optional[int] = Field(default=0, description="页码") + page_size: Optional[int] = Field(default=0, description="每页大小") + orderby: Optional[str] = Field(default="create_time", description="排序字段") + desc: Optional[str] = Field(default="true", description="是否降序") + + +class ListMCPServersBody(BaseModel): + """列出MCP服务器请求体""" + mcp_ids: Optional[List[str]] = Field(default=[], description="MCP服务器ID列表") + + +class CreateMCPServerRequest(BaseModel): + """创建MCP服务器请求""" + name: str = Field(..., description="服务器名称") + url: str = Field(..., description="服务器URL") + server_type: str = Field(..., description="服务器类型") + headers: Optional[Dict[str, Any]] = Field(default={}, description="请求头") + variables: Optional[Dict[str, Any]] = Field(default={}, description="变量") + timeout: Optional[float] = Field(default=10.0, description="超时时间") + + +class UpdateMCPServerRequest(BaseModel): + """更新MCP服务器请求""" + mcp_id: str = Field(..., description="MCP服务器ID") + name: Optional[str] = Field(default=None, description="服务器名称") + url: Optional[str] = Field(default=None, description="服务器URL") + server_type: Optional[str] = Field(default=None, description="服务器类型") + headers: Optional[Dict[str, Any]] = Field(default=None, description="请求头") + variables: Optional[Dict[str, Any]] = Field(default=None, description="变量") + timeout: Optional[float] = Field(default=10.0, description="超时时间") + + +class DeleteMCPServersRequest(BaseModel): + """删除MCP服务器请求""" + mcp_ids: List[str] = Field(..., description="MCP服务器ID列表") + + +class ImportMCPServersRequest(BaseModel): + """批量导入MCP服务器请求""" + mcpServers: Dict[str, Any] = Field(..., description="MCP服务器配置字典") + timeout: Optional[float] = Field(default=10.0, description="超时时间") + + +class ExportMCPServersRequest(BaseModel): + """批量导出MCP服务器请求""" + mcp_ids: List[str] = Field(..., description="MCP服务器ID列表") + + +class ListMCPToolsRequest(BaseModel): + """列出MCP工具请求""" + mcp_ids: List[str] = Field(..., description="MCP服务器ID列表") + timeout: Optional[float] = Field(default=10.0, description="超时时间") + + +class TestMCPToolRequest(BaseModel): + """测试MCP工具请求""" + mcp_id: str = Field(..., description="MCP服务器ID") + tool_name: str = Field(..., description="工具名称") + arguments: Dict[str, Any] = Field(..., description="工具参数") + timeout: Optional[float] = Field(default=10.0, description="超时时间") + + +class CacheMCPToolsRequest(BaseModel): + """缓存MCP工具请求""" + mcp_id: str = Field(..., description="MCP服务器ID") + tools: List[Dict[str, Any]] = Field(..., description="工具列表") + + +class TestMCPRequest(BaseModel): + """测试MCP服务器请求(不需要登录)""" + url: str = Field(..., description="服务器URL") + server_type: str = Field(..., description="服务器类型") + headers: Optional[Dict[str, Any]] = Field(default={}, description="请求头") + variables: Optional[Dict[str, Any]] = Field(default={}, description="变量") + timeout: Optional[float] = Field(default=10.0, description="超时时间") + diff --git a/api/apps/plugin_app.py b/api/apps/plugin_app.py index dcd209d..9ca0441 100644 --- a/api/apps/plugin_app.py +++ b/api/apps/plugin_app.py @@ -1,8 +1,26 @@ +# +# Copyright 2025 The InfiniFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + from flask import Response from flask_login import login_required from api.utils.api_utils import get_json_result from plugin import GlobalPluginManager + @manager.route('/llm_tools', methods=['GET']) # noqa: F821 @login_required def llm_tools() -> Response: diff --git a/api/apps/sdk/agent.py b/api/apps/sdk/agent.py index 704a3ff..b413283 100644 --- a/api/apps/sdk/agent.py +++ b/api/apps/sdk/agent.py @@ -25,6 +25,7 @@ from api.utils.api_utils import get_data_error_result, get_error_data_result, ge from api.utils.api_utils import get_result from flask import request + @manager.route('/agents', methods=['GET']) # noqa: F821 @token_required def list_agents(tenant_id): @@ -41,7 +42,7 @@ def list_agents(tenant_id): desc = False else: desc = True - canvas = UserCanvasService.get_list(tenant_id,page_number,items_per_page,orderby,desc,id,title) + canvas = UserCanvasService.get_list(tenant_id, page_number, items_per_page, orderby, desc, id, title) return get_result(data=canvas) @@ -93,7 +94,7 @@ def update_agent(tenant_id: str, agent_id: str): req["dsl"] = json.dumps(req["dsl"], ensure_ascii=False) req["dsl"] = json.loads(req["dsl"]) - + if req.get("title") is not None: req["title"] = req["title"].strip() diff --git a/api/apps/sdk/dataset.py b/api/apps/sdk/dataset.py index 7b25f1d..5a43b68 100644 --- a/api/apps/sdk/dataset.py +++ b/api/apps/sdk/dataset.py @@ -215,7 +215,8 @@ def delete(tenant_id): continue kb_id_instance_pairs.append((kb_id, kb)) if len(error_kb_ids) > 0: - return get_error_permission_result(message=f"""User '{tenant_id}' lacks permission for datasets: '{", ".join(error_kb_ids)}'""") + return get_error_permission_result( + message=f"""User '{tenant_id}' lacks permission for datasets: '{", ".join(error_kb_ids)}'""") errors = [] success_count = 0 @@ -232,7 +233,8 @@ def delete(tenant_id): ] ) File2DocumentService.delete_by_document_id(doc.id) - FileService.filter_delete([File.source_type == FileSource.KNOWLEDGEBASE, File.type == "folder", File.name == kb.name]) + FileService.filter_delete( + [File.source_type == FileSource.KNOWLEDGEBASE, File.type == "folder", File.name == kb.name]) if not KnowledgebaseService.delete_by_id(kb_id): errors.append(f"Delete dataset error for {kb_id}") continue @@ -329,7 +331,8 @@ def update(tenant_id, dataset_id): try: kb = KnowledgebaseService.get_or_none(id=dataset_id, tenant_id=tenant_id) if kb is None: - return get_error_permission_result(message=f"User '{tenant_id}' lacks permission for dataset '{dataset_id}'") + return get_error_permission_result( + message=f"User '{tenant_id}' lacks permission for dataset '{dataset_id}'") if req.get("parser_config"): req["parser_config"] = deep_merge(kb.parser_config, req["parser_config"]) @@ -341,7 +344,8 @@ def update(tenant_id, dataset_id): del req["parser_config"] if "name" in req and req["name"].lower() != kb.name.lower(): - exists = KnowledgebaseService.get_or_none(name=req["name"], tenant_id=tenant_id, status=StatusEnum.VALID.value) + exists = KnowledgebaseService.get_or_none(name=req["name"], tenant_id=tenant_id, + status=StatusEnum.VALID.value) if exists: return get_error_data_result(message=f"Dataset name '{req['name']}' already exists") @@ -349,7 +353,8 @@ def update(tenant_id, dataset_id): if not req["embd_id"]: req["embd_id"] = kb.embd_id if kb.chunk_num != 0 and req["embd_id"] != kb.embd_id: - return get_error_data_result(message=f"When chunk_num ({kb.chunk_num}) > 0, embedding_model must remain {kb.embd_id}") + return get_error_data_result( + message=f"When chunk_num ({kb.chunk_num}) > 0, embedding_model must remain {kb.embd_id}") ok, err = verify_embedding_availability(req["embd_id"], tenant_id) if not ok: return err @@ -359,10 +364,12 @@ def update(tenant_id, dataset_id): return get_error_argument_result(message="'pagerank' can only be set when doc_engine is elasticsearch") if req["pagerank"] > 0: - settings.docStoreConn.update({"kb_id": kb.id}, {PAGERANK_FLD: req["pagerank"]}, search.index_name(kb.tenant_id), kb.id) + settings.docStoreConn.update({"kb_id": kb.id}, {PAGERANK_FLD: req["pagerank"]}, + search.index_name(kb.tenant_id), kb.id) else: # Elasticsearch requires PAGERANK_FLD be non-zero! - settings.docStoreConn.update({"exists": PAGERANK_FLD}, {"remove": PAGERANK_FLD}, search.index_name(kb.tenant_id), kb.id) + settings.docStoreConn.update({"exists": PAGERANK_FLD}, {"remove": PAGERANK_FLD}, + search.index_name(kb.tenant_id), kb.id) if not KnowledgebaseService.update_by_id(kb.id, req): return get_error_data_result(message="Update dataset error.(Database error)") @@ -454,7 +461,7 @@ def list_datasets(tenant_id): return get_error_permission_result(message=f"User '{tenant_id}' lacks permission for dataset '{name}'") tenants = TenantService.get_joined_tenants_by_user_id(tenant_id) - kbs = KnowledgebaseService.get_list( + kbs, total = KnowledgebaseService.get_list( [m["tenant_id"] for m in tenants], tenant_id, args["page"], @@ -468,14 +475,15 @@ def list_datasets(tenant_id): response_data_list = [] for kb in kbs: response_data_list.append(remap_dictionary_keys(kb)) - return get_result(data=response_data_list) + return get_result(data=response_data_list, total=total) except OperationalError as e: logging.exception(e) return get_error_data_result(message="Database operation failed") + @manager.route('/datasets//knowledge_graph', methods=['GET']) # noqa: F821 @token_required -def knowledge_graph(tenant_id,dataset_id): +def knowledge_graph(tenant_id, dataset_id): if not KnowledgebaseService.accessible(dataset_id, tenant_id): return get_result( data=False, @@ -491,7 +499,7 @@ def knowledge_graph(tenant_id,dataset_id): obj = {"graph": {}, "mind_map": {}} if not settings.docStoreConn.indexExist(search.index_name(kb.tenant_id), dataset_id): return get_result(data=obj) - sres = settings.retrievaler.search(req, search.index_name(kb.tenant_id), [dataset_id]) + sres = settings.retriever.search(req, search.index_name(kb.tenant_id), [dataset_id]) if not len(sres.ids): return get_result(data=obj) @@ -507,14 +515,16 @@ def knowledge_graph(tenant_id,dataset_id): if "nodes" in obj["graph"]: obj["graph"]["nodes"] = sorted(obj["graph"]["nodes"], key=lambda x: x.get("pagerank", 0), reverse=True)[:256] if "edges" in obj["graph"]: - node_id_set = { o["id"] for o in obj["graph"]["nodes"] } - filtered_edges = [o for o in obj["graph"]["edges"] if o["source"] != o["target"] and o["source"] in node_id_set and o["target"] in node_id_set] + node_id_set = {o["id"] for o in obj["graph"]["nodes"]} + filtered_edges = [o for o in obj["graph"]["edges"] if + o["source"] != o["target"] and o["source"] in node_id_set and o["target"] in node_id_set] obj["graph"]["edges"] = sorted(filtered_edges, key=lambda x: x.get("weight", 0), reverse=True)[:128] return get_result(data=obj) + @manager.route('/datasets//knowledge_graph', methods=['DELETE']) # noqa: F821 @token_required -def delete_knowledge_graph(tenant_id,dataset_id): +def delete_knowledge_graph(tenant_id, dataset_id): if not KnowledgebaseService.accessible(dataset_id, tenant_id): return get_result( data=False, @@ -522,6 +532,7 @@ def delete_knowledge_graph(tenant_id,dataset_id): code=settings.RetCode.AUTHENTICATION_ERROR ) _, kb = KnowledgebaseService.get_by_id(dataset_id) - settings.docStoreConn.delete({"knowledge_graph_kwd": ["graph", "subgraph", "entity", "relation"]}, search.index_name(kb.tenant_id), dataset_id) + settings.docStoreConn.delete({"knowledge_graph_kwd": ["graph", "subgraph", "entity", "relation"]}, + search.index_name(kb.tenant_id), dataset_id) return get_result(data=True) diff --git a/api/apps/sdk/dify_retrieval.py b/api/apps/sdk/dify_retrieval.py index 446d4d7..cbfb6e7 100644 --- a/api/apps/sdk/dify_retrieval.py +++ b/api/apps/sdk/dify_retrieval.py @@ -1,4 +1,4 @@ - # +# # Copyright 2024 The InfiniFlow Authors. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -31,6 +31,89 @@ from api.db.services.dialog_service import meta_filter, convert_conditions @apikey_required @validate_request("knowledge_id", "query") def retrieval(tenant_id): + """ + Dify-compatible retrieval API + --- + tags: + - SDK + security: + - ApiKeyAuth: [] + parameters: + - in: body + name: body + required: true + schema: + type: object + required: + - knowledge_id + - query + properties: + knowledge_id: + type: string + description: Knowledge base ID + query: + type: string + description: Query text + use_kg: + type: boolean + description: Whether to use knowledge graph + default: false + retrieval_setting: + type: object + description: Retrieval configuration + properties: + score_threshold: + type: number + description: Similarity threshold + default: 0.0 + top_k: + type: integer + description: Number of results to return + default: 1024 + metadata_condition: + type: object + description: Metadata filter condition + properties: + conditions: + type: array + items: + type: object + properties: + name: + type: string + description: Field name + comparison_operator: + type: string + description: Comparison operator + value: + type: string + description: Field value + responses: + 200: + description: Retrieval succeeded + schema: + type: object + properties: + records: + type: array + items: + type: object + properties: + content: + type: string + description: Content text + score: + type: number + description: Similarity score + title: + type: string + description: Document title + metadata: + type: object + description: Metadata info + 404: + description: Knowledge base or document not found + """ req = request.json question = req["query"] kb_id = req["knowledge_id"] @@ -38,9 +121,9 @@ def retrieval(tenant_id): retrieval_setting = req.get("retrieval_setting", {}) similarity_threshold = float(retrieval_setting.get("score_threshold", 0.0)) top = int(retrieval_setting.get("top_k", 1024)) - metadata_condition = req.get("metadata_condition",{}) + metadata_condition = req.get("metadata_condition", {}) metas = DocumentService.get_meta_by_kbs([kb_id]) - + doc_ids = [] try: @@ -50,12 +133,12 @@ def retrieval(tenant_id): embd_mdl = LLMBundle(kb.tenant_id, LLMType.EMBEDDING.value, llm_name=kb.embd_id) print(metadata_condition) - print("after",convert_conditions(metadata_condition)) + # print("after", convert_conditions(metadata_condition)) doc_ids.extend(meta_filter(metas, convert_conditions(metadata_condition))) - print("doc_ids",doc_ids) + # print("doc_ids", doc_ids) if not doc_ids and metadata_condition is not None: doc_ids = ['-999'] - ranks = settings.retrievaler.retrieval( + ranks = settings.retriever.retrieval( question, embd_mdl, kb.tenant_id, @@ -70,17 +153,17 @@ def retrieval(tenant_id): ) if use_kg: - ck = settings.kg_retrievaler.retrieval(question, - [tenant_id], - [kb_id], - embd_mdl, - LLMBundle(kb.tenant_id, LLMType.CHAT)) + ck = settings.kg_retriever.retrieval(question, + [tenant_id], + [kb_id], + embd_mdl, + LLMBundle(kb.tenant_id, LLMType.CHAT)) if ck["content_with_weight"]: ranks["chunks"].insert(0, ck) records = [] for c in ranks["chunks"]: - e, doc = DocumentService.get_by_id( c["doc_id"]) + e, doc = DocumentService.get_by_id(c["doc_id"]) c.pop("vector", None) meta = getattr(doc, 'meta_fields', {}) meta["doc_id"] = c["doc_id"] @@ -100,5 +183,3 @@ def retrieval(tenant_id): ) logging.exception(e) return build_error_result(message=str(e), code=settings.RetCode.SERVER_ERROR) - - diff --git a/api/apps/sdk/doc.py b/api/apps/sdk/doc.py index 6008afd..ec9387e 100644 --- a/api/apps/sdk/doc.py +++ b/api/apps/sdk/doc.py @@ -69,7 +69,7 @@ class Chunk(BaseModel): @manager.route("/datasets//documents", methods=["POST"]) # noqa: F821 @token_required -async def upload(dataset_id, tenant_id): +def upload(dataset_id, tenant_id): """ Upload documents to a dataset. --- @@ -151,7 +151,7 @@ async def upload(dataset_id, tenant_id): e, kb = KnowledgebaseService.get_by_id(dataset_id) if not e: raise LookupError(f"Can't find the dataset with ID {dataset_id}!") - err, files = await FileService.upload_document(kb, file_objs, tenant_id) + err, files = FileService.upload_document(kb, file_objs, tenant_id) if err: return get_result(message="\n".join(err), code=settings.RetCode.SERVER_ERROR) # rename key's name @@ -470,6 +470,20 @@ def list_docs(dataset_id, tenant_id): required: false default: 0 description: Unix timestamp for filtering documents created before this time. 0 means no filter. + - in: query + name: suffix + type: array + items: + type: string + required: false + description: Filter by file suffix (e.g., ["pdf", "txt", "docx"]). + - in: query + name: run + type: array + items: + type: string + required: false + description: Filter by document run status. Supports both numeric ("0", "1", "2", "3", "4") and text formats ("UNSTART", "RUNNING", "CANCEL", "DONE", "FAIL"). - in: header name: Authorization type: string @@ -512,63 +526,62 @@ def list_docs(dataset_id, tenant_id): description: Processing status. """ if not KnowledgebaseService.accessible(kb_id=dataset_id, user_id=tenant_id): - return get_error_data_result(message=f"You don't own the dataset {dataset_id}. ") - id = request.args.get("id") - name = request.args.get("name") + return get_error_data_result(message=f"You don't own the dataset {dataset_id}. ") - if id and not DocumentService.query(id=id, kb_id=dataset_id): - return get_error_data_result(message=f"You don't own the document {id}.") + q = request.args + document_id = q.get("id") + name = q.get("name") + + if document_id and not DocumentService.query(id=document_id, kb_id=dataset_id): + return get_error_data_result(message=f"You don't own the document {document_id}.") if name and not DocumentService.query(name=name, kb_id=dataset_id): return get_error_data_result(message=f"You don't own the document {name}.") - page = int(request.args.get("page", 1)) - keywords = request.args.get("keywords", "") - page_size = int(request.args.get("page_size", 30)) - orderby = request.args.get("orderby", "create_time") - if request.args.get("desc") == "False": - desc = False - else: - desc = True - docs, tol = DocumentService.get_list(dataset_id, page, page_size, orderby, desc, keywords, id, name) + page = int(q.get("page", 1)) + page_size = int(q.get("page_size", 30)) + orderby = q.get("orderby", "create_time") + desc = str(q.get("desc", "true")).strip().lower() != "false" + keywords = q.get("keywords", "") - create_time_from = int(request.args.get("create_time_from", 0)) - create_time_to = int(request.args.get("create_time_to", 0)) + # filters - align with OpenAPI parameter names + suffix = q.getlist("suffix") + run_status = q.getlist("run") + create_time_from = int(q.get("create_time_from", 0)) + create_time_to = int(q.get("create_time_to", 0)) + # map run status (accept text or numeric) - align with API parameter + run_status_text_to_numeric = {"UNSTART": "0", "RUNNING": "1", "CANCEL": "2", "DONE": "3", "FAIL": "4"} + run_status_converted = [run_status_text_to_numeric.get(v, v) for v in run_status] + + docs, total = DocumentService.get_list( + dataset_id, page, page_size, orderby, desc, keywords, document_id, name, suffix, run_status_converted + ) + + # time range filter (0 means no bound) if create_time_from or create_time_to: - filtered_docs = [] - for doc in docs: - doc_create_time = doc.get("create_time", 0) - if (create_time_from == 0 or doc_create_time >= create_time_from) and (create_time_to == 0 or doc_create_time <= create_time_to): - filtered_docs.append(doc) - docs = filtered_docs + docs = [ + d for d in docs + if (create_time_from == 0 or d.get("create_time", 0) >= create_time_from) + and (create_time_to == 0 or d.get("create_time", 0) <= create_time_to) + ] - # rename key's name - renamed_doc_list = [] + # rename keys + map run status back to text for output key_mapping = { "chunk_num": "chunk_count", - "kb_id": "dataset_id", + "kb_id": "dataset_id", "token_num": "token_count", "parser_id": "chunk_method", } - run_mapping = { - "0": "UNSTART", - "1": "RUNNING", - "2": "CANCEL", - "3": "DONE", - "4": "FAIL", - } - for doc in docs: - renamed_doc = {} - for key, value in doc.items(): - if key == "run": - renamed_doc["run"] = run_mapping.get(str(value)) - new_key = key_mapping.get(key, key) - renamed_doc[new_key] = value - if key == "run": - renamed_doc["run"] = run_mapping.get(value) - renamed_doc_list.append(renamed_doc) - return get_result(data={"total": tol, "docs": renamed_doc_list}) + run_status_numeric_to_text = {"0": "UNSTART", "1": "RUNNING", "2": "CANCEL", "3": "DONE", "4": "FAIL"} + output_docs = [] + for d in docs: + renamed_doc = {key_mapping.get(k, k): v for k, v in d.items()} + if "run" in d: + renamed_doc["run"] = run_status_numeric_to_text.get(str(d["run"]), d["run"]) + output_docs.append(renamed_doc) + + return get_result(data={"total": total, "docs": output_docs}) @manager.route("/datasets//documents", methods=["DELETE"]) # noqa: F821 @token_required @@ -982,7 +995,7 @@ def list_chunks(tenant_id, dataset_id, document_id): _ = Chunk(**final_chunk) elif settings.docStoreConn.indexExist(search.index_name(tenant_id), dataset_id): - sres = settings.retrievaler.search(query, search.index_name(tenant_id), [dataset_id], emb_mdl=None, highlight=True) + sres = settings.retriever.search(query, search.index_name(tenant_id), [dataset_id], emb_mdl=None, highlight=True) res["total"] = sres.total for id in sres.ids: d = { @@ -1446,7 +1459,7 @@ def retrieval_test(tenant_id): chat_mdl = LLMBundle(kb.tenant_id, LLMType.CHAT) question += keyword_extraction(chat_mdl, question) - ranks = settings.retrievaler.retrieval( + ranks = settings.retriever.retrieval( question, embd_mdl, tenant_ids, @@ -1462,7 +1475,7 @@ def retrieval_test(tenant_id): rank_feature=label_question(question, kbs), ) if use_kg: - ck = settings.kg_retrievaler.retrieval(question, [k.tenant_id for k in kbs], kb_ids, embd_mdl, LLMBundle(kb.tenant_id, LLMType.CHAT)) + ck = settings.kg_retriever.retrieval(question, [k.tenant_id for k in kbs], kb_ids, embd_mdl, LLMBundle(kb.tenant_id, LLMType.CHAT)) if ck["content_with_weight"]: ranks["chunks"].insert(0, ck) diff --git a/api/apps/sdk/files.py b/api/apps/sdk/files.py index 96efe20..d2a3de2 100644 --- a/api/apps/sdk/files.py +++ b/api/apps/sdk/files.py @@ -1,3 +1,20 @@ +# +# Copyright 2025 The InfiniFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + import pathlib import re @@ -17,7 +34,8 @@ from api.utils.api_utils import get_json_result from api.utils.file_utils import filename_type from rag.utils.storage_factory import STORAGE_IMPL -@manager.route('/file/upload', methods=['POST']) # noqa: F821 + +@manager.route('/file/upload', methods=['POST']) # noqa: F821 @token_required def upload(tenant_id): """ @@ -44,22 +62,22 @@ def upload(tenant_id): type: object properties: data: - type: array - items: - type: object - properties: - id: - type: string - description: File ID - name: - type: string - description: File name - size: - type: integer - description: File size in bytes - type: - type: string - description: File type (e.g., document, folder) + type: array + items: + type: object + properties: + id: + type: string + description: File ID + name: + type: string + description: File name + size: + type: integer + description: File size in bytes + type: + type: string + description: File type (e.g., document, folder) """ pf_id = request.form.get("parent_id") @@ -97,12 +115,14 @@ def upload(tenant_id): e, file = FileService.get_by_id(file_id_list[len_id_list - 1]) if not e: return get_json_result(data=False, message="Folder not found!", code=404) - last_folder = FileService.create_folder(file, file_id_list[len_id_list - 1], file_obj_names, len_id_list) + last_folder = FileService.create_folder(file, file_id_list[len_id_list - 1], file_obj_names, + len_id_list) else: e, file = FileService.get_by_id(file_id_list[len_id_list - 2]) if not e: return get_json_result(data=False, message="Folder not found!", code=404) - last_folder = FileService.create_folder(file, file_id_list[len_id_list - 2], file_obj_names, len_id_list) + last_folder = FileService.create_folder(file, file_id_list[len_id_list - 2], file_obj_names, + len_id_list) filetype = filename_type(file_obj_names[file_len - 1]) location = file_obj_names[file_len - 1] @@ -129,7 +149,7 @@ def upload(tenant_id): return server_error_response(e) -@manager.route('/file/create', methods=['POST']) # noqa: F821 +@manager.route('/file/create', methods=['POST']) # noqa: F821 @token_required def create(tenant_id): """ @@ -207,7 +227,7 @@ def create(tenant_id): return server_error_response(e) -@manager.route('/file/list', methods=['GET']) # noqa: F821 +@manager.route('/file/list', methods=['GET']) # noqa: F821 @token_required def list_files(tenant_id): """ @@ -299,7 +319,7 @@ def list_files(tenant_id): return server_error_response(e) -@manager.route('/file/root_folder', methods=['GET']) # noqa: F821 +@manager.route('/file/root_folder', methods=['GET']) # noqa: F821 @token_required def get_root_folder(tenant_id): """ @@ -335,7 +355,7 @@ def get_root_folder(tenant_id): return server_error_response(e) -@manager.route('/file/parent_folder', methods=['GET']) # noqa: F821 +@manager.route('/file/parent_folder', methods=['GET']) # noqa: F821 @token_required def get_parent_folder(): """ @@ -380,7 +400,7 @@ def get_parent_folder(): return server_error_response(e) -@manager.route('/file/all_parent_folder', methods=['GET']) # noqa: F821 +@manager.route('/file/all_parent_folder', methods=['GET']) # noqa: F821 @token_required def get_all_parent_folders(tenant_id): """ @@ -428,7 +448,7 @@ def get_all_parent_folders(tenant_id): return server_error_response(e) -@manager.route('/file/rm', methods=['POST']) # noqa: F821 +@manager.route('/file/rm', methods=['POST']) # noqa: F821 @token_required def rm(tenant_id): """ @@ -502,7 +522,7 @@ def rm(tenant_id): return server_error_response(e) -@manager.route('/file/rename', methods=['POST']) # noqa: F821 +@manager.route('/file/rename', methods=['POST']) # noqa: F821 @token_required def rename(tenant_id): """ @@ -542,7 +562,8 @@ def rename(tenant_id): if not e: return get_json_result(message="File not found!", code=404) - if file.type != FileType.FOLDER.value and pathlib.Path(req["name"].lower()).suffix != pathlib.Path(file.name.lower()).suffix: + if file.type != FileType.FOLDER.value and pathlib.Path(req["name"].lower()).suffix != pathlib.Path( + file.name.lower()).suffix: return get_json_result(data=False, message="The extension of file can't be changed", code=400) for existing_file in FileService.query(name=req["name"], pf_id=file.parent_id): @@ -562,9 +583,9 @@ def rename(tenant_id): return server_error_response(e) -@manager.route('/file/get/', methods=['GET']) # noqa: F821 +@manager.route('/file/get/', methods=['GET']) # noqa: F821 @token_required -def get(tenant_id,file_id): +def get(tenant_id, file_id): """ Download a file. --- @@ -610,7 +631,7 @@ def get(tenant_id,file_id): return server_error_response(e) -@manager.route('/file/mv', methods=['POST']) # noqa: F821 +@manager.route('/file/mv', methods=['POST']) # noqa: F821 @token_required def move(tenant_id): """ @@ -669,6 +690,7 @@ def move(tenant_id): except Exception as e: return server_error_response(e) + @manager.route('/file/convert', methods=['POST']) # noqa: F821 @token_required def convert(tenant_id): @@ -735,4 +757,4 @@ def convert(tenant_id): file2documents.append(file2document.to_json()) return get_json_result(data=file2documents) except Exception as e: - return server_error_response(e) \ No newline at end of file + return server_error_response(e) diff --git a/api/apps/sdk/session.py b/api/apps/sdk/session.py index 10b6e97..684d009 100644 --- a/api/apps/sdk/session.py +++ b/api/apps/sdk/session.py @@ -36,7 +36,8 @@ from api.db.services.llm_service import LLMBundle from api.db.services.search_service import SearchService from api.db.services.user_service import UserTenantService from api.utils import get_uuid -from api.utils.api_utils import check_duplicate_ids, get_data_openai, get_error_data_result, get_json_result, get_result, server_error_response, token_required, validate_request +from api.utils.api_utils import check_duplicate_ids, get_data_openai, get_error_data_result, get_json_result, \ + get_result, server_error_response, token_required, validate_request from rag.app.tag import label_question from rag.prompts.template import load_prompt from rag.prompts.generator import cross_languages, gen_meta_filter, keyword_extraction, chunks_format @@ -88,7 +89,8 @@ def create_agent_session(tenant_id, agent_id): canvas.reset() cvs.dsl = json.loads(str(canvas)) - conv = {"id": session_id, "dialog_id": cvs.id, "user_id": user_id, "message": [{"role": "assistant", "content": canvas.get_prologue()}], "source": "agent", "dsl": cvs.dsl} + conv = {"id": session_id, "dialog_id": cvs.id, "user_id": user_id, + "message": [{"role": "assistant", "content": canvas.get_prologue()}], "source": "agent", "dsl": cvs.dsl} API4ConversationService.save(**conv) conv["agent_id"] = conv.pop("dialog_id") return get_result(data=conv) @@ -279,7 +281,7 @@ def chat_completion_openai_like(tenant_id, chat_id): reasoning_match = re.search(r"(.*?)", answer, flags=re.DOTALL) if reasoning_match: reasoning_part = reasoning_match.group(1) - content_part = answer[reasoning_match.end() :] + content_part = answer[reasoning_match.end():] else: reasoning_part = "" content_part = answer @@ -324,7 +326,8 @@ def chat_completion_openai_like(tenant_id, chat_id): response["choices"][0]["delta"]["content"] = None response["choices"][0]["delta"]["reasoning_content"] = None response["choices"][0]["finish_reason"] = "stop" - response["usage"] = {"prompt_tokens": len(prompt), "completion_tokens": token_used, "total_tokens": len(prompt) + token_used} + response["usage"] = {"prompt_tokens": len(prompt), "completion_tokens": token_used, + "total_tokens": len(prompt) + token_used} if need_reference: response["choices"][0]["delta"]["reference"] = chunks_format(last_ans.get("reference", [])) response["choices"][0]["delta"]["final_content"] = last_ans.get("answer", "") @@ -559,7 +562,8 @@ def list_agent_session(tenant_id, agent_id): desc = True # dsl defaults to True in all cases except for False and false include_dsl = request.args.get("dsl") != "False" and request.args.get("dsl") != "false" - total, convs = API4ConversationService.get_list(agent_id, tenant_id, page_number, items_per_page, orderby, desc, id, user_id, include_dsl) + total, convs = API4ConversationService.get_list(agent_id, tenant_id, page_number, items_per_page, orderby, desc, id, + user_id, include_dsl) if not convs: return get_result(data=[]) for conv in convs: @@ -581,7 +585,8 @@ def list_agent_session(tenant_id, agent_id): if message_num != 0 and messages[message_num]["role"] != "user": chunk_list = [] # Add boundary and type checks to prevent KeyError - if chunk_num < len(conv["reference"]) and conv["reference"][chunk_num] is not None and isinstance(conv["reference"][chunk_num], dict) and "chunks" in conv["reference"][chunk_num]: + if chunk_num < len(conv["reference"]) and conv["reference"][chunk_num] is not None and isinstance( + conv["reference"][chunk_num], dict) and "chunks" in conv["reference"][chunk_num]: chunks = conv["reference"][chunk_num]["chunks"] for chunk in chunks: # Ensure chunk is a dictionary before calling get method @@ -639,13 +644,16 @@ def delete(tenant_id, chat_id): if errors: if success_count > 0: - return get_result(data={"success_count": success_count, "errors": errors}, message=f"Partially deleted {success_count} sessions with {len(errors)} errors") + return get_result(data={"success_count": success_count, "errors": errors}, + message=f"Partially deleted {success_count} sessions with {len(errors)} errors") else: return get_error_data_result(message="; ".join(errors)) if duplicate_messages: if success_count > 0: - return get_result(message=f"Partially deleted {success_count} sessions with {len(duplicate_messages)} errors", data={"success_count": success_count, "errors": duplicate_messages}) + return get_result( + message=f"Partially deleted {success_count} sessions with {len(duplicate_messages)} errors", + data={"success_count": success_count, "errors": duplicate_messages}) else: return get_error_data_result(message=";".join(duplicate_messages)) @@ -691,13 +699,16 @@ def delete_agent_session(tenant_id, agent_id): if errors: if success_count > 0: - return get_result(data={"success_count": success_count, "errors": errors}, message=f"Partially deleted {success_count} sessions with {len(errors)} errors") + return get_result(data={"success_count": success_count, "errors": errors}, + message=f"Partially deleted {success_count} sessions with {len(errors)} errors") else: return get_error_data_result(message="; ".join(errors)) if duplicate_messages: if success_count > 0: - return get_result(message=f"Partially deleted {success_count} sessions with {len(duplicate_messages)} errors", data={"success_count": success_count, "errors": duplicate_messages}) + return get_result( + message=f"Partially deleted {success_count} sessions with {len(duplicate_messages)} errors", + data={"success_count": success_count, "errors": duplicate_messages}) else: return get_error_data_result(message=";".join(duplicate_messages)) @@ -730,7 +741,9 @@ def ask_about(tenant_id): for ans in ask(req["question"], req["kb_ids"], uid): yield "data:" + json.dumps({"code": 0, "message": "", "data": ans}, ensure_ascii=False) + "\n\n" except Exception as e: - yield "data:" + json.dumps({"code": 500, "message": str(e), "data": {"answer": "**ERROR**: " + str(e), "reference": []}}, ensure_ascii=False) + "\n\n" + yield "data:" + json.dumps( + {"code": 500, "message": str(e), "data": {"answer": "**ERROR**: " + str(e), "reference": []}}, + ensure_ascii=False) + "\n\n" yield "data:" + json.dumps({"code": 0, "message": "", "data": True}, ensure_ascii=False) + "\n\n" resp = Response(stream(), mimetype="text/event-stream") @@ -882,7 +895,9 @@ def begin_inputs(agent_id): return get_error_data_result(f"Can't find agent by ID: {agent_id}") canvas = Canvas(json.dumps(cvs.dsl), objs[0].tenant_id) - return get_result(data={"title": cvs.title, "avatar": cvs.avatar, "inputs": canvas.get_component_input_form("begin"), "prologue": canvas.get_prologue(), "mode": canvas.get_mode()}) + return get_result( + data={"title": cvs.title, "avatar": cvs.avatar, "inputs": canvas.get_component_input_form("begin"), + "prologue": canvas.get_prologue(), "mode": canvas.get_mode()}) @manager.route("/searchbots/ask", methods=["POST"]) # noqa: F821 @@ -911,7 +926,9 @@ def ask_about_embedded(): for ans in ask(req["question"], req["kb_ids"], uid, search_config=search_config): yield "data:" + json.dumps({"code": 0, "message": "", "data": ans}, ensure_ascii=False) + "\n\n" except Exception as e: - yield "data:" + json.dumps({"code": 500, "message": str(e), "data": {"answer": "**ERROR**: " + str(e), "reference": []}}, ensure_ascii=False) + "\n\n" + yield "data:" + json.dumps( + {"code": 500, "message": str(e), "data": {"answer": "**ERROR**: " + str(e), "reference": []}}, + ensure_ascii=False) + "\n\n" yield "data:" + json.dumps({"code": 0, "message": "", "data": True}, ensure_ascii=False) + "\n\n" resp = Response(stream(), mimetype="text/event-stream") @@ -978,7 +995,8 @@ def retrieval_test_embedded(): tenant_ids.append(tenant.tenant_id) break else: - return get_json_result(data=False, message="Only owner of knowledgebase authorized for this operation.", code=settings.RetCode.OPERATING_ERROR) + return get_json_result(data=False, message="Only owner of knowledgebase authorized for this operation.", + code=settings.RetCode.OPERATING_ERROR) e, kb = KnowledgebaseService.get_by_id(kb_ids[0]) if not e: @@ -998,11 +1016,13 @@ def retrieval_test_embedded(): question += keyword_extraction(chat_mdl, question) labels = label_question(question, [kb]) - ranks = settings.retrievaler.retrieval( - question, embd_mdl, tenant_ids, kb_ids, page, size, similarity_threshold, vector_similarity_weight, top, doc_ids, rerank_mdl=rerank_mdl, highlight=req.get("highlight"), rank_feature=labels + ranks = settings.retriever.retrieval( + question, embd_mdl, tenant_ids, kb_ids, page, size, similarity_threshold, vector_similarity_weight, top, + doc_ids, rerank_mdl=rerank_mdl, highlight=req.get("highlight"), rank_feature=labels ) if use_kg: - ck = settings.kg_retrievaler.retrieval(question, tenant_ids, kb_ids, embd_mdl, LLMBundle(kb.tenant_id, LLMType.CHAT)) + ck = settings.kg_retriever.retrieval(question, tenant_ids, kb_ids, embd_mdl, + LLMBundle(kb.tenant_id, LLMType.CHAT)) if ck["content_with_weight"]: ranks["chunks"].insert(0, ck) @@ -1013,7 +1033,8 @@ def retrieval_test_embedded(): return get_json_result(data=ranks) except Exception as e: if str(e).find("not_found") > 0: - return get_json_result(data=False, message="No chunk found! Check the chunk status please!", code=settings.RetCode.DATA_ERROR) + return get_json_result(data=False, message="No chunk found! Check the chunk status please!", + code=settings.RetCode.DATA_ERROR) return server_error_response(e) @@ -1082,7 +1103,8 @@ def detail_share_embedded(): if SearchService.query(tenant_id=tenant.tenant_id, id=search_id): break else: - return get_json_result(data=False, message="Has no permission for this operation.", code=settings.RetCode.OPERATING_ERROR) + return get_json_result(data=False, message="Has no permission for this operation.", + code=settings.RetCode.OPERATING_ERROR) search = SearchService.get_detail(search_id) if not search: diff --git a/api/apps/system_app.py b/api/apps/system_app.py index fa2b5f1..4302813 100644 --- a/api/apps/system_app.py +++ b/api/apps/system_app.py @@ -39,6 +39,7 @@ from rag.utils.redis_conn import REDIS_CONN from flask import jsonify from api.utils.health_utils import run_health_checks + @manager.route("/version", methods=["GET"]) # noqa: F821 @login_required def version(): @@ -161,7 +162,7 @@ def status(): task_executors = REDIS_CONN.smembers("TASKEXE") now = datetime.now().timestamp() for task_executor_id in task_executors: - heartbeats = REDIS_CONN.zrangebyscore(task_executor_id, now - 60*30, now) + heartbeats = REDIS_CONN.zrangebyscore(task_executor_id, now - 60 * 30, now) heartbeats = [json.loads(heartbeat) for heartbeat in heartbeats] task_executor_heartbeats[task_executor_id] = heartbeats except Exception: @@ -273,7 +274,8 @@ def token_list(): objs = [o.to_dict() for o in objs] for o in objs: if not o["beta"]: - o["beta"] = generate_confirmation_token(generate_confirmation_token(tenants[0].tenant_id)).replace("ragflow-", "")[:32] + o["beta"] = generate_confirmation_token(generate_confirmation_token(tenants[0].tenant_id)).replace( + "ragflow-", "")[:32] APITokenService.filter_update([APIToken.tenant_id == tenant_id, APIToken.token == o["token"]], o) return get_json_result(data=objs) except Exception as e: diff --git a/api/apps/tenant_app.py b/api/apps/tenant_app.py index d7ffb70..1066849 100644 --- a/api/apps/tenant_app.py +++ b/api/apps/tenant_app.py @@ -14,9 +14,8 @@ # limitations under the License. # -from fastapi import APIRouter, Depends, HTTPException, Query, status -from api.models.tenant_models import InviteUserRequest, UserTenantResponse -from api.utils.api_utils import get_current_user +from flask import request +from flask_login import login_required, current_user from api import settings from api.apps import smtp_mail_server @@ -25,18 +24,13 @@ from api.db.db_models import UserTenant from api.db.services.user_service import UserTenantService, UserService from api.utils import get_uuid, delta_seconds -from api.utils.api_utils import get_json_result, server_error_response, get_data_error_result +from api.utils.api_utils import get_json_result, validate_request, server_error_response, get_data_error_result from api.utils.web_utils import send_invite_email -# 创建 FastAPI 路由器 -router = APIRouter() - -@router.get("/{tenant_id}/user/list") -async def user_list( - tenant_id: str, - current_user = Depends(get_current_user) -): +@manager.route("//user/list", methods=["GET"]) # noqa: F821 +@login_required +def user_list(tenant_id): if current_user.id != tenant_id: return get_json_result( data=False, @@ -52,19 +46,18 @@ async def user_list( return server_error_response(e) -@router.post('/{tenant_id}/user') -async def create( - tenant_id: str, - request: InviteUserRequest, - current_user = Depends(get_current_user) -): +@manager.route('//user', methods=['POST']) # noqa: F821 +@login_required +@validate_request("email") +def create(tenant_id): if current_user.id != tenant_id: return get_json_result( data=False, message='No authorization.', code=settings.RetCode.AUTHENTICATION_ERROR) - invite_user_email = request.email + req = request.json + invite_user_email = req["email"] invite_users = UserService.query(email=invite_user_email) if not invite_users: return get_data_error_result(message="User not found.") @@ -77,7 +70,8 @@ async def create( return get_data_error_result(message=f"{invite_user_email} is already in the team.") if user_tenant_role == UserTenantRole.OWNER: return get_data_error_result(message=f"{invite_user_email} is the owner of the team.") - return get_data_error_result(message=f"{invite_user_email} is in the team, but the role: {user_tenant_role} is invalid.") + return get_data_error_result( + message=f"{invite_user_email} is in the team, but the role: {user_tenant_role} is invalid.") UserTenantService.save( id=get_uuid(), @@ -107,12 +101,9 @@ async def create( return get_json_result(data=usr) -@router.delete('/{tenant_id}/user/{user_id}') -async def rm( - tenant_id: str, - user_id: str, - current_user = Depends(get_current_user) -): +@manager.route('//user/', methods=['DELETE']) # noqa: F821 +@login_required +def rm(tenant_id, user_id): if current_user.id != tenant_id and current_user.id != user_id: return get_json_result( data=False, @@ -126,10 +117,9 @@ async def rm( return server_error_response(e) -@router.get("/list") -async def tenant_list( - current_user = Depends(get_current_user) -): +@manager.route("/list", methods=["GET"]) # noqa: F821 +@login_required +def tenant_list(): try: users = UserTenantService.get_tenants_by_user_id(current_user.id) for u in users: @@ -139,13 +129,12 @@ async def tenant_list( return server_error_response(e) -@router.put("/agree/{tenant_id}") -async def agree( - tenant_id: str, - current_user = Depends(get_current_user) -): +@manager.route("/agree/", methods=["PUT"]) # noqa: F821 +@login_required +def agree(tenant_id): try: - UserTenantService.filter_update([UserTenant.tenant_id == tenant_id, UserTenant.user_id == current_user.id], {"role": UserTenantRole.NORMAL}) + UserTenantService.filter_update([UserTenant.tenant_id == tenant_id, UserTenant.user_id == current_user.id], + {"role": UserTenantRole.NORMAL}) return get_json_result(data=True) except Exception as e: return server_error_response(e) diff --git a/api/apps/user_app.py b/api/apps/user_app.py index f99b7c1..7e124d3 100644 --- a/api/apps/user_app.py +++ b/api/apps/user_app.py @@ -15,11 +15,14 @@ # import json import logging +import string +import os import re import secrets +import time from datetime import datetime -from flask import redirect, request, session +from flask import redirect, request, session, make_response from flask_login import current_user, login_required, login_user, logout_user from werkzeug.security import check_password_hash, generate_password_hash @@ -46,6 +49,19 @@ from api.utils.api_utils import ( validate_request, ) from api.utils.crypt import decrypt +from rag.utils.redis_conn import REDIS_CONN +from api.apps import smtp_mail_server +from api.utils.web_utils import ( + send_email_html, + OTP_LENGTH, + OTP_TTL_SECONDS, + ATTEMPT_LIMIT, + ATTEMPT_LOCK_SECONDS, + RESEND_COOLDOWN_SECONDS, + otp_keys, + hash_code, + captcha_key, +) @manager.route("/login", methods=["POST", "GET"]) # noqa: F821 @@ -825,3 +841,172 @@ def set_tenant_info(): return get_json_result(data=True) except Exception as e: return server_error_response(e) + + +@manager.route("/forget/captcha", methods=["GET"]) # noqa: F821 +def forget_get_captcha(): + """ + GET /forget/captcha?email= + - Generate an image captcha and cache it in Redis under key captcha:{email} with TTL = OTP_TTL_SECONDS. + - Returns the captcha as a PNG image. + """ + email = (request.args.get("email") or "") + if not email: + return get_json_result(data=False, code=settings.RetCode.ARGUMENT_ERROR, message="email is required") + + users = UserService.query(email=email) + if not users: + return get_json_result(data=False, code=settings.RetCode.DATA_ERROR, message="invalid email") + + # Generate captcha text + allowed = string.ascii_uppercase + string.digits + captcha_text = "".join(secrets.choice(allowed) for _ in range(OTP_LENGTH)) + REDIS_CONN.set(captcha_key(email), captcha_text, 60) # Valid for 60 seconds + + from captcha.image import ImageCaptcha + image = ImageCaptcha(width=300, height=120, font_sizes=[50, 60, 70]) + img_bytes = image.generate(captcha_text).read() + response = make_response(img_bytes) + response.headers.set("Content-Type", "image/JPEG") + return response + + +@manager.route("/forget/otp", methods=["POST"]) # noqa: F821 +def forget_send_otp(): + """ + POST /forget/otp + - Verify the image captcha stored at captcha:{email} (case-insensitive). + - On success, generate an email OTP (A–Z with length = OTP_LENGTH), store hash + salt (and timestamp) in Redis with TTL, reset attempts and cooldown, and send the OTP via email. + """ + req = request.get_json() + email = req.get("email") or "" + captcha = (req.get("captcha") or "").strip() + + if not email or not captcha: + return get_json_result(data=False, code=settings.RetCode.ARGUMENT_ERROR, message="email and captcha required") + + users = UserService.query(email=email) + if not users: + return get_json_result(data=False, code=settings.RetCode.DATA_ERROR, message="invalid email") + + stored_captcha = REDIS_CONN.get(captcha_key(email)) + if not stored_captcha: + return get_json_result(data=False, code=settings.RetCode.NOT_EFFECTIVE, message="invalid or expired captcha") + if (stored_captcha or "").strip().lower() != captcha.lower(): + return get_json_result(data=False, code=settings.RetCode.AUTHENTICATION_ERROR, message="invalid or expired captcha") + + # Delete captcha to prevent reuse + REDIS_CONN.delete(captcha_key(email)) + + k_code, k_attempts, k_last, k_lock = otp_keys(email) + now = int(time.time()) + last_ts = REDIS_CONN.get(k_last) + if last_ts: + try: + elapsed = now - int(last_ts) + except Exception: + elapsed = RESEND_COOLDOWN_SECONDS + remaining = RESEND_COOLDOWN_SECONDS - elapsed + if remaining > 0: + return get_json_result(data=False, code=settings.RetCode.NOT_EFFECTIVE, message=f"you still have to wait {remaining} seconds") + + # Generate OTP (uppercase letters only) and store hashed + otp = "".join(secrets.choice(string.ascii_uppercase) for _ in range(OTP_LENGTH)) + salt = os.urandom(16) + code_hash = hash_code(otp, salt) + REDIS_CONN.set(k_code, f"{code_hash}:{salt.hex()}", OTP_TTL_SECONDS) + REDIS_CONN.set(k_attempts, 0, OTP_TTL_SECONDS) + REDIS_CONN.set(k_last, now, OTP_TTL_SECONDS) + REDIS_CONN.delete(k_lock) + + ttl_min = OTP_TTL_SECONDS // 60 + + if not smtp_mail_server: + logging.warning("SMTP mail server not initialized; skip sending email.") + else: + try: + send_email_html( + subject="Your Password Reset Code", + to_email=email, + template_key="reset_code", + code=otp, + ttl_min=ttl_min, + ) + except Exception: + return get_json_result(data=False, code=settings.RetCode.SERVER_ERROR, message="failed to send email") + + return get_json_result(data=True, code=settings.RetCode.SUCCESS, message="verification passed, email sent") + + +@manager.route("/forget", methods=["POST"]) # noqa: F821 +def forget(): + """ + POST: Verify email + OTP and reset password, then log the user in. + Request JSON: { email, otp, new_password, confirm_new_password } + """ + req = request.get_json() + email = req.get("email") or "" + otp = (req.get("otp") or "").strip() + new_pwd = req.get("new_password") + new_pwd2 = req.get("confirm_new_password") + + if not all([email, otp, new_pwd, new_pwd2]): + return get_json_result(data=False, code=settings.RetCode.ARGUMENT_ERROR, message="email, otp and passwords are required") + + # For reset, passwords are provided as-is (no decrypt needed) + if new_pwd != new_pwd2: + return get_json_result(data=False, code=settings.RetCode.ARGUMENT_ERROR, message="passwords do not match") + + users = UserService.query(email=email) + if not users: + return get_json_result(data=False, code=settings.RetCode.DATA_ERROR, message="invalid email") + + user = users[0] + # Verify OTP from Redis + k_code, k_attempts, k_last, k_lock = otp_keys(email) + if REDIS_CONN.get(k_lock): + return get_json_result(data=False, code=settings.RetCode.NOT_EFFECTIVE, message="too many attempts, try later") + + stored = REDIS_CONN.get(k_code) + if not stored: + return get_json_result(data=False, code=settings.RetCode.NOT_EFFECTIVE, message="expired otp") + + try: + stored_hash, salt_hex = str(stored).split(":", 1) + salt = bytes.fromhex(salt_hex) + except Exception: + return get_json_result(data=False, code=settings.RetCode.EXCEPTION_ERROR, message="otp storage corrupted") + + # Case-insensitive verification: OTP generated uppercase + calc = hash_code(otp.upper(), salt) + if calc != stored_hash: + # bump attempts + try: + attempts = int(REDIS_CONN.get(k_attempts) or 0) + 1 + except Exception: + attempts = 1 + REDIS_CONN.set(k_attempts, attempts, OTP_TTL_SECONDS) + if attempts >= ATTEMPT_LIMIT: + REDIS_CONN.set(k_lock, int(time.time()), ATTEMPT_LOCK_SECONDS) + return get_json_result(data=False, code=settings.RetCode.AUTHENTICATION_ERROR, message="expired otp") + + # Success: consume OTP and reset password + REDIS_CONN.delete(k_code) + REDIS_CONN.delete(k_attempts) + REDIS_CONN.delete(k_last) + REDIS_CONN.delete(k_lock) + + try: + UserService.update_user_password(user.id, new_pwd) + except Exception as e: + logging.exception(e) + return get_json_result(data=False, code=settings.RetCode.EXCEPTION_ERROR, message="failed to reset password") + + # Auto login (reuse login flow) + user.access_token = get_uuid() + login_user(user) + user.update_time = (current_timestamp(),) + user.update_date = (datetime_format(datetime.now()),) + user.save() + msg = "Password reset successful. Logged in." + return construct_response(data=user.to_json(), auth=user.get_id(), message=msg) diff --git a/api/apps/user_app_fastapi.py b/api/apps/user_app_fastapi.py index 4525bee..16e98a8 100644 --- a/api/apps/user_app_fastapi.py +++ b/api/apps/user_app_fastapi.py @@ -21,8 +21,7 @@ from datetime import datetime from typing import Optional, Dict, Any from fastapi import APIRouter, Depends, HTTPException, Request, Response, status -from fastapi.security import HTTPAuthorizationCredentials -from api.utils.api_utils import security +from api.apps.models.auth_dependencies import get_current_user from fastapi.responses import RedirectResponse from pydantic import BaseModel, EmailStr try: @@ -89,63 +88,7 @@ class TenantInfoRequest(BaseModel): img2txt_id: str llm_id: str -# 依赖项:获取当前用户 -async def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(security)): - """获取当前用户""" - from api.db import StatusEnum - try: - from itsdangerous.url_safe import URLSafeTimedSerializer as Serializer - except ImportError: - # 如果没有itsdangerous,使用jwt作为替代 - import jwt - Serializer = jwt - - jwt = Serializer(secret_key=settings.SECRET_KEY) - authorization = credentials.credentials - - if authorization: - try: - access_token = str(jwt.loads(authorization)) - - if not access_token or not access_token.strip(): - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Authentication attempt with empty access token" - ) - - # Access tokens should be UUIDs (32 hex characters) - if len(access_token.strip()) < 32: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail=f"Authentication attempt with invalid token format: {len(access_token)} chars" - ) - - user = UserService.query( - access_token=access_token, status=StatusEnum.VALID.value - ) - if user: - if not user[0].access_token or not user[0].access_token.strip(): - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail=f"User {user[0].email} has empty access_token in database" - ) - return user[0] - else: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Invalid access token" - ) - except Exception as e: - logging.warning(f"load_user got exception {e}") - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Invalid access token" - ) - else: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Authorization header required" - ) +# 依赖项:获取当前用户 - 从 auth_dependencies 导入 @router.post("/login") async def login(request: LoginRequest): diff --git a/api/common/exceptions.py b/api/common/exceptions.py index 5ce0e0b..508fb55 100644 --- a/api/common/exceptions.py +++ b/api/common/exceptions.py @@ -1,3 +1,20 @@ +# +# Copyright 2025 The InfiniFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + class AdminException(Exception): def __init__(self, message, code=400): super().__init__(message) @@ -18,4 +35,9 @@ class UserAlreadyExistsError(AdminException): class CannotDeleteAdminError(AdminException): def __init__(self): - super().__init__("Cannot delete admin account", 403) \ No newline at end of file + super().__init__("Cannot delete admin account", 403) + + +class NotAdminError(AdminException): + def __init__(self, username): + super().__init__(f"User '{username}' is not admin", 403) diff --git a/api/db/db_models.py b/api/db/db_models.py index d31586d..6f2529e 100644 --- a/api/db/db_models.py +++ b/api/db/db_models.py @@ -313,9 +313,75 @@ class RetryingPooledMySQLDatabase(PooledMySQLDatabase): raise +class RetryingPooledPostgresqlDatabase(PooledPostgresqlDatabase): + def __init__(self, *args, **kwargs): + self.max_retries = kwargs.pop("max_retries", 5) + self.retry_delay = kwargs.pop("retry_delay", 1) + super().__init__(*args, **kwargs) + + def execute_sql(self, sql, params=None, commit=True): + for attempt in range(self.max_retries + 1): + try: + return super().execute_sql(sql, params, commit) + except (OperationalError, InterfaceError) as e: + # PostgreSQL specific error codes + # 57P01: admin_shutdown + # 57P02: crash_shutdown + # 57P03: cannot_connect_now + # 08006: connection_failure + # 08003: connection_does_not_exist + # 08000: connection_exception + error_messages = ['connection', 'server closed', 'connection refused', + 'no connection to the server', 'terminating connection'] + + should_retry = any(msg in str(e).lower() for msg in error_messages) + + if should_retry and attempt < self.max_retries: + logging.warning( + f"PostgreSQL connection issue (attempt {attempt+1}/{self.max_retries}): {e}" + ) + self._handle_connection_loss() + time.sleep(self.retry_delay * (2 ** attempt)) + else: + logging.error(f"PostgreSQL execution failure: {e}") + raise + return None + + def _handle_connection_loss(self): + try: + self.close() + except Exception: + pass + try: + self.connect() + except Exception as e: + logging.error(f"Failed to reconnect to PostgreSQL: {e}") + time.sleep(0.1) + self.connect() + + def begin(self): + for attempt in range(self.max_retries + 1): + try: + return super().begin() + except (OperationalError, InterfaceError) as e: + error_messages = ['connection', 'server closed', 'connection refused', + 'no connection to the server', 'terminating connection'] + + should_retry = any(msg in str(e).lower() for msg in error_messages) + + if should_retry and attempt < self.max_retries: + logging.warning( + f"PostgreSQL connection lost during transaction (attempt {attempt+1}/{self.max_retries})" + ) + self._handle_connection_loss() + time.sleep(self.retry_delay * (2 ** attempt)) + else: + raise + + class PooledDatabase(Enum): MYSQL = RetryingPooledMySQLDatabase - POSTGRES = PooledPostgresqlDatabase + POSTGRES = RetryingPooledPostgresqlDatabase class DatabaseMigrator(Enum): @@ -329,12 +395,11 @@ class BaseDataBase: database_config = settings.DATABASE.copy() db_name = database_config.pop("name") - # pool_config = { - # 'max_retries': 5, - # 'retry_delay': 1, - # } - # database_config.update(pool_config) - + pool_config = { + 'max_retries': 5, + 'retry_delay': 1, + } + database_config.update(pool_config) self.database_connection = PooledDatabase[settings.DATABASE_TYPE.upper()].value( db_name, **database_config ) @@ -642,7 +707,7 @@ class TenantLLM(DataBaseModel): llm_factory = CharField(max_length=128, null=False, help_text="LLM factory name", index=True) model_type = CharField(max_length=128, null=True, help_text="LLM, Text Embedding, Image2Text, ASR", index=True) llm_name = CharField(max_length=128, null=True, help_text="LLM name", default="", index=True) - api_key = CharField(max_length=2048, null=True, help_text="API KEY", index=True) + api_key = TextField(null=True, help_text="API KEY") api_base = CharField(max_length=255, null=True, help_text="API Base") max_tokens = IntegerField(default=8192, index=True) used_tokens = IntegerField(default=0, index=True) @@ -1143,4 +1208,8 @@ def migrate_db(): migrate(migrator.add_column("knowledgebase", "mindmap_task_finish_at", CharField(null=True))) except Exception: pass + try: + migrate(migrator.alter_column_type("tenant_llm", "api_key", TextField(null=True, help_text="API KEY"))) + except Exception: + pass logging.disable(logging.NOTSET) diff --git a/api/db/services/canvas_service.py b/api/db/services/canvas_service.py index f72c6f9..4c9da3a 100644 --- a/api/db/services/canvas_service.py +++ b/api/db/services/canvas_service.py @@ -143,15 +143,12 @@ class UserCanvasService(CommonService): ] if keywords: agents = cls.model.select(*fields).join(User, on=(cls.model.user_id == User.id)).where( - cls.model.user_id.in_(joined_tenant_ids), - fn.LOWER(cls.model.title).contains(keywords.lower()) - #(((cls.model.user_id.in_(joined_tenant_ids)) & (cls.model.permission == TenantPermission.TEAM.value)) | (cls.model.user_id == user_id)), - #(fn.LOWER(cls.model.title).contains(keywords.lower())) + (((cls.model.user_id.in_(joined_tenant_ids)) & (cls.model.permission == TenantPermission.TEAM.value)) | (cls.model.user_id == user_id)), + (fn.LOWER(cls.model.title).contains(keywords.lower())) ) else: agents = cls.model.select(*fields).join(User, on=(cls.model.user_id == User.id)).where( - cls.model.user_id.in_(joined_tenant_ids) - #(((cls.model.user_id.in_(joined_tenant_ids)) & (cls.model.permission == TenantPermission.TEAM.value)) | (cls.model.user_id == user_id)) + (((cls.model.user_id.in_(joined_tenant_ids)) & (cls.model.permission == TenantPermission.TEAM.value)) | (cls.model.user_id == user_id)) ) if canvas_category: agents = agents.where(cls.model.canvas_category == canvas_category) diff --git a/api/db/services/dialog_service.py b/api/db/services/dialog_service.py index 673000f..9bde623 100644 --- a/api/db/services/dialog_service.py +++ b/api/db/services/dialog_service.py @@ -370,7 +370,7 @@ def chat(dialog, messages, stream=True, **kwargs): chat_mdl.bind_tools(toolcall_session, tools) bind_models_ts = timer() - retriever = settings.retrievaler + retriever = settings.retriever questions = [m["content"] for m in messages if m["role"] == "user"][-3:] attachments = kwargs["doc_ids"].split(",") if "doc_ids" in kwargs else [] if "doc_ids" in messages[-1]: @@ -466,13 +466,17 @@ def chat(dialog, messages, stream=True, **kwargs): rerank_mdl=rerank_mdl, rank_feature=label_question(" ".join(questions), kbs), ) + if prompt_config.get("toc_enhance"): + cks = retriever.retrieval_by_toc(" ".join(questions), kbinfos["chunks"], tenant_ids, chat_mdl, dialog.top_n) + if cks: + kbinfos["chunks"] = cks if prompt_config.get("tavily_api_key"): tav = Tavily(prompt_config["tavily_api_key"]) tav_res = tav.retrieve_chunks(" ".join(questions)) kbinfos["chunks"].extend(tav_res["chunks"]) kbinfos["doc_aggs"].extend(tav_res["doc_aggs"]) if prompt_config.get("use_kg"): - ck = settings.kg_retrievaler.retrieval(" ".join(questions), tenant_ids, dialog.kb_ids, embd_mdl, + ck = settings.kg_retriever.retrieval(" ".join(questions), tenant_ids, dialog.kb_ids, embd_mdl, LLMBundle(dialog.tenant_id, LLMType.CHAT)) if ck["content_with_weight"]: kbinfos["chunks"].insert(0, ck) @@ -658,7 +662,7 @@ Please write the SQL, only SQL, without any other explanations or text. logging.debug(f"{question} get SQL(refined): {sql}") tried_times += 1 - return settings.retrievaler.sql_retrieval(sql, format="json"), sql + return settings.retriever.sql_retrieval(sql, format="json"), sql tbl, sql = get_table() if tbl is None: @@ -752,7 +756,7 @@ def ask(question, kb_ids, tenant_id, chat_llm_name=None, search_config={}): embedding_list = list(set([kb.embd_id for kb in kbs])) is_knowledge_graph = all([kb.parser_id == ParserType.KG for kb in kbs]) - retriever = settings.retrievaler if not is_knowledge_graph else settings.kg_retrievaler + retriever = settings.retriever if not is_knowledge_graph else settings.kg_retriever embd_mdl = LLMBundle(tenant_id, LLMType.EMBEDDING, embedding_list[0]) chat_mdl = LLMBundle(tenant_id, LLMType.CHAT, chat_llm_name) @@ -848,7 +852,7 @@ def gen_mindmap(question, kb_ids, tenant_id, search_config={}): if not doc_ids: doc_ids = None - ranks = settings.retrievaler.retrieval( + ranks = settings.retriever.retrieval( question=question, embd_mdl=embd_mdl, tenant_ids=tenant_ids, diff --git a/api/db/services/document_service.py b/api/db/services/document_service.py index 61d1c6e..660dee6 100644 --- a/api/db/services/document_service.py +++ b/api/db/services/document_service.py @@ -79,7 +79,7 @@ class DocumentService(CommonService): @classmethod @DB.connection_context() def get_list(cls, kb_id, page_number, items_per_page, - orderby, desc, keywords, id, name): + orderby, desc, keywords, id, name, suffix=None, run = None): fields = cls.get_cls_model_fields() docs = cls.model.select(*[*fields, UserCanvas.title]).join(File2Document, on = (File2Document.document_id == cls.model.id))\ .join(File, on = (File.id == File2Document.file_id))\ @@ -96,6 +96,10 @@ class DocumentService(CommonService): docs = docs.where( fn.LOWER(cls.model.name).contains(keywords.lower()) ) + if suffix: + docs = docs.where(cls.model.suffix.in_(suffix)) + if run: + docs = docs.where(cls.model.run.in_(run)) if desc: docs = docs.order_by(cls.model.getter_by(orderby).desc()) else: @@ -667,9 +671,11 @@ class DocumentService(CommonService): @classmethod @DB.connection_context() def _sync_progress(cls, docs:list[dict]): + from api.db.services.task_service import TaskService + for d in docs: try: - tsks = Task.query(doc_id=d["id"], order_by=Task.create_time) + tsks = TaskService.query(doc_id=d["id"], order_by=Task.create_time) if not tsks: continue msg = [] @@ -787,21 +793,23 @@ class DocumentService(CommonService): "cancelled": int(cancelled), } -def queue_raptor_o_graphrag_tasks(doc, ty, priority, fake_doc_id="", doc_ids=[]): +def queue_raptor_o_graphrag_tasks(sample_doc_id, ty, priority, fake_doc_id="", doc_ids=[]): """ You can provide a fake_doc_id to bypass the restriction of tasks at the knowledgebase level. Optionally, specify a list of doc_ids to determine which documents participate in the task. """ - chunking_config = DocumentService.get_chunking_config(doc["id"]) + assert ty in ["graphrag", "raptor", "mindmap"], "type should be graphrag, raptor or mindmap" + + chunking_config = DocumentService.get_chunking_config(sample_doc_id["id"]) hasher = xxhash.xxh64() for field in sorted(chunking_config.keys()): hasher.update(str(chunking_config[field]).encode("utf-8")) def new_task(): - nonlocal doc + nonlocal sample_doc_id return { "id": get_uuid(), - "doc_id": fake_doc_id if fake_doc_id else doc["id"], + "doc_id": sample_doc_id["id"], "from_page": 100000000, "to_page": 100000000, "task_type": ty, @@ -816,9 +824,9 @@ def queue_raptor_o_graphrag_tasks(doc, ty, priority, fake_doc_id="", doc_ids=[]) task["digest"] = hasher.hexdigest() bulk_insert_into_db(Task, [task], True) - if ty in ["graphrag", "raptor", "mindmap"]: - task["doc_ids"] = doc_ids - DocumentService.begin2parse(doc["id"]) + task["doc_id"] = fake_doc_id + task["doc_ids"] = doc_ids + DocumentService.begin2parse(sample_doc_id["id"]) assert REDIS_CONN.queue_product(get_svr_queue_name(priority), message=task), "Can't access Redis. Please check the Redis' status." return task["id"] @@ -830,7 +838,7 @@ def get_queue_length(priority): return int(group_info.get("lag", 0) or 0) -async def doc_upload_and_parse(conversation_id, file_objs, user_id): +def doc_upload_and_parse(conversation_id, file_objs, user_id): from api.db.services.api_service import API4ConversationService from api.db.services.conversation_service import ConversationService from api.db.services.dialog_service import DialogService @@ -855,7 +863,7 @@ async def doc_upload_and_parse(conversation_id, file_objs, user_id): embd_mdl = LLMBundle(kb.tenant_id, LLMType.EMBEDDING, llm_name=kb.embd_id, lang=kb.language) - err, files = await FileService.upload_document(kb, file_objs, user_id) + err, files = FileService.upload_document(kb, file_objs, user_id) assert not err, "\n".join(err) def dummy(prog=None, msg=""): diff --git a/api/db/services/file_service.py b/api/db/services/file_service.py index 6e40642..50b1266 100644 --- a/api/db/services/file_service.py +++ b/api/db/services/file_service.py @@ -15,7 +15,6 @@ # import logging import re -import traceback from concurrent.futures import ThreadPoolExecutor from pathlib import Path @@ -421,7 +420,7 @@ class FileService(CommonService): @classmethod @DB.connection_context() - async def upload_document(self, kb, file_objs, user_id): + def upload_document(self, kb, file_objs, user_id): root_folder = self.get_root_folder(user_id) pf_id = root_folder["id"] self.init_knowledgebase_docs(pf_id, user_id) @@ -441,7 +440,14 @@ class FileService(CommonService): while STORAGE_IMPL.obj_exist(kb.id, location): location += "_" - blob = await file.read() + # 支持 FastAPI UploadFile,直接使用 file 属性进行同步读取 + if hasattr(file, 'file') and hasattr(file, 'filename'): + # FastAPI UploadFile + file.file.seek(0) + blob = file.file.read() + else: + # 普通文件对象 + blob = file.read() if filetype == FileType.PDF.value: blob = read_potential_broken_pdf(blob) STORAGE_IMPL.put(kb.id, location, blob) @@ -473,33 +479,33 @@ class FileService(CommonService): FileService.add_file_from_kb(doc, kb_folder["id"], kb.tenant_id) files.append((doc, blob)) except Exception as e: - traceback.print_exc() err.append(file.filename + ": " + str(e)) return err, files + @classmethod + @DB.connection_context() + def list_all_files_by_parent_id(cls, parent_id): + try: + files = cls.model.select().where((cls.model.parent_id == parent_id) & (cls.model.id != parent_id)) + return list(files) + except Exception: + logging.exception("list_by_parent_id failed") + raise RuntimeError("Database error (list_by_parent_id)!") + @staticmethod - async def parse_docs(file_objs, user_id): + def parse_docs(file_objs, user_id): exe = ThreadPoolExecutor(max_workers=12) threads = [] for file in file_objs: - # Check if file has async read method (UploadFile) - if hasattr(file, 'read') and hasattr(file.read, '__call__'): - try: - # Try to get the coroutine to check if it's async - read_result = file.read() - if hasattr(read_result, '__await__'): - # It's an async method, await it - blob = await read_result - else: - # It's a sync method - blob = read_result - except Exception: - # Fallback to sync read - blob = file.read() + # 支持 FastAPI UploadFile,直接使用 file 属性进行同步读取 + if hasattr(file, 'file') and hasattr(file, 'filename'): + # FastAPI UploadFile + file.file.seek(0) + blob = file.file.read() else: + # 普通文件对象 blob = file.read() - threads.append(exe.submit(FileService.parse, file.filename, blob, False)) res = [] diff --git a/api/db/services/knowledgebase_service.py b/api/db/services/knowledgebase_service.py index 492c245..f8c1035 100644 --- a/api/db/services/knowledgebase_service.py +++ b/api/db/services/knowledgebase_service.py @@ -379,6 +379,7 @@ class KnowledgebaseService(CommonService): # name: Optional name filter # Returns: # List of knowledge bases + # Total count of knowledge bases kbs = cls.model.select() if id: kbs = kbs.where(cls.model.id == id) @@ -390,14 +391,16 @@ class KnowledgebaseService(CommonService): cls.model.tenant_id == user_id)) & (cls.model.status == StatusEnum.VALID.value) ) + if desc: kbs = kbs.order_by(cls.model.getter_by(orderby).desc()) else: kbs = kbs.order_by(cls.model.getter_by(orderby).asc()) + total = kbs.count() kbs = kbs.paginate(page_number, items_per_page) - return list(kbs.dicts()) + return list(kbs.dicts()), total @classmethod @DB.connection_context() diff --git a/api/db/services/llm_service.py b/api/db/services/llm_service.py index 76c4cb4..bdad306 100644 --- a/api/db/services/llm_service.py +++ b/api/db/services/llm_service.py @@ -205,32 +205,31 @@ class LLMBundle(LLM4Tenant): return txt return txt[last_think_end + len("") :] - + @staticmethod def _clean_param(chat_partial, **kwargs): func = chat_partial.func sig = inspect.signature(func) - keyword_args = [] support_var_args = False - for param in sig.parameters.values(): - if param.kind == inspect.Parameter.VAR_KEYWORD or param.kind == inspect.Parameter.VAR_POSITIONAL: - support_var_args = True - elif param.kind == inspect.Parameter.KEYWORD_ONLY: - keyword_args.append(param.name) + allowed_params = set() - use_kwargs = kwargs - if not support_var_args: - use_kwargs = {k: v for k, v in kwargs.items() if k in keyword_args} - return use_kwargs - + for param in sig.parameters.values(): + if param.kind == inspect.Parameter.VAR_KEYWORD: + support_var_args = True + elif param.kind in (inspect.Parameter.POSITIONAL_OR_KEYWORD, inspect.Parameter.KEYWORD_ONLY): + allowed_params.add(param.name) + if support_var_args: + return kwargs + else: + return {k: v for k, v in kwargs.items() if k in allowed_params} def chat(self, system: str, history: list, gen_conf: dict = {}, **kwargs) -> str: if self.langfuse: generation = self.langfuse.start_generation(trace_context=self.trace_context, name="chat", model=self.llm_name, input={"system": system, "history": history}) - chat_partial = partial(self.mdl.chat, system, history, gen_conf) + chat_partial = partial(self.mdl.chat, system, history, gen_conf, **kwargs) if self.is_tools and self.mdl.is_tools: - chat_partial = partial(self.mdl.chat_with_tools, system, history, gen_conf) - + chat_partial = partial(self.mdl.chat_with_tools, system, history, gen_conf, **kwargs) + use_kwargs = self._clean_param(chat_partial, **kwargs) txt, used_tokens = chat_partial(**use_kwargs) txt = self._remove_reasoning_content(txt) @@ -266,7 +265,7 @@ class LLMBundle(LLM4Tenant): break if txt.endswith(""): - ans = ans.rstrip("") + ans = ans[: -len("")] if not self.verbose_tool_use: txt = re.sub(r".*?", "", txt, flags=re.DOTALL) diff --git a/api/db/services/mcp_server_service.py b/api/db/services/mcp_server_service.py index 101555f..1eae882 100644 --- a/api/db/services/mcp_server_service.py +++ b/api/db/services/mcp_server_service.py @@ -33,7 +33,8 @@ class MCPServerService(CommonService): @classmethod @DB.connection_context() - def get_servers(cls, tenant_id: str, id_list: list[str] | None, page_number, items_per_page, orderby, desc, keywords): + def get_servers(cls, tenant_id: str, id_list: list[str] | None, page_number, items_per_page, orderby, desc, + keywords): """Retrieve all MCP servers associated with a tenant. This method fetches all MCP servers for a given tenant, ordered by creation time. diff --git a/api/db/services/search_service.py b/api/db/services/search_service.py index acb07da..de69f28 100644 --- a/api/db/services/search_service.py +++ b/api/db/services/search_service.py @@ -94,7 +94,8 @@ class SearchService(CommonService): query = ( cls.model.select(*fields) .join(User, on=(cls.model.tenant_id == User.id)) - .where(((cls.model.tenant_id.in_(joined_tenant_ids)) | (cls.model.tenant_id == user_id)) & (cls.model.status == StatusEnum.VALID.value)) + .where(((cls.model.tenant_id.in_(joined_tenant_ids)) | (cls.model.tenant_id == user_id)) & ( + cls.model.status == StatusEnum.VALID.value)) ) if keywords: diff --git a/api/db/services/task_service.py b/api/db/services/task_service.py index f31494b..b0fc2a1 100644 --- a/api/db/services/task_service.py +++ b/api/db/services/task_service.py @@ -165,7 +165,7 @@ class TaskService(CommonService): ] tasks = ( cls.model.select(*fields).order_by(cls.model.from_page.asc(), cls.model.create_time.desc()) - .where(cls.model.doc_id == doc_id) + .where(cls.model.doc_id == doc_id) ) tasks = list(tasks.dicts()) if not tasks: @@ -205,18 +205,18 @@ class TaskService(CommonService): cls.model.select( *[Document.id, Document.kb_id, Document.location, File.parent_id] ) - .join(Document, on=(cls.model.doc_id == Document.id)) - .join( + .join(Document, on=(cls.model.doc_id == Document.id)) + .join( File2Document, on=(File2Document.document_id == Document.id), join_type=JOIN.LEFT_OUTER, ) - .join( + .join( File, on=(File2Document.file_id == File.id), join_type=JOIN.LEFT_OUTER, ) - .where( + .where( Document.status == StatusEnum.VALID.value, Document.run == TaskStatus.RUNNING.value, ~(Document.type == FileType.VIRTUAL.value), @@ -294,8 +294,8 @@ class TaskService(CommonService): cls.model.update(progress=prog).where( (cls.model.id == id) & ( - (cls.model.progress != -1) & - ((prog == -1) | (prog > cls.model.progress)) + (cls.model.progress != -1) & + ((prog == -1) | (prog > cls.model.progress)) ) ).execute() else: @@ -343,6 +343,7 @@ def queue_tasks(doc: dict, bucket: str, name: str, priority: int): - Task digests are calculated for optimization and reuse - Previous task chunks may be reused if available """ + def new_task(): return { "id": get_uuid(), @@ -350,7 +351,7 @@ def queue_tasks(doc: dict, bucket: str, name: str, priority: int): "progress": 0.0, "from_page": 0, "to_page": 100000000, - "begin_at": datetime.now(), + "begin_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), } parse_task_array = [] @@ -502,7 +503,7 @@ def queue_dataflow(tenant_id:str, flow_id:str, task_id:str, doc_id:str=CANVAS_DE to_page=100000000, task_type="dataflow" if not rerun else "dataflow_rerun", priority=priority, - begin_at=datetime.now(), + begin_at= datetime.now().strftime("%Y-%m-%d %H:%M:%S"), ) if doc_id not in [CANVAS_DEBUG_DOC_ID, GRAPH_RAPTOR_FAKE_DOC_ID]: TaskService.model.delete().where(TaskService.model.doc_id == doc_id).execute() @@ -515,7 +516,7 @@ def queue_dataflow(tenant_id:str, flow_id:str, task_id:str, doc_id:str=CANVAS_DE task["file"] = file if not REDIS_CONN.queue_product( - get_svr_queue_name(priority), message=task + get_svr_queue_name(priority), message=task ): return False, "Can't access Redis. Please check the Redis' status." diff --git a/api/db/services/tenant_llm_service.py b/api/db/services/tenant_llm_service.py index 4eca970..6e826eb 100644 --- a/api/db/services/tenant_llm_service.py +++ b/api/db/services/tenant_llm_service.py @@ -57,8 +57,10 @@ class TenantLLMService(CommonService): @classmethod @DB.connection_context() def get_my_llms(cls, tenant_id): - fields = [cls.model.llm_factory, LLMFactories.logo, LLMFactories.tags, cls.model.model_type, cls.model.llm_name, cls.model.used_tokens] - objs = cls.model.select(*fields).join(LLMFactories, on=(cls.model.llm_factory == LLMFactories.name)).where(cls.model.tenant_id == tenant_id, ~cls.model.api_key.is_null()).dicts() + fields = [cls.model.llm_factory, LLMFactories.logo, LLMFactories.tags, cls.model.model_type, cls.model.llm_name, + cls.model.used_tokens] + objs = cls.model.select(*fields).join(LLMFactories, on=(cls.model.llm_factory == LLMFactories.name)).where( + cls.model.tenant_id == tenant_id, ~cls.model.api_key.is_null()).dicts() return list(objs) @@ -122,7 +124,8 @@ class TenantLLMService(CommonService): model_config = {"llm_factory": llm[0].fid, "api_key": "", "llm_name": mdlnm, "api_base": ""} if not model_config: if mdlnm == "flag-embedding": - model_config = {"llm_factory": "Tongyi-Qianwen", "api_key": "", "llm_name": llm_name, "api_base": ""} + model_config = {"llm_factory": "Tongyi-Qianwen", "api_key": "", "llm_name": llm_name, + "api_base": ""} else: if not mdlnm: raise LookupError(f"Type of {llm_type} model is not set.") @@ -137,27 +140,33 @@ class TenantLLMService(CommonService): if llm_type == LLMType.EMBEDDING.value: if model_config["llm_factory"] not in EmbeddingModel: return - return EmbeddingModel[model_config["llm_factory"]](model_config["api_key"], model_config["llm_name"], base_url=model_config["api_base"]) + return EmbeddingModel[model_config["llm_factory"]](model_config["api_key"], model_config["llm_name"], + base_url=model_config["api_base"]) if llm_type == LLMType.RERANK: if model_config["llm_factory"] not in RerankModel: return - return RerankModel[model_config["llm_factory"]](model_config["api_key"], model_config["llm_name"], base_url=model_config["api_base"]) + return RerankModel[model_config["llm_factory"]](model_config["api_key"], model_config["llm_name"], + base_url=model_config["api_base"]) if llm_type == LLMType.IMAGE2TEXT.value: if model_config["llm_factory"] not in CvModel: return - return CvModel[model_config["llm_factory"]](model_config["api_key"], model_config["llm_name"], lang, base_url=model_config["api_base"], **kwargs) + return CvModel[model_config["llm_factory"]](model_config["api_key"], model_config["llm_name"], lang, + base_url=model_config["api_base"], **kwargs) if llm_type == LLMType.CHAT.value: if model_config["llm_factory"] not in ChatModel: return - return ChatModel[model_config["llm_factory"]](model_config["api_key"], model_config["llm_name"], base_url=model_config["api_base"], **kwargs) + return ChatModel[model_config["llm_factory"]](model_config["api_key"], model_config["llm_name"], + base_url=model_config["api_base"], **kwargs) if llm_type == LLMType.SPEECH2TEXT: if model_config["llm_factory"] not in Seq2txtModel: return - return Seq2txtModel[model_config["llm_factory"]](key=model_config["api_key"], model_name=model_config["llm_name"], lang=lang, base_url=model_config["api_base"]) + return Seq2txtModel[model_config["llm_factory"]](key=model_config["api_key"], + model_name=model_config["llm_name"], lang=lang, + base_url=model_config["api_base"]) if llm_type == LLMType.TTS: if model_config["llm_factory"] not in TTSModel: return @@ -194,11 +203,14 @@ class TenantLLMService(CommonService): try: num = ( cls.model.update(used_tokens=cls.model.used_tokens + used_tokens) - .where(cls.model.tenant_id == tenant_id, cls.model.llm_name == llm_name, cls.model.llm_factory == llm_factory if llm_factory else True) + .where(cls.model.tenant_id == tenant_id, cls.model.llm_name == llm_name, + cls.model.llm_factory == llm_factory if llm_factory else True) .execute() ) except Exception: - logging.exception("TenantLLMService.increase_usage got exception,Failed to update used_tokens for tenant_id=%s, llm_name=%s", tenant_id, llm_name) + logging.exception( + "TenantLLMService.increase_usage got exception,Failed to update used_tokens for tenant_id=%s, llm_name=%s", + tenant_id, llm_name) return 0 return num @@ -206,7 +218,9 @@ class TenantLLMService(CommonService): @classmethod @DB.connection_context() def get_openai_models(cls): - objs = cls.model.select().where((cls.model.llm_factory == "OpenAI"), ~(cls.model.llm_name == "text-embedding-3-small"), ~(cls.model.llm_name == "text-embedding-3-large")).dicts() + objs = cls.model.select().where((cls.model.llm_factory == "OpenAI"), + ~(cls.model.llm_name == "text-embedding-3-small"), + ~(cls.model.llm_name == "text-embedding-3-large")).dicts() return list(objs) @classmethod @@ -250,8 +264,9 @@ class LLM4Tenant: langfuse_keys = TenantLangfuseService.filter_by_tenant(tenant_id=tenant_id) self.langfuse = None if langfuse_keys: - langfuse = Langfuse(public_key=langfuse_keys.public_key, secret_key=langfuse_keys.secret_key, host=langfuse_keys.host) + langfuse = Langfuse(public_key=langfuse_keys.public_key, secret_key=langfuse_keys.secret_key, + host=langfuse_keys.host) if langfuse.auth_check(): self.langfuse = langfuse trace_id = self.langfuse.create_trace_id() - self.trace_context = {"trace_id": trace_id} \ No newline at end of file + self.trace_context = {"trace_id": trace_id} diff --git a/api/db/services/user_canvas_version.py b/api/db/services/user_canvas_version.py index 9696a78..89f7326 100644 --- a/api/db/services/user_canvas_version.py +++ b/api/db/services/user_canvas_version.py @@ -2,22 +2,22 @@ from api.db.db_models import UserCanvasVersion, DB from api.db.services.common_service import CommonService from peewee import DoesNotExist + class UserCanvasVersionService(CommonService): model = UserCanvasVersion - - + @classmethod @DB.connection_context() def list_by_canvas_id(cls, user_canvas_id): try: user_canvas_version = cls.model.select( - *[cls.model.id, - cls.model.create_time, - cls.model.title, - cls.model.create_date, - cls.model.update_date, - cls.model.user_canvas_id, - cls.model.update_time] + *[cls.model.id, + cls.model.create_time, + cls.model.title, + cls.model.create_date, + cls.model.update_date, + cls.model.user_canvas_id, + cls.model.update_time] ).where(cls.model.user_canvas_id == user_canvas_id) return user_canvas_version except DoesNotExist: @@ -46,18 +46,16 @@ class UserCanvasVersionService(CommonService): @DB.connection_context() def delete_all_versions(cls, user_canvas_id): try: - user_canvas_version = cls.model.select().where(cls.model.user_canvas_id == user_canvas_id).order_by(cls.model.create_time.desc()) + user_canvas_version = cls.model.select().where(cls.model.user_canvas_id == user_canvas_id).order_by( + cls.model.create_time.desc()) if user_canvas_version.count() > 20: delete_ids = [] for i in range(20, user_canvas_version.count()): delete_ids.append(user_canvas_version[i].id) - + cls.delete_by_ids(delete_ids) return True except DoesNotExist: return None except Exception: return None - - - diff --git a/api/models/chunk_models.py b/api/models/chunk_models.py deleted file mode 100644 index 8daf956..0000000 --- a/api/models/chunk_models.py +++ /dev/null @@ -1,88 +0,0 @@ -# -# Copyright 2024 The InfiniFlow Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -from typing import Optional, List, Dict, Any -from pydantic import BaseModel, Field - - -class ListChunkRequest(BaseModel): - """列出块请求模型""" - doc_id: str = Field(..., description="文档ID") - page: Optional[int] = Field(1, description="页码") - size: Optional[int] = Field(30, description="每页大小") - keywords: Optional[str] = Field("", description="关键词") - available_int: Optional[int] = Field(None, description="可用性状态") - - -class GetChunkRequest(BaseModel): - """获取块请求模型""" - chunk_id: str = Field(..., description="块ID") - - -class SetChunkRequest(BaseModel): - """设置块请求模型""" - doc_id: str = Field(..., description="文档ID") - chunk_id: str = Field(..., description="块ID") - content_with_weight: str = Field(..., description="带权重的内容") - important_kwd: Optional[List[str]] = Field(None, description="重要关键词") - question_kwd: Optional[List[str]] = Field(None, description="问题关键词") - tag_kwd: Optional[str] = Field(None, description="标签关键词") - tag_feas: Optional[Any] = Field(None, description="标签特征") - available_int: Optional[int] = Field(None, description="可用性状态") - - -class SwitchChunkRequest(BaseModel): - """切换块状态请求模型""" - chunk_ids: List[str] = Field(..., description="块ID列表") - available_int: int = Field(..., description="可用性状态") - doc_id: str = Field(..., description="文档ID") - - -class RemoveChunkRequest(BaseModel): - """删除块请求模型""" - chunk_ids: List[str] = Field(..., description="块ID列表") - doc_id: str = Field(..., description="文档ID") - - -class CreateChunkRequest(BaseModel): - """创建块请求模型""" - doc_id: str = Field(..., description="文档ID") - content_with_weight: str = Field(..., description="带权重的内容") - important_kwd: Optional[List[str]] = Field([], description="重要关键词") - question_kwd: Optional[List[str]] = Field([], description="问题关键词") - tag_feas: Optional[Any] = Field(None, description="标签特征") - - -class RetrievalTestRequest(BaseModel): - """检索测试请求模型""" - kb_id: List[str] = Field(..., description="知识库ID列表") - question: str = Field(..., description="问题") - page: Optional[int] = Field(1, description="页码") - size: Optional[int] = Field(30, description="每页大小") - doc_ids: Optional[List[str]] = Field([], description="文档ID列表") - use_kg: Optional[bool] = Field(False, description="是否使用知识图谱") - top_k: Optional[int] = Field(1024, description="返回数量") - cross_languages: Optional[List[str]] = Field([], description="跨语言列表") - search_id: Optional[str] = Field("", description="搜索ID") - rerank_id: Optional[str] = Field(None, description="重排序ID") - keyword: Optional[bool] = Field(False, description="是否使用关键词") - similarity_threshold: Optional[float] = Field(0.0, description="相似度阈值") - vector_similarity_weight: Optional[float] = Field(0.3, description="向量相似度权重") - highlight: Optional[bool] = Field(None, description="是否高亮") - - -class KnowledgeGraphRequest(BaseModel): - """知识图谱请求模型""" - doc_id: str = Field(..., description="文档ID") diff --git a/api/models/kb_models.py b/api/models/kb_models.py deleted file mode 100644 index a66bff4..0000000 --- a/api/models/kb_models.py +++ /dev/null @@ -1,98 +0,0 @@ -# -# Copyright 2024 The InfiniFlow Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -from typing import Optional, List, Dict, Any -from pydantic import BaseModel, Field - - -class CreateKnowledgeBaseRequest(BaseModel): - """创建知识库请求模型""" - name: str = Field(..., description="知识库名称") - description: Optional[str] = Field(None, description="知识库描述") - parser_id: Optional[str] = Field("naive", description="解析器ID") - parser_config: Optional[Dict[str, Any]] = Field(None, description="解析器配置") - embd_id: Optional[str] = Field(None, description="嵌入模型ID") - - -class UpdateKnowledgeBaseRequest(BaseModel): - """更新知识库请求模型""" - kb_id: str = Field(..., description="知识库ID") - name: str = Field(..., description="知识库名称") - description: Optional[str] = Field(None, description="知识库描述") - permission: Optional[str] = Field(None, description="权限设置") - avatar: Optional[str] = Field(None, description="头像base64字符串") - parser_id: Optional[str] = Field(None, description="解析器ID") - embd_id: Optional[str] = Field(None, description="嵌入模型ID") - parser_config: Optional[Dict[str, Any]] = Field(None, description="解析器配置") - pagerank: Optional[int] = Field(0, description="页面排名") - - -class DeleteKnowledgeBaseRequest(BaseModel): - """删除知识库请求模型""" - kb_id: str = Field(..., description="知识库ID") - - -class ListKnowledgeBasesRequest(BaseModel): - """列出知识库请求模型""" - owner_ids: Optional[List[str]] = Field([], description="所有者ID列表") - - -class RemoveTagsRequest(BaseModel): - """移除标签请求模型""" - tags: List[str] = Field(..., description="要移除的标签列表") - - -class RenameTagRequest(BaseModel): - """重命名标签请求模型""" - from_tag: str = Field(..., description="原标签名") - to_tag: str = Field(..., description="新标签名") - - -class RunGraphRAGRequest(BaseModel): - """运行GraphRAG请求模型""" - kb_id: str = Field(..., description="知识库ID") - - -class RunRaptorRequest(BaseModel): - """运行RAPTOR请求模型""" - kb_id: str = Field(..., description="知识库ID") - - -class RunMindmapRequest(BaseModel): - """运行Mindmap请求模型""" - kb_id: str = Field(..., description="知识库ID") - - -class ListPipelineLogsRequest(BaseModel): - """列出管道日志请求模型""" - operation_status: Optional[List[str]] = Field([], description="操作状态列表") - types: Optional[List[str]] = Field([], description="文件类型列表") - suffix: Optional[List[str]] = Field([], description="文件后缀列表") - - -class ListPipelineDatasetLogsRequest(BaseModel): - """列出管道数据集日志请求模型""" - operation_status: Optional[List[str]] = Field([], description="操作状态列表") - - -class DeletePipelineLogsRequest(BaseModel): - """删除管道日志请求模型""" - log_ids: List[str] = Field(..., description="日志ID列表") - - -class UnbindTaskRequest(BaseModel): - """解绑任务请求模型""" - kb_id: str = Field(..., description="知识库ID") - pipeline_task_type: str = Field(..., description="管道任务类型") diff --git a/api/models/llm_models.py b/api/models/llm_models.py deleted file mode 100644 index bac70d0..0000000 --- a/api/models/llm_models.py +++ /dev/null @@ -1,84 +0,0 @@ -# -# Copyright 2024 The InfiniFlow Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -from typing import Optional, Dict, Any -from pydantic import BaseModel, Field - - -class SetApiKeyRequest(BaseModel): - """设置API密钥请求模型""" - llm_factory: str = Field(..., description="LLM工厂名称") - api_key: str = Field(..., description="API密钥") - base_url: Optional[str] = Field(None, description="基础URL") - - -class AddLLMRequest(BaseModel): - """添加LLM请求模型""" - llm_factory: str = Field(..., description="LLM工厂名称") - api_key: Optional[str] = Field("x", description="API密钥") - llm_name: Optional[str] = Field(None, description="LLM名称") - model_type: Optional[str] = Field(None, description="模型类型") - api_base: Optional[str] = Field(None, description="API基础URL") - max_tokens: Optional[int] = Field(None, description="最大token数") - - # VolcEngine specific fields - ark_api_key: Optional[str] = Field(None, description="VolcEngine ARK API密钥") - endpoint_id: Optional[str] = Field(None, description="VolcEngine端点ID") - - # Tencent Hunyuan specific fields - hunyuan_sid: Optional[str] = Field(None, description="腾讯混元SID") - hunyuan_sk: Optional[str] = Field(None, description="腾讯混元SK") - - # Tencent Cloud specific fields - tencent_cloud_sid: Optional[str] = Field(None, description="腾讯云SID") - tencent_cloud_sk: Optional[str] = Field(None, description="腾讯云SK") - - # Bedrock specific fields - bedrock_ak: Optional[str] = Field(None, description="Bedrock访问密钥") - bedrock_sk: Optional[str] = Field(None, description="Bedrock秘密密钥") - bedrock_region: Optional[str] = Field(None, description="Bedrock区域") - - # XunFei Spark specific fields - spark_api_password: Optional[str] = Field(None, description="讯飞Spark API密码") - spark_app_id: Optional[str] = Field(None, description="讯飞Spark应用ID") - spark_api_secret: Optional[str] = Field(None, description="讯飞Spark API密钥") - spark_api_key: Optional[str] = Field(None, description="讯飞Spark API密钥") - - # BaiduYiyan specific fields - yiyan_ak: Optional[str] = Field(None, description="百度文心一言AK") - yiyan_sk: Optional[str] = Field(None, description="百度文心一言SK") - - # Fish Audio specific fields - fish_audio_ak: Optional[str] = Field(None, description="Fish Audio AK") - fish_audio_refid: Optional[str] = Field(None, description="Fish Audio参考ID") - - # Google Cloud specific fields - google_project_id: Optional[str] = Field(None, description="Google Cloud项目ID") - google_region: Optional[str] = Field(None, description="Google Cloud区域") - google_service_account_key: Optional[str] = Field(None, description="Google Cloud服务账户密钥") - - # Azure OpenAI specific fields - api_version: Optional[str] = Field(None, description="Azure OpenAI API版本") - - -class DeleteLLMRequest(BaseModel): - """删除LLM请求模型""" - llm_factory: str = Field(..., description="LLM工厂名称") - llm_name: str = Field(..., description="LLM名称") - - -class DeleteFactoryRequest(BaseModel): - """删除工厂请求模型""" - llm_factory: str = Field(..., description="LLM工厂名称") diff --git a/api/models/tenant_models.py b/api/models/tenant_models.py deleted file mode 100644 index 79cac6c..0000000 --- a/api/models/tenant_models.py +++ /dev/null @@ -1,30 +0,0 @@ -# -# Copyright 2024 The InfiniFlow Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -from typing import Optional -from pydantic import BaseModel, Field - - -class InviteUserRequest(BaseModel): - """邀请用户请求模型""" - email: str = Field(..., description="用户邮箱") - - -class UserTenantResponse(BaseModel): - """用户租户响应模型""" - id: str = Field(..., description="用户ID") - avatar: Optional[str] = Field(None, description="用户头像") - email: str = Field(..., description="用户邮箱") - nickname: Optional[str] = Field(None, description="用户昵称") diff --git a/api/settings.py b/api/settings.py index e6763d8..9c003d2 100644 --- a/api/settings.py +++ b/api/settings.py @@ -65,8 +65,8 @@ OAUTH_CONFIG = None DOC_ENGINE = None docStoreConn = None -retrievaler = None -kg_retrievaler = None +retriever = None +kg_retriever = None # user registration switch REGISTER_ENABLED = 1 @@ -174,7 +174,7 @@ def init_settings(): OAUTH_CONFIG = get_base_config("oauth", {}) - global DOC_ENGINE, docStoreConn, retrievaler, kg_retrievaler + global DOC_ENGINE, docStoreConn, retriever, kg_retriever DOC_ENGINE = os.environ.get("DOC_ENGINE", "elasticsearch") # DOC_ENGINE = os.environ.get('DOC_ENGINE', "opensearch") lower_case_doc_engine = DOC_ENGINE.lower() @@ -187,10 +187,10 @@ def init_settings(): else: raise Exception(f"Not supported doc engine: {DOC_ENGINE}") - retrievaler = search.Dealer(docStoreConn) + retriever = search.Dealer(docStoreConn) from graphrag import search as kg_search - kg_retrievaler = kg_search.KGSearch(docStoreConn) + kg_retriever = kg_search.KGSearch(docStoreConn) if int(os.environ.get("SANDBOX_ENABLED", "0")): global SANDBOX_HOST diff --git a/api/utils/api_utils.py b/api/utils/api_utils.py index 3736255..a756f1e 100644 --- a/api/utils/api_utils.py +++ b/api/utils/api_utils.py @@ -53,6 +53,7 @@ from api.db.services.llm_service import LLMService from api.db.services.tenant_llm_service import TenantLLMService from api.utils.json import CustomJSONEncoder, json_dumps + # 自定义认证方案,支持不传Bearer格式 class CustomHTTPBearer(SecurityBase): def __init__(self, *, scheme_name: str = None, auto_error: bool = True): @@ -71,13 +72,13 @@ class CustomHTTPBearer(SecurityBase): ) else: return None - + # 支持Bearer格式和直接token格式 if authorization.startswith("Bearer "): token = authorization[7:] # 移除"Bearer "前缀 else: token = authorization # 直接使用token - + return HTTPAuthorizationCredentials(scheme="Bearer", credentials=token) # FastAPI 安全方案 @@ -95,8 +96,8 @@ def serialize_for_json(obj): if hasattr(obj, '__dict__'): # For objects with __dict__, try to serialize their attributes try: - return {key: serialize_for_json(value) for key, value in obj.__dict__.items() - if not key.startswith('_')} + return {key: serialize_for_json(value) for key, value in obj.__dict__.items() + if not key.startswith('_')} except (AttributeError, TypeError): return str(obj) elif hasattr(obj, '__name__'): @@ -112,6 +113,7 @@ def serialize_for_json(obj): # Fallback: convert to string representation return str(obj) + def request(**kwargs): sess = requests.Session() stream = kwargs.pop("stream", sess.stream) @@ -132,7 +134,8 @@ def request(**kwargs): settings.HTTP_APP_KEY.encode("ascii"), prepped.path_url.encode("ascii"), prepped.body if kwargs.get("json") else b"", - urlencode(sorted(kwargs["data"].items()), quote_via=quote, safe="-._~").encode("ascii") if kwargs.get("data") and isinstance(kwargs["data"], dict) else b"", + urlencode(sorted(kwargs["data"].items()), quote_via=quote, safe="-._~").encode( + "ascii") if kwargs.get("data") and isinstance(kwargs["data"], dict) else b"", ] ), "sha1", @@ -154,7 +157,7 @@ def request(**kwargs): def get_exponential_backoff_interval(retries, full_jitter=False): """Calculate the exponential backoff wait time.""" # Will be zero if factor equals 0 - countdown = min(REQUEST_MAX_WAIT_SEC, REQUEST_WAIT_SEC * (2**retries)) + countdown = min(REQUEST_MAX_WAIT_SEC, REQUEST_WAIT_SEC * (2 ** retries)) # Full jitter according to # https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/ if full_jitter: @@ -185,11 +188,12 @@ def server_error_response(e): if len(e.args) > 1: try: serialized_data = serialize_for_json(e.args[1]) - return get_json_result(code= settings.RetCode.EXCEPTION_ERROR, message=repr(e.args[0]), data=serialized_data) + return get_json_result(code=settings.RetCode.EXCEPTION_ERROR, message=repr(e.args[0]), data=serialized_data) except Exception: return get_json_result(code=settings.RetCode.EXCEPTION_ERROR, message=repr(e.args[0]), data=None) if repr(e).find("index_not_found_exception") >= 0: - return get_json_result(code=settings.RetCode.EXCEPTION_ERROR, message="No chunk found, please upload file and parse it.") + return get_json_result(code=settings.RetCode.EXCEPTION_ERROR, + message="No chunk found, please upload file and parse it.") return get_json_result(code=settings.RetCode.EXCEPTION_ERROR, message=repr(e)) @@ -214,12 +218,15 @@ def validate_request(*args, **kwargs): 废弃的装饰器:在 FastAPI 中使用 Pydantic 模型进行验证 这个函数保留是为了向后兼容,但不会执行任何验证 """ + def wrapper(func): @wraps(func) def decorated_function(*_args, **_kwargs): # FastAPI 中不需要手动验证,Pydantic 会自动处理 return func(*_args, **_kwargs) + return decorated_function + return wrapper @@ -228,11 +235,14 @@ def not_allowed_parameters(*params): 废弃的装饰器:在 FastAPI 中使用 Pydantic 模型进行验证 这个函数保留是为了向后兼容,但不会执行任何验证 """ + def decorator(f): def wrapper(*args, **kwargs): # FastAPI 中不需要手动验证,Pydantic 会自动处理 return f(*args, **kwargs) + return wrapper + return decorator @@ -241,10 +251,12 @@ def active_required(f): 废弃的装饰器:在 FastAPI 中使用依赖注入进行用户验证 这个函数保留是为了向后兼容,但不会执行任何验证 """ + @wraps(f) def wrapper(*args, **kwargs): # FastAPI 中使用依赖注入进行用户验证 return f(*args, **kwargs) + return wrapper @@ -281,10 +293,12 @@ def apikey_required(func): 废弃的装饰器:在 FastAPI 中使用依赖注入进行 API Key 验证 这个函数保留是为了向后兼容,但不会执行任何验证 """ + @wraps(func) def decorated_function(*args, **kwargs): # FastAPI 中使用依赖注入进行 API Key 验证 return func(*args, **kwargs) + return decorated_function @@ -301,7 +315,7 @@ def construct_response(code=settings.RetCode.SUCCESS, message="success", data=No continue else: response_dict[key] = value - + headers = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Method": "*", @@ -310,7 +324,7 @@ def construct_response(code=settings.RetCode.SUCCESS, message="success", data=No } if auth: headers["Authorization"] = auth - + return JSONResponse(content=response_dict, headers=headers) @@ -349,10 +363,12 @@ def token_required(func): 废弃的装饰器:在 FastAPI 中使用依赖注入进行 Token 验证 这个函数保留是为了向后兼容,但不会执行任何验证 """ + @wraps(func) def decorated_function(*args, **kwargs): # FastAPI 中使用依赖注入进行 Token 验证 return func(*args, **kwargs) + return decorated_function @@ -368,8 +384,8 @@ def get_result(code=settings.RetCode.SUCCESS, message="", data=None): def get_error_data_result( - message="Sorry! Data missing!", - code=settings.RetCode.DATA_ERROR, + message="Sorry! Data missing!", + code=settings.RetCode.DATA_ERROR, ): result_dict = {"code": code, "message": message} response = {} @@ -392,24 +408,24 @@ async def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(s try: jwt = URLSafeTimedSerializer(secret_key=settings.SECRET_KEY) authorization = credentials.credentials - + if authorization: try: access_token = str(jwt.loads(authorization)) - + if not access_token or not access_token.strip(): raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Authentication attempt with empty access token" ) - + # Access tokens should be UUIDs (32 hex characters) if len(access_token.strip()) < 32: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail=f"Authentication attempt with invalid token format: {len(access_token)} chars" ) - + user = UserService.query( access_token=access_token, status=StatusEnum.VALID.value ) @@ -474,7 +490,7 @@ def create_file_response(data, filename: str, media_type: str = "application/oct data = json_dumps(data) if isinstance(data, str): data = data.encode("utf-8") - + return StreamingResponse( BytesIO(data), media_type=media_type, @@ -501,7 +517,8 @@ def get_parser_config(chunk_method, parser_config): # Define default configurations for each chunking method key_mapping = { - "naive": {"chunk_token_num": 512, "delimiter": r"\n", "html4excel": False, "layout_recognize": "DeepDOC", "raptor": {"use_raptor": False}, "graphrag": {"use_graphrag": False}}, + "naive": {"chunk_token_num": 512, "delimiter": r"\n", "html4excel": False, "layout_recognize": "DeepDOC", + "raptor": {"use_raptor": False}, "graphrag": {"use_graphrag": False}}, "qa": {"raptor": {"use_raptor": False}, "graphrag": {"use_graphrag": False}}, "tag": None, "resume": None, @@ -540,16 +557,16 @@ def get_parser_config(chunk_method, parser_config): def get_data_openai( - id=None, - created=None, - model=None, - prompt_tokens=0, - completion_tokens=0, - content=None, - finish_reason=None, - object="chat.completion", - param=None, - stream=False + id=None, + created=None, + model=None, + prompt_tokens=0, + completion_tokens=0, + content=None, + finish_reason=None, + object="chat.completion", + param=None, + stream=False ): total_tokens = prompt_tokens + completion_tokens @@ -661,7 +678,9 @@ def verify_embedding_availability(embd_id: str, tenant_id: str) -> tuple[bool, J in_llm_service = bool(LLMService.query(llm_name=llm_name, fid=llm_factory, model_type="embedding")) tenant_llms = TenantLLMService.get_my_llms(tenant_id=tenant_id) - is_tenant_model = any(llm["llm_name"] == llm_name and llm["llm_factory"] == llm_factory and llm["model_type"] == "embedding" for llm in tenant_llms) + is_tenant_model = any( + llm["llm_name"] == llm_name and llm["llm_factory"] == llm_factory and llm["model_type"] == "embedding" for + llm in tenant_llms) is_builtin_model = embd_id in settings.BUILTIN_EMBEDDING_MODELS if not (is_builtin_model or is_tenant_model or in_llm_service): @@ -804,7 +823,8 @@ TimeoutException = Union[Type[BaseException], BaseException] OnTimeoutCallback = Union[Callable[..., Any], Coroutine[Any, Any, Any]] -def timeout(seconds: float | int | str = None, attempts: int = 2, *, exception: Optional[TimeoutException] = None, on_timeout: Optional[OnTimeoutCallback] = None): +def timeout(seconds: float | int | str = None, attempts: int = 2, *, exception: Optional[TimeoutException] = None, + on_timeout: Optional[OnTimeoutCallback] = None): if isinstance(seconds, str): seconds = float(seconds) def decorator(func): @@ -892,7 +912,8 @@ async def is_strong_enough(chat_model, embedding_model): _ = await trio.to_thread.run_sync(lambda: embedding_model.encode(["Are you strong enough!?"])) if chat_model: with trio.fail_after(30): - res = await trio.to_thread.run_sync(lambda: chat_model.chat("Nothing special.", [{"role": "user", "content": "Are you strong enough!?"}], {})) + res = await trio.to_thread.run_sync(lambda: chat_model.chat("Nothing special.", [ + {"role": "user", "content": "Are you strong enough!?"}], {})) if res.find("**ERROR**") >= 0: raise Exception(res) diff --git a/api/utils/email_templates.py b/api/utils/email_templates.py new file mode 100644 index 0000000..1047390 --- /dev/null +++ b/api/utils/email_templates.py @@ -0,0 +1,25 @@ +""" +Reusable HTML email templates and registry. +""" + +# Invitation email template +INVITE_EMAIL_TMPL = """ +

Hi {{email}},

+

{{inviter}} has invited you to join their team (ID: {{tenant_id}}).

+

Click the link below to complete your registration:
+{{invite_url}}

+

If you did not request this, please ignore this email.

+""" + +# Password reset code template +RESET_CODE_EMAIL_TMPL = """ +

Hello,

+

Your password reset code is: {{ code }}

+

This code will expire in {{ ttl_min }} minutes.

+""" + +# Template registry +EMAIL_TEMPLATES = { + "invite": INVITE_EMAIL_TMPL, + "reset_code": RESET_CODE_EMAIL_TMPL, +} diff --git a/api/utils/file_utils.py b/api/utils/file_utils.py index 63e96fb..9815f0d 100644 --- a/api/utils/file_utils.py +++ b/api/utils/file_utils.py @@ -13,7 +13,12 @@ # See the License for the specific language governing permissions and # limitations under the License. # + + +# Standard library imports import base64 +import hashlib +import io import json import os import re @@ -22,13 +27,20 @@ import subprocess import sys import tempfile import threading +import zipfile from io import BytesIO +# Typing +from typing import List, Union, Tuple + +# Third-party imports +import olefile import pdfplumber from cachetools import LRUCache, cached from PIL import Image from ruamel.yaml import YAML +# Local imports from api.constants import IMG_BASE64_PREFIX from api.db import FileType @@ -161,7 +173,7 @@ def filename_type(filename): if re.match(r".*\.(wav|flac|ape|alac|wavpack|wv|mp3|aac|ogg|vorbis|opus)$", filename): return FileType.AURAL.value - if re.match(r".*\.(jpg|jpeg|png|tif|gif|pcx|tga|exif|fpx|svg|psd|cdr|pcd|dxf|ufo|eps|ai|raw|WMF|webp|avif|apng|icon|ico|mpg|mpeg|avi|rm|rmvb|mov|wmv|asf|dat|asx|wvx|mpe|mpa|mp4)$", filename): + if re.match(r".*\.(jpg|jpeg|png|tif|gif|pcx|tga|exif|fpx|svg|psd|cdr|pcd|dxf|ufo|eps|ai|raw|WMF|webp|avif|apng|icon|ico|mpg|mpeg|avi|rm|rmvb|mov|wmv|asf|dat|asx|wvx|mpe|mpa|mp4|avi|mkv)$", filename): return FileType.VISUAL.value return FileType.OTHER.value @@ -284,3 +296,125 @@ def read_potential_broken_pdf(blob): return repaired return blob + + + +def _is_zip(h: bytes) -> bool: + return h.startswith(b"PK\x03\x04") or h.startswith(b"PK\x05\x06") or h.startswith(b"PK\x07\x08") + +def _is_pdf(h: bytes) -> bool: + return h.startswith(b"%PDF-") + +def _is_ole(h: bytes) -> bool: + return h.startswith(b"\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1") + +def _sha10(b: bytes) -> str: + return hashlib.sha256(b).hexdigest()[:10] + +def _guess_ext(b: bytes) -> str: + h = b[:8] + if _is_zip(h): + try: + with zipfile.ZipFile(io.BytesIO(b), "r") as z: + names = [n.lower() for n in z.namelist()] + if any(n.startswith("word/") for n in names): + return ".docx" + if any(n.startswith("ppt/") for n in names): + return ".pptx" + if any(n.startswith("xl/") for n in names): + return ".xlsx" + except Exception: + pass + return ".zip" + if _is_pdf(h): + return ".pdf" + if _is_ole(h): + return ".doc" + return ".bin" + +# Try to extract the real embedded payload from OLE's Ole10Native +def _extract_ole10native_payload(data: bytes) -> bytes: + try: + pos = 0 + if len(data) < 4: + return data + _ = int.from_bytes(data[pos:pos+4], "little") + pos += 4 + # filename/src/tmp (NUL-terminated ANSI) + for _ in range(3): + z = data.index(b"\x00", pos) + pos = z + 1 + # skip unknown 4 bytes + pos += 4 + if pos + 4 > len(data): + return data + size = int.from_bytes(data[pos:pos+4], "little") + pos += 4 + if pos + size <= len(data): + return data[pos:pos+size] + except Exception: + pass + return data + +def extract_embed_file(target: Union[bytes, bytearray]) -> List[Tuple[str, bytes]]: + """ + Only extract the 'first layer' of embedding, returning raw (filename, bytes). + """ + top = bytes(target) + head = top[:8] + out: List[Tuple[str, bytes]] = [] + seen = set() + + def push(b: bytes, name_hint: str = ""): + h10 = _sha10(b) + if h10 in seen: + return + seen.add(h10) + ext = _guess_ext(b) + # If name_hint has an extension use its basename; else fallback to guessed ext + if "." in name_hint: + fname = name_hint.split("/")[-1] + else: + fname = f"{h10}{ext}" + out.append((fname, b)) + + # OOXML/ZIP container (docx/xlsx/pptx) + if _is_zip(head): + try: + with zipfile.ZipFile(io.BytesIO(top), "r") as z: + embed_dirs = ( + "word/embeddings/", "word/objects/", "word/activex/", + "xl/embeddings/", "ppt/embeddings/" + ) + for name in z.namelist(): + low = name.lower() + if any(low.startswith(d) for d in embed_dirs): + try: + b = z.read(name) + push(b, name) + except Exception: + pass + except Exception: + pass + return out + + # OLE container (doc/ppt/xls) + if _is_ole(head): + try: + with olefile.OleFileIO(io.BytesIO(top)) as ole: + for entry in ole.listdir(): + p = "/".join(entry) + try: + data = ole.openstream(entry).read() + except Exception: + continue + if not data: + continue + if "Ole10Native" in p or "ole10native" in p.lower(): + data = _extract_ole10native_payload(data) + push(data, p) + except Exception: + pass + return out + + return out \ No newline at end of file diff --git a/api/utils/health_utils.py b/api/utils/health_utils.py index 1396d75..3a97fc5 100644 --- a/api/utils/health_utils.py +++ b/api/utils/health_utils.py @@ -74,12 +74,12 @@ def get_es_cluster_stats() -> dict: raise Exception("Elasticsearch is not in use.") try: return { - "alive": True, + "status": "alive", "message": ESConnection().get_cluster_stats() } except Exception as e: return { - "alive": False, + "status": "timeout", "message": f"error: {str(e)}", } @@ -90,12 +90,12 @@ def get_infinity_status(): raise Exception("Infinity is not in use.") try: return { - "alive": True, + "status": "alive", "message": InfinityConnection().health() } except Exception as e: return { - "alive": False, + "status": "timeout", "message": f"error: {str(e)}", } @@ -107,12 +107,12 @@ def get_mysql_status(): headers = ['id', 'user', 'host', 'db', 'command', 'time', 'state', 'info'] cursor.close() return { - "alive": True, + "status": "alive", "message": [dict(zip(headers, r)) for r in res_rows] } except Exception as e: return { - "alive": False, + "status": "timeout", "message": f"error: {str(e)}", } @@ -122,12 +122,12 @@ def check_minio_alive(): try: response = requests.get(f'http://{rag_settings.MINIO["host"]}/minio/health/live') if response.status_code == 200: - return {'alive': True, "message": f"Confirm elapsed: {(timer() - start_time) * 1000.0:.1f} ms."} + return {"status": "alive", "message": f"Confirm elapsed: {(timer() - start_time) * 1000.0:.1f} ms."} else: - return {'alive': False, "message": f"Confirm elapsed: {(timer() - start_time) * 1000.0:.1f} ms."} + return {"status": "timeout", "message": f"Confirm elapsed: {(timer() - start_time) * 1000.0:.1f} ms."} except Exception as e: return { - "alive": False, + "status": "timeout", "message": f"error: {str(e)}", } @@ -135,12 +135,12 @@ def check_minio_alive(): def get_redis_info(): try: return { - "alive": True, + "status": "alive", "message": REDIS_CONN.info() } except Exception as e: return { - "alive": False, + "status": "timeout", "message": f"error: {str(e)}", } @@ -150,12 +150,12 @@ def check_ragflow_server_alive(): try: response = requests.get(f'http://{settings.HOST_IP}:{settings.HOST_PORT}/v1/system/ping') if response.status_code == 200: - return {'alive': True, "message": f"Confirm elapsed: {(timer() - start_time) * 1000.0:.1f} ms."} + return {"status": "alive", "message": f"Confirm elapsed: {(timer() - start_time) * 1000.0:.1f} ms."} else: - return {'alive': False, "message": f"Confirm elapsed: {(timer() - start_time) * 1000.0:.1f} ms."} + return {"status": "timeout", "message": f"Confirm elapsed: {(timer() - start_time) * 1000.0:.1f} ms."} except Exception as e: return { - "alive": False, + "status": "timeout", "message": f"error: {str(e)}", } @@ -192,9 +192,7 @@ def run_health_checks() -> tuple[dict, bool]: except Exception: result["storage"] = "nok" - - all_ok = (result.get("db") == "ok") and (result.get("redis") == "ok") and (result.get("doc_engine") == "ok") and (result.get("storage") == "ok") + all_ok = (result.get("db") == "ok") and (result.get("redis") == "ok") and (result.get("doc_engine") == "ok") and ( + result.get("storage") == "ok") result["status"] = "ok" if all_ok else "nok" return result, all_ok - - diff --git a/api/utils/web_utils.py b/api/utils/web_utils.py index 55ce561..e0e47f4 100644 --- a/api/utils/web_utils.py +++ b/api/utils/web_utils.py @@ -24,6 +24,7 @@ from urllib.parse import urlparse from api.apps import smtp_mail_server from flask_mail import Message from flask import render_template_string +from api.utils.email_templates import EMAIL_TEMPLATES from selenium import webdriver from selenium.common.exceptions import TimeoutException from selenium.webdriver.chrome.options import Options @@ -34,6 +35,12 @@ from selenium.webdriver.support.ui import WebDriverWait from webdriver_manager.chrome import ChromeDriverManager +OTP_LENGTH = 8 +OTP_TTL_SECONDS = 5 * 60 +ATTEMPT_LIMIT = 5 +ATTEMPT_LOCK_SECONDS = 30 * 60 +RESEND_COOLDOWN_SECONDS = 60 + CONTENT_TYPE_MAP = { # Office @@ -178,24 +185,49 @@ def get_float(req: dict, key: str, default: float | int = 10.0) -> float: return default -INVITE_EMAIL_TMPL = """ -

Hi {{email}},

-

{{inviter}} has invited you to join their team (ID: {{tenant_id}}).

-

Click the link below to complete your registration:
-{{invite_url}}

-

If you did not request this, please ignore this email.

-""" +def send_email_html(subject: str, to_email: str, template_key: str, **context): + """Generic HTML email sender using shared templates. + template_key must exist in EMAIL_TEMPLATES. + """ + from api.apps import app + tmpl = EMAIL_TEMPLATES.get(template_key) + if not tmpl: + raise ValueError(f"Unknown email template: {template_key}") + with app.app_context(): + msg = Message(subject=subject, recipients=[to_email]) + msg.html = render_template_string(tmpl, **context) + smtp_mail_server.send(msg) + def send_invite_email(to_email, invite_url, tenant_id, inviter): - from api.apps import app - with app.app_context(): - msg = Message(subject="RAGFlow Invitation", - recipients=[to_email]) - msg.html = render_template_string( - INVITE_EMAIL_TMPL, - email=to_email, - invite_url=invite_url, - tenant_id=tenant_id, - inviter=inviter, - ) - smtp_mail_server.send(msg) + # Reuse the generic HTML sender with 'invite' template + send_email_html( + subject="RAGFlow Invitation", + to_email=to_email, + template_key="invite", + email=to_email, + invite_url=invite_url, + tenant_id=tenant_id, + inviter=inviter, + ) + + +def otp_keys(email: str): + email = (email or "").strip().lower() + return ( + f"otp:{email}", + f"otp_attempts:{email}", + f"otp_last_sent:{email}", + f"otp_lock:{email}", + ) + + +def hash_code(code: str, salt: bytes) -> str: + import hashlib + import hmac + return hmac.new(salt, (code or "").encode("utf-8"), hashlib.sha256).hexdigest() + + +def captcha_key(email: str) -> str: + return f"captcha:{email}" + diff --git a/chat_demo/index.html b/chat_demo/index.html new file mode 100644 index 0000000..114b136 --- /dev/null +++ b/chat_demo/index.html @@ -0,0 +1,19 @@ + + \ No newline at end of file diff --git a/chat_demo/widget_demo.html b/chat_demo/widget_demo.html new file mode 100644 index 0000000..34c262b --- /dev/null +++ b/chat_demo/widget_demo.html @@ -0,0 +1,154 @@ + + + + + + Floating Chat Widget Demo + + + +
+

🚀 Floating Chat Widget Demo

+ +

+ Welcome to our demo page! This page simulates a real website with content. + Look for the floating chat button in the bottom-right corner - just like Intercom! +

+ +
+

🎯 Widget Features

+
    +
  • Floating button that stays visible while scrolling
  • +
  • Click to open/close the chat window
  • +
  • Minimize button to collapse the chat
  • +
  • Professional Intercom-style design
  • +
  • Unread message indicator (red badge)
  • +
  • Transparent background integration
  • +
  • Responsive design for all screen sizes
  • +
+
+ +

+ The chat widget is completely separate from your website's content and won't + interfere with your existing layout or functionality. It's designed to be + lightweight and performant. +

+ +

+ Try scrolling this page - notice how the chat button stays in position. + Click it to start a conversation with our AI assistant! +

+ +
+

🔧 Implementation

+
    +
  • Simple iframe embed - just copy and paste
  • +
  • No JavaScript dependencies required
  • +
  • Works on any website or platform
  • +
  • Customizable appearance and behavior
  • +
  • Secure and privacy-focused
  • +
+
+ +

+ This is just placeholder content to demonstrate how the widget integrates + seamlessly with your existing website content. The widget floats above + everything else without disrupting your user experience. +

+ +

+ 🎉 Ready to add this to your website? Get your embed code from the admin panel! +

+
+ + + + + \ No newline at end of file diff --git a/conf/infinity_mapping.json b/conf/infinity_mapping.json index 3e39044..e447664 100644 --- a/conf/infinity_mapping.json +++ b/conf/infinity_mapping.json @@ -31,7 +31,6 @@ "entities_kwd": {"type": "varchar", "default": "", "analyzer": "whitespace-#"}, "pagerank_fea": {"type": "integer", "default": 0}, "tag_feas": {"type": "varchar", "default": "", "analyzer": "rankfeatures"}, - "from_entity_kwd": {"type": "varchar", "default": "", "analyzer": "whitespace-#"}, "to_entity_kwd": {"type": "varchar", "default": "", "analyzer": "whitespace-#"}, "entity_kwd": {"type": "varchar", "default": "", "analyzer": "whitespace-#"}, @@ -39,6 +38,6 @@ "source_id": {"type": "varchar", "default": "", "analyzer": "whitespace-#"}, "n_hop_with_weight": {"type": "varchar", "default": ""}, "removed_kwd": {"type": "varchar", "default": "", "analyzer": "whitespace-#"}, - - "doc_type_kwd": {"type": "varchar", "default": "", "analyzer": "whitespace-#"} + "doc_type_kwd": {"type": "varchar", "default": "", "analyzer": "whitespace-#"}, + "toc_kwd": {"type": "varchar", "default": "", "analyzer": "whitespace-#"} } diff --git a/conf/llm_factories.json b/conf/llm_factories.json index e6b82f4..75b55ad 100644 --- a/conf/llm_factories.json +++ b/conf/llm_factories.json @@ -803,6 +803,12 @@ "tags": "TEXT EMBEDDING", "max_tokens": 512, "model_type": "embedding" + }, + { + "llm_name": "glm-asr", + "tags": "SPEECH2TEXT", + "max_tokens": 4096, + "model_type": "speech2text" } ] }, @@ -965,31 +971,9 @@ { "name": "VolcEngine", "logo": "", - "tags": "LLM, TEXT EMBEDDING", + "tags": "LLM, TEXT EMBEDDING, IMAGE2TEXT", "status": "1", - "llm": [ - { - "llm_name": "Doubao-pro-128k", - "tags": "LLM,CHAT,128k", - "max_tokens": 131072, - "model_type": "chat", - "is_tools": true - }, - { - "llm_name": "Doubao-pro-32k", - "tags": "LLM,CHAT,32k", - "max_tokens": 32768, - "model_type": "chat", - "is_tools": true - }, - { - "llm_name": "Doubao-pro-4k", - "tags": "LLM,CHAT,4k", - "max_tokens": 4096, - "model_type": "chat", - "is_tools": true - } - ] + "llm": [] }, { "name": "BaiChuan", @@ -1361,35 +1345,35 @@ "llm_name": "gemini-2.5-flash", "tags": "LLM,CHAT,1024K,IMAGE2TEXT", "max_tokens": 1048576, - "model_type": "chat", + "model_type": "image2text", "is_tools": true }, { "llm_name": "gemini-2.5-pro", "tags": "LLM,CHAT,IMAGE2TEXT,1024K", "max_tokens": 1048576, - "model_type": "chat", + "model_type": "image2text", "is_tools": true }, { "llm_name": "gemini-2.5-flash-lite", "tags": "LLM,CHAT,1024K,IMAGE2TEXT", "max_tokens": 1048576, - "model_type": "chat", + "model_type": "image2text", "is_tools": true }, { "llm_name": "gemini-2.0-flash", "tags": "LLM,CHAT,1024K", "max_tokens": 1048576, - "model_type": "chat", + "model_type": "image2text", "is_tools": true }, { "llm_name": "gemini-2.0-flash-lite", "tags": "LLM,CHAT,1024K", "max_tokens": 1048576, - "model_type": "chat", + "model_type": "image2text", "is_tools": true }, { @@ -2816,6 +2800,13 @@ "tags": "LLM,TEXT EMBEDDING,TEXT RE-RANK,IMAGE2TEXT", "status": "1", "llm": [ + { + "llm_name":"THUDM/GLM-4.1V-9B-Thinking", + "tags":"LLM,CHAT,IMAGE2TEXT, 64k", + "max_tokens":64000, + "model_type":"chat", + "is_tools": false + }, { "llm_name": "Qwen/Qwen3-Embedding-8B", "tags": "TEXT EMBEDDING,TEXT RE-RANK,32k", @@ -2996,7 +2987,7 @@ "tags": "LLM,CHAT,IMAGE2TEXT,32k", "max_tokens": 32000, "model_type": "image2text", - "is_tools": true + "is_tools": false }, { "llm_name": "THUDM/GLM-Z1-32B-0414", @@ -3145,13 +3136,6 @@ "model_type": "chat", "is_tools": true }, - { - "llm_name": "Qwen/Qwen2-1.5B-Instruct", - "tags": "LLM,CHAT,32k", - "max_tokens": 32000, - "model_type": "chat", - "is_tools": true - }, { "llm_name": "Pro/Qwen/Qwen2.5-Coder-7B-Instruct", "tags": "LLM,CHAT,32k", @@ -3159,13 +3143,6 @@ "model_type": "chat", "is_tools": false }, - { - "llm_name": "Pro/Qwen/Qwen2-VL-7B-Instruct", - "tags": "LLM,CHAT,IMAGE2TEXT,32k", - "max_tokens": 32000, - "model_type": "image2text", - "is_tools": false - }, { "llm_name": "Pro/Qwen/Qwen2.5-7B-Instruct", "tags": "LLM,CHAT,32k", @@ -5147,4 +5124,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/conf/os_mapping.json b/conf/os_mapping.json index a8663e0..47b7c24 100644 --- a/conf/os_mapping.json +++ b/conf/os_mapping.json @@ -200,6 +200,61 @@ } } }, + { + "knn_vector": { + "match": "*_2048_vec", + "mapping": { + "type": "knn_vector", + "index": true, + "space_type": "cosinesimil", + "dimension": 2048 + } + } + }, + { + "knn_vector": { + "match": "*_4096_vec", + "mapping": { + "type": "knn_vector", + "index": true, + "space_type": "cosinesimil", + "dimension": 4096 + } + } + }, + { + "knn_vector": { + "match": "*_6144_vec", + "mapping": { + "type": "knn_vector", + "index": true, + "space_type": "cosinesimil", + "dimension": 6144 + } + } + }, + { + "knn_vector": { + "match": "*_8192_vec", + "mapping": { + "type": "knn_vector", + "index": true, + "space_type": "cosinesimil", + "dimension": 8192 + } + } + }, + { + "knn_vector": { + "match": "*_10240_vec", + "mapping": { + "type": "knn_vector", + "index": true, + "space_type": "cosinesimil", + "dimension": 10240 + } + } + }, { "binary": { "match": "*_bin", diff --git a/deepdoc/README.md b/deepdoc/README.md deleted file mode 100644 index 14c7947..0000000 --- a/deepdoc/README.md +++ /dev/null @@ -1,122 +0,0 @@ -English | [简体中文](./README_zh.md) - -# *Deep*Doc - -- [1. Introduction](#1) -- [2. Vision](#2) -- [3. Parser](#3) - - -## 1. Introduction - -With a bunch of documents from various domains with various formats and along with diverse retrieval requirements, -an accurate analysis becomes a very challenge task. *Deep*Doc is born for that purpose. -There are 2 parts in *Deep*Doc so far: vision and parser. -You can run the flowing test programs if you're interested in our results of OCR, layout recognition and TSR. -```bash -python deepdoc/vision/t_ocr.py -h -usage: t_ocr.py [-h] --inputs INPUTS [--output_dir OUTPUT_DIR] - -options: - -h, --help show this help message and exit - --inputs INPUTS Directory where to store images or PDFs, or a file path to a single image or PDF - --output_dir OUTPUT_DIR - Directory where to store the output images. Default: './ocr_outputs' -``` -```bash -python deepdoc/vision/t_recognizer.py -h -usage: t_recognizer.py [-h] --inputs INPUTS [--output_dir OUTPUT_DIR] [--threshold THRESHOLD] [--mode {layout,tsr}] - -options: - -h, --help show this help message and exit - --inputs INPUTS Directory where to store images or PDFs, or a file path to a single image or PDF - --output_dir OUTPUT_DIR - Directory where to store the output images. Default: './layouts_outputs' - --threshold THRESHOLD - A threshold to filter out detections. Default: 0.5 - --mode {layout,tsr} Task mode: layout recognition or table structure recognition -``` - -Our models are served on HuggingFace. If you have trouble downloading HuggingFace models, this might help!! -```bash -export HF_ENDPOINT=https://hf-mirror.com -``` - - -## 2. Vision - -We use vision information to resolve problems as human being. - - OCR. Since a lot of documents presented as images or at least be able to transform to image, - OCR is a very essential and fundamental or even universal solution for text extraction. - ```bash - python deepdoc/vision/t_ocr.py --inputs=path_to_images_or_pdfs --output_dir=path_to_store_result - ``` - The inputs could be directory to images or PDF, or a image or PDF. - You can look into the folder 'path_to_store_result' where has images which demonstrate the positions of results, - txt files which contain the OCR text. -
- -
- - - Layout recognition. Documents from different domain may have various layouts, - like, newspaper, magazine, book and résumé are distinct in terms of layout. - Only when machine have an accurate layout analysis, it can decide if these text parts are successive or not, - or this part needs Table Structure Recognition(TSR) to process, or this part is a figure and described with this caption. - We have 10 basic layout components which covers most cases: - - Text - - Title - - Figure - - Figure caption - - Table - - Table caption - - Header - - Footer - - Reference - - Equation - - Have a try on the following command to see the layout detection results. - ```bash - python deepdoc/vision/t_recognizer.py --inputs=path_to_images_or_pdfs --threshold=0.2 --mode=layout --output_dir=path_to_store_result - ``` - The inputs could be directory to images or PDF, or a image or PDF. - You can look into the folder 'path_to_store_result' where has images which demonstrate the detection results as following: -
- -
- - - Table Structure Recognition(TSR). Data table is a frequently used structure to present data including numbers or text. - And the structure of a table might be very complex, like hierarchy headers, spanning cells and projected row headers. - Along with TSR, we also reassemble the content into sentences which could be well comprehended by LLM. - We have five labels for TSR task: - - Column - - Row - - Column header - - Projected row header - - Spanning cell - - Have a try on the following command to see the layout detection results. - ```bash - python deepdoc/vision/t_recognizer.py --inputs=path_to_images_or_pdfs --threshold=0.2 --mode=tsr --output_dir=path_to_store_result - ``` - The inputs could be directory to images or PDF, or a image or PDF. - You can look into the folder 'path_to_store_result' where has both images and html pages which demonstrate the detection results as following: -
- -
- - -## 3. Parser - -Four kinds of document formats as PDF, DOCX, EXCEL and PPT have their corresponding parser. -The most complex one is PDF parser since PDF's flexibility. The output of PDF parser includes: - - Text chunks with their own positions in PDF(page number and rectangular positions). - - Tables with cropped image from the PDF, and contents which has already translated into natural language sentences. - - Figures with caption and text in the figures. - -### Résumé - -The résumé is a very complicated kind of document. A résumé which is composed of unstructured text -with various layouts could be resolved into structured data composed of nearly a hundred of fields. -We haven't opened the parser yet, as we open the processing method after parsing procedure. - - \ No newline at end of file diff --git a/deepdoc/README_zh.md b/deepdoc/README_zh.md deleted file mode 100644 index 4ada7ed..0000000 --- a/deepdoc/README_zh.md +++ /dev/null @@ -1,116 +0,0 @@ -[English](./README.md) | 简体中文 - -# *Deep*Doc - -- [*Deep*Doc](#deepdoc) - - [1. 介绍](#1-介绍) - - [2. 视觉处理](#2-视觉处理) - - [3. 解析器](#3-解析器) - - [简历](#简历) - - -## 1. 介绍 - -对于来自不同领域、具有不同格式和不同检索要求的大量文档,准确的分析成为一项极具挑战性的任务。*Deep*Doc 就是为了这个目的而诞生的。到目前为止,*Deep*Doc 中有两个组成部分:视觉处理和解析器。如果您对我们的OCR、布局识别和TSR结果感兴趣,您可以运行下面的测试程序。 - -```bash -python deepdoc/vision/t_ocr.py -h -usage: t_ocr.py [-h] --inputs INPUTS [--output_dir OUTPUT_DIR] - -options: - -h, --help show this help message and exit - --inputs INPUTS Directory where to store images or PDFs, or a file path to a single image or PDF - --output_dir OUTPUT_DIR - Directory where to store the output images. Default: './ocr_outputs' -``` - -```bash -python deepdoc/vision/t_recognizer.py -h -usage: t_recognizer.py [-h] --inputs INPUTS [--output_dir OUTPUT_DIR] [--threshold THRESHOLD] [--mode {layout,tsr}] - -options: - -h, --help show this help message and exit - --inputs INPUTS Directory where to store images or PDFs, or a file path to a single image or PDF - --output_dir OUTPUT_DIR - Directory where to store the output images. Default: './layouts_outputs' - --threshold THRESHOLD - A threshold to filter out detections. Default: 0.5 - --mode {layout,tsr} Task mode: layout recognition or table structure recognition -``` - -HuggingFace为我们的模型提供服务。如果你在下载HuggingFace模型时遇到问题,这可能会有所帮助!! - -```bash -export HF_ENDPOINT=https://hf-mirror.com -``` - - -## 2. 视觉处理 - -作为人类,我们使用视觉信息来解决问题。 - - - **OCR(Optical Character Recognition,光学字符识别)**。由于许多文档都是以图像形式呈现的,或者至少能够转换为图像,因此OCR是文本提取的一个非常重要、基本,甚至通用的解决方案。 - - ```bash - python deepdoc/vision/t_ocr.py --inputs=path_to_images_or_pdfs --output_dir=path_to_store_result - ``` - - 输入可以是图像或PDF的目录,或者单个图像、PDF文件。您可以查看文件夹 `path_to_store_result` ,其中有演示结果位置的图像,以及包含OCR文本的txt文件。 - -
- -
- - - 布局识别(Layout recognition)。来自不同领域的文件可能有不同的布局,如报纸、杂志、书籍和简历在布局方面是不同的。只有当机器有准确的布局分析时,它才能决定这些文本部分是连续的还是不连续的,或者这个部分需要表结构识别(Table Structure Recognition,TSR)来处理,或者这个部件是一个图形并用这个标题来描述。我们有10个基本布局组件,涵盖了大多数情况: - - 文本 - - 标题 - - 配图 - - 配图标题 - - 表格 - - 表格标题 - - 页头 - - 页尾 - - 参考引用 - - 公式 - - 请尝试以下命令以查看布局检测结果。 - - ```bash - python deepdoc/vision/t_recognizer.py --inputs=path_to_images_or_pdfs --threshold=0.2 --mode=layout --output_dir=path_to_store_result - ``` - - 输入可以是图像或PDF的目录,或者单个图像、PDF文件。您可以查看文件夹 `path_to_store_result` ,其中有显示检测结果的图像,如下所示: -
- -
- - - **TSR(Table Structure Recognition,表结构识别)**。数据表是一种常用的结构,用于表示包括数字或文本在内的数据。表的结构可能非常复杂,比如层次结构标题、跨单元格和投影行标题。除了TSR,我们还将内容重新组合成LLM可以很好理解的句子。TSR任务有五个标签: - - 列 - - 行 - - 列标题 - - 行标题 - - 合并单元格 - - 请尝试以下命令以查看布局检测结果。 - - ```bash - python deepdoc/vision/t_recognizer.py --inputs=path_to_images_or_pdfs --threshold=0.2 --mode=tsr --output_dir=path_to_store_result - ``` - - 输入可以是图像或PDF的目录,或者单个图像、PDF文件。您可以查看文件夹 `path_to_store_result` ,其中包含图像和html页面,这些页面展示了以下检测结果: - -
- -
- - -## 3. 解析器 - -PDF、DOCX、EXCEL和PPT四种文档格式都有相应的解析器。最复杂的是PDF解析器,因为PDF具有灵活性。PDF解析器的输出包括: - - 在PDF中有自己位置的文本块(页码和矩形位置)。 - - 带有PDF裁剪图像的表格,以及已经翻译成自然语言句子的内容。 - - 图中带标题和文字的图。 - -### 简历 - -简历是一种非常复杂的文档。由各种格式的非结构化文本构成的简历可以被解析为包含近百个字段的结构化数据。我们还没有启用解析器,因为在解析过程之后才会启动处理方法。 diff --git a/deepdoc/__init__.py b/deepdoc/__init__.py deleted file mode 100644 index 643f797..0000000 --- a/deepdoc/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright 2025 The InfiniFlow Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -from beartype.claw import beartype_this_package -beartype_this_package() diff --git a/deepdoc/parser/__init__.py b/deepdoc/parser/__init__.py deleted file mode 100644 index 809a56e..0000000 --- a/deepdoc/parser/__init__.py +++ /dev/null @@ -1,40 +0,0 @@ -# -# Copyright 2025 The InfiniFlow Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -from .docx_parser import RAGFlowDocxParser as DocxParser -from .excel_parser import RAGFlowExcelParser as ExcelParser -from .html_parser import RAGFlowHtmlParser as HtmlParser -from .json_parser import RAGFlowJsonParser as JsonParser -from .markdown_parser import MarkdownElementExtractor -from .markdown_parser import RAGFlowMarkdownParser as MarkdownParser -from .pdf_parser import PlainParser -from .pdf_parser import RAGFlowPdfParser as PdfParser -from .ppt_parser import RAGFlowPptParser as PptParser -from .txt_parser import RAGFlowTxtParser as TxtParser - -__all__ = [ - "PdfParser", - "PlainParser", - "DocxParser", - "ExcelParser", - "PptParser", - "HtmlParser", - "JsonParser", - "MarkdownParser", - "TxtParser", - "MarkdownElementExtractor", -] - diff --git a/deepdoc/parser/docx_parser.py b/deepdoc/parser/docx_parser.py deleted file mode 100644 index 2a65841..0000000 --- a/deepdoc/parser/docx_parser.py +++ /dev/null @@ -1,139 +0,0 @@ -# -# Copyright 2025 The InfiniFlow Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -from docx import Document -import re -import pandas as pd -from collections import Counter -from rag.nlp import rag_tokenizer -from io import BytesIO - - -class RAGFlowDocxParser: - - def __extract_table_content(self, tb): - df = [] - for row in tb.rows: - df.append([c.text for c in row.cells]) - return self.__compose_table_content(pd.DataFrame(df)) - - def __compose_table_content(self, df): - - def blockType(b): - pattern = [ - ("^(20|19)[0-9]{2}[年/-][0-9]{1,2}[月/-][0-9]{1,2}日*$", "Dt"), - (r"^(20|19)[0-9]{2}年$", "Dt"), - (r"^(20|19)[0-9]{2}[年/-][0-9]{1,2}月*$", "Dt"), - ("^[0-9]{1,2}[月/-][0-9]{1,2}日*$", "Dt"), - (r"^第*[一二三四1-4]季度$", "Dt"), - (r"^(20|19)[0-9]{2}年*[一二三四1-4]季度$", "Dt"), - (r"^(20|19)[0-9]{2}[ABCDE]$", "DT"), - ("^[0-9.,+%/ -]+$", "Nu"), - (r"^[0-9A-Z/\._~-]+$", "Ca"), - (r"^[A-Z]*[a-z' -]+$", "En"), - (r"^[0-9.,+-]+[0-9A-Za-z/$¥%<>()()' -]+$", "NE"), - (r"^.{1}$", "Sg") - ] - for p, n in pattern: - if re.search(p, b): - return n - tks = [t for t in rag_tokenizer.tokenize(b).split() if len(t) > 1] - if len(tks) > 3: - if len(tks) < 12: - return "Tx" - else: - return "Lx" - - if len(tks) == 1 and rag_tokenizer.tag(tks[0]) == "nr": - return "Nr" - - return "Ot" - - if len(df) < 2: - return [] - max_type = Counter([blockType(str(df.iloc[i, j])) for i in range( - 1, len(df)) for j in range(len(df.iloc[i, :]))]) - max_type = max(max_type.items(), key=lambda x: x[1])[0] - - colnm = len(df.iloc[0, :]) - hdrows = [0] # header is not necessarily appear in the first line - if max_type == "Nu": - for r in range(1, len(df)): - tys = Counter([blockType(str(df.iloc[r, j])) - for j in range(len(df.iloc[r, :]))]) - tys = max(tys.items(), key=lambda x: x[1])[0] - if tys != max_type: - hdrows.append(r) - - lines = [] - for i in range(1, len(df)): - if i in hdrows: - continue - hr = [r - i for r in hdrows] - hr = [r for r in hr if r < 0] - t = len(hr) - 1 - while t > 0: - if hr[t] - hr[t - 1] > 1: - hr = hr[t:] - break - t -= 1 - headers = [] - for j in range(len(df.iloc[i, :])): - t = [] - for h in hr: - x = str(df.iloc[i + h, j]).strip() - if x in t: - continue - t.append(x) - t = ",".join(t) - if t: - t += ": " - headers.append(t) - cells = [] - for j in range(len(df.iloc[i, :])): - if not str(df.iloc[i, j]): - continue - cells.append(headers[j] + str(df.iloc[i, j])) - lines.append(";".join(cells)) - - if colnm > 3: - return lines - return ["\n".join(lines)] - - def __call__(self, fnm, from_page=0, to_page=100000000): - self.doc = Document(fnm) if isinstance( - fnm, str) else Document(BytesIO(fnm)) - pn = 0 # parsed page - secs = [] # parsed contents - for p in self.doc.paragraphs: - if pn > to_page: - break - - runs_within_single_paragraph = [] # save runs within the range of pages - for run in p.runs: - if pn > to_page: - break - if from_page <= pn < to_page and p.text.strip(): - runs_within_single_paragraph.append(run.text) # append run.text first - - # wrap page break checker into a static method - if 'lastRenderedPageBreak' in run._element.xml: - pn += 1 - - secs.append(("".join(runs_within_single_paragraph), p.style.name if hasattr(p.style, 'name') else '')) # then concat run.text as part of the paragraph - - tbls = [self.__extract_table_content(tb) for tb in self.doc.tables] - return secs, tbls diff --git a/deepdoc/parser/excel_parser.py b/deepdoc/parser/excel_parser.py deleted file mode 100644 index 315df7d..0000000 --- a/deepdoc/parser/excel_parser.py +++ /dev/null @@ -1,189 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -import logging -import re -import sys -from io import BytesIO - -import pandas as pd -from openpyxl import Workbook, load_workbook - -from rag.nlp import find_codec - -# copied from `/openpyxl/cell/cell.py` -ILLEGAL_CHARACTERS_RE = re.compile(r"[\000-\010]|[\013-\014]|[\016-\037]") - - -class RAGFlowExcelParser: - @staticmethod - def _load_excel_to_workbook(file_like_object): - if isinstance(file_like_object, bytes): - file_like_object = BytesIO(file_like_object) - - # Read first 4 bytes to determine file type - file_like_object.seek(0) - file_head = file_like_object.read(4) - file_like_object.seek(0) - - if not (file_head.startswith(b"PK\x03\x04") or file_head.startswith(b"\xd0\xcf\x11\xe0")): - logging.info("Not an Excel file, converting CSV to Excel Workbook") - - try: - file_like_object.seek(0) - df = pd.read_csv(file_like_object) - return RAGFlowExcelParser._dataframe_to_workbook(df) - - except Exception as e_csv: - raise Exception(f"Failed to parse CSV and convert to Excel Workbook: {e_csv}") - - try: - return load_workbook(file_like_object, data_only=True) - except Exception as e: - logging.info(f"openpyxl load error: {e}, try pandas instead") - try: - file_like_object.seek(0) - try: - df = pd.read_excel(file_like_object) - return RAGFlowExcelParser._dataframe_to_workbook(df) - except Exception as ex: - logging.info(f"pandas with default engine load error: {ex}, try calamine instead") - file_like_object.seek(0) - df = pd.read_excel(file_like_object, engine="calamine") - return RAGFlowExcelParser._dataframe_to_workbook(df) - except Exception as e_pandas: - raise Exception(f"pandas.read_excel error: {e_pandas}, original openpyxl error: {e}") - - @staticmethod - def _clean_dataframe(df: pd.DataFrame): - def clean_string(s): - if isinstance(s, str): - return ILLEGAL_CHARACTERS_RE.sub(" ", s) - return s - - return df.apply(lambda col: col.map(clean_string)) - - @staticmethod - def _dataframe_to_workbook(df): - df = RAGFlowExcelParser._clean_dataframe(df) - wb = Workbook() - ws = wb.active - ws.title = "Data" - - for col_num, column_name in enumerate(df.columns, 1): - ws.cell(row=1, column=col_num, value=column_name) - - for row_num, row in enumerate(df.values, 2): - for col_num, value in enumerate(row, 1): - ws.cell(row=row_num, column=col_num, value=value) - - return wb - - def html(self, fnm, chunk_rows=256): - from html import escape - - file_like_object = BytesIO(fnm) if not isinstance(fnm, str) else fnm - wb = RAGFlowExcelParser._load_excel_to_workbook(file_like_object) - tb_chunks = [] - - def _fmt(v): - if v is None: - return "" - return str(v).strip() - - for sheetname in wb.sheetnames: - ws = wb[sheetname] - rows = list(ws.rows) - if not rows: - continue - - tb_rows_0 = "" - for t in list(rows[0]): - tb_rows_0 += f"{escape(_fmt(t.value))}" - tb_rows_0 += "" - - for chunk_i in range((len(rows) - 1) // chunk_rows + 1): - tb = "" - tb += f"" - tb += tb_rows_0 - for r in list(rows[1 + chunk_i * chunk_rows : min(1 + (chunk_i + 1) * chunk_rows, len(rows))]): - tb += "" - for i, c in enumerate(r): - if c.value is None: - tb += "" - else: - tb += f"" - tb += "" - tb += "
{sheetname}
{escape(_fmt(c.value))}
\n" - tb_chunks.append(tb) - - return tb_chunks - - def markdown(self, fnm): - import pandas as pd - - file_like_object = BytesIO(fnm) if not isinstance(fnm, str) else fnm - try: - file_like_object.seek(0) - df = pd.read_excel(file_like_object) - except Exception as e: - logging.warning(f"Parse spreadsheet error: {e}, trying to interpret as CSV file") - file_like_object.seek(0) - df = pd.read_csv(file_like_object) - df = df.replace(r"^\s*$", "", regex=True) - return df.to_markdown(index=False) - - def __call__(self, fnm): - file_like_object = BytesIO(fnm) if not isinstance(fnm, str) else fnm - wb = RAGFlowExcelParser._load_excel_to_workbook(file_like_object) - - res = [] - for sheetname in wb.sheetnames: - ws = wb[sheetname] - rows = list(ws.rows) - if not rows: - continue - ti = list(rows[0]) - for r in list(rows[1:]): - fields = [] - for i, c in enumerate(r): - if not c.value: - continue - t = str(ti[i].value) if i < len(ti) else "" - t += (":" if t else "") + str(c.value) - fields.append(t) - line = "; ".join(fields) - if sheetname.lower().find("sheet") < 0: - line += " ——" + sheetname - res.append(line) - return res - - @staticmethod - def row_number(fnm, binary): - if fnm.split(".")[-1].lower().find("xls") >= 0: - wb = RAGFlowExcelParser._load_excel_to_workbook(BytesIO(binary)) - total = 0 - for sheetname in wb.sheetnames: - ws = wb[sheetname] - total += len(list(ws.rows)) - return total - - if fnm.split(".")[-1].lower() in ["csv", "txt"]: - encoding = find_codec(binary) - txt = binary.decode(encoding, errors="ignore") - return len(txt.split("\n")) - - -if __name__ == "__main__": - psr = RAGFlowExcelParser() - psr(sys.argv[1]) diff --git a/deepdoc/parser/figure_parser.py b/deepdoc/parser/figure_parser.py deleted file mode 100644 index 0274f54..0000000 --- a/deepdoc/parser/figure_parser.py +++ /dev/null @@ -1,105 +0,0 @@ -# -# Copyright 2025 The InfiniFlow Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -from concurrent.futures import ThreadPoolExecutor, as_completed - -from PIL import Image - -from api.utils.api_utils import timeout -from rag.app.picture import vision_llm_chunk as picture_vision_llm_chunk -from rag.prompts.generator import vision_llm_figure_describe_prompt - - -def vision_figure_parser_figure_data_wrapper(figures_data_without_positions): - return [ - ( - (figure_data[1], [figure_data[0]]), - [(0, 0, 0, 0, 0)], - ) - for figure_data in figures_data_without_positions - if isinstance(figure_data[1], Image.Image) - ] - - -shared_executor = ThreadPoolExecutor(max_workers=10) - - -class VisionFigureParser: - def __init__(self, vision_model, figures_data, *args, **kwargs): - self.vision_model = vision_model - self._extract_figures_info(figures_data) - assert len(self.figures) == len(self.descriptions) - assert not self.positions or (len(self.figures) == len(self.positions)) - - def _extract_figures_info(self, figures_data): - self.figures = [] - self.descriptions = [] - self.positions = [] - - for item in figures_data: - # position - if len(item) == 2 and isinstance(item[0], tuple) and len(item[0]) == 2 and isinstance(item[1], list) and isinstance(item[1][0], tuple) and len(item[1][0]) == 5: - img_desc = item[0] - assert len(img_desc) == 2 and isinstance(img_desc[0], Image.Image) and isinstance(img_desc[1], list), "Should be (figure, [description])" - self.figures.append(img_desc[0]) - self.descriptions.append(img_desc[1]) - self.positions.append(item[1]) - else: - assert len(item) == 2 and isinstance(item[0], Image.Image) and isinstance(item[1], list), f"Unexpected form of figure data: get {len(item)=}, {item=}" - self.figures.append(item[0]) - self.descriptions.append(item[1]) - - def _assemble(self): - self.assembled = [] - self.has_positions = len(self.positions) != 0 - for i in range(len(self.figures)): - figure = self.figures[i] - desc = self.descriptions[i] - pos = self.positions[i] if self.has_positions else None - - figure_desc = (figure, desc) - - if pos is not None: - self.assembled.append((figure_desc, pos)) - else: - self.assembled.append((figure_desc,)) - - return self.assembled - - def __call__(self, **kwargs): - callback = kwargs.get("callback", lambda prog, msg: None) - - @timeout(30, 3) - def process(figure_idx, figure_binary): - description_text = picture_vision_llm_chunk( - binary=figure_binary, - vision_model=self.vision_model, - prompt=vision_llm_figure_describe_prompt(), - callback=callback, - ) - return figure_idx, description_text - - futures = [] - for idx, img_binary in enumerate(self.figures or []): - futures.append(shared_executor.submit(process, idx, img_binary)) - - for future in as_completed(futures): - figure_num, txt = future.result() - if txt: - self.descriptions[figure_num] = txt + "\n".join(self.descriptions[figure_num]) - - self._assemble() - - return self.assembled diff --git a/deepdoc/parser/html_parser.py b/deepdoc/parser/html_parser.py deleted file mode 100644 index 44ff103..0000000 --- a/deepdoc/parser/html_parser.py +++ /dev/null @@ -1,214 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2025 The InfiniFlow Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -from rag.nlp import find_codec, rag_tokenizer -import uuid -import chardet -from bs4 import BeautifulSoup, NavigableString, Tag, Comment -import html - -def get_encoding(file): - with open(file,'rb') as f: - tmp = chardet.detect(f.read()) - return tmp['encoding'] - -BLOCK_TAGS = [ - "h1", "h2", "h3", "h4", "h5", "h6", - "p", "div", "article", "section", "aside", - "ul", "ol", "li", - "table", "pre", "code", "blockquote", - "figure", "figcaption" -] -TITLE_TAGS = {"h1": "#", "h2": "##", "h3": "###", "h4": "#####", "h5": "#####", "h6": "######"} - - -class RAGFlowHtmlParser: - def __call__(self, fnm, binary=None, chunk_token_num=512): - if binary: - encoding = find_codec(binary) - txt = binary.decode(encoding, errors="ignore") - else: - with open(fnm, "r",encoding=get_encoding(fnm)) as f: - txt = f.read() - return self.parser_txt(txt, chunk_token_num) - - @classmethod - def parser_txt(cls, txt, chunk_token_num): - if not isinstance(txt, str): - raise TypeError("txt type should be string!") - - temp_sections = [] - soup = BeautifulSoup(txt, "html5lib") - # delete - - - %s - - -""" % TableStructureRecognizer.construct_table(boxes, html=True) - return html - - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument('--inputs', - help="Directory where to store images or PDFs, or a file path to a single image or PDF", - required=True) - parser.add_argument('--output_dir', help="Directory where to store the output images. Default: './layouts_outputs'", - default="./layouts_outputs") - parser.add_argument( - '--threshold', - help="A threshold to filter out detections. Default: 0.5", - default=0.5) - parser.add_argument('--mode', help="Task mode: layout recognition or table structure recognition", choices=["layout", "tsr"], - default="layout") - args = parser.parse_args() - main(args) diff --git a/deepdoc/vision/table_structure_recognizer.py b/deepdoc/vision/table_structure_recognizer.py deleted file mode 100644 index 7f4736c..0000000 --- a/deepdoc/vision/table_structure_recognizer.py +++ /dev/null @@ -1,612 +0,0 @@ -# -# Copyright 2025 The InfiniFlow Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -import logging -import os -import re -from collections import Counter - -import numpy as np -from huggingface_hub import snapshot_download - -from api.utils.file_utils import get_project_base_directory -from rag.nlp import rag_tokenizer - -from .recognizer import Recognizer - - -class TableStructureRecognizer(Recognizer): - labels = [ - "table", - "table column", - "table row", - "table column header", - "table projected row header", - "table spanning cell", - ] - - def __init__(self): - try: - super().__init__(self.labels, "tsr", os.path.join(get_project_base_directory(), "rag/res/deepdoc")) - except Exception: - super().__init__( - self.labels, - "tsr", - snapshot_download( - repo_id="InfiniFlow/deepdoc", - local_dir=os.path.join(get_project_base_directory(), "rag/res/deepdoc"), - local_dir_use_symlinks=False, - ), - ) - - def __call__(self, images, thr=0.2): - table_structure_recognizer_type = os.getenv("TABLE_STRUCTURE_RECOGNIZER_TYPE", "onnx").lower() - if table_structure_recognizer_type not in ["onnx", "ascend"]: - raise RuntimeError("Unsupported table structure recognizer type.") - - if table_structure_recognizer_type == "onnx": - logging.debug("Using Onnx table structure recognizer", flush=True) - tbls = super().__call__(images, thr) - else: # ascend - logging.debug("Using Ascend table structure recognizer", flush=True) - tbls = self._run_ascend_tsr(images, thr) - - res = [] - # align left&right for rows, align top&bottom for columns - for tbl in tbls: - lts = [ - { - "label": b["type"], - "score": b["score"], - "x0": b["bbox"][0], - "x1": b["bbox"][2], - "top": b["bbox"][1], - "bottom": b["bbox"][-1], - } - for b in tbl - ] - if not lts: - continue - - left = [b["x0"] for b in lts if b["label"].find("row") > 0 or b["label"].find("header") > 0] - right = [b["x1"] for b in lts if b["label"].find("row") > 0 or b["label"].find("header") > 0] - if not left: - continue - left = np.mean(left) if len(left) > 4 else np.min(left) - right = np.mean(right) if len(right) > 4 else np.max(right) - for b in lts: - if b["label"].find("row") > 0 or b["label"].find("header") > 0: - if b["x0"] > left: - b["x0"] = left - if b["x1"] < right: - b["x1"] = right - - top = [b["top"] for b in lts if b["label"] == "table column"] - bottom = [b["bottom"] for b in lts if b["label"] == "table column"] - if not top: - res.append(lts) - continue - top = np.median(top) if len(top) > 4 else np.min(top) - bottom = np.median(bottom) if len(bottom) > 4 else np.max(bottom) - for b in lts: - if b["label"] == "table column": - if b["top"] > top: - b["top"] = top - if b["bottom"] < bottom: - b["bottom"] = bottom - - res.append(lts) - return res - - @staticmethod - def is_caption(bx): - patt = [r"[图表]+[ 0-9::]{2,}"] - if any([re.match(p, bx["text"].strip()) for p in patt]) or bx.get("layout_type", "").find("caption") >= 0: - return True - return False - - @staticmethod - def blockType(b): - patt = [ - ("^(20|19)[0-9]{2}[年/-][0-9]{1,2}[月/-][0-9]{1,2}日*$", "Dt"), - (r"^(20|19)[0-9]{2}年$", "Dt"), - (r"^(20|19)[0-9]{2}[年-][0-9]{1,2}月*$", "Dt"), - ("^[0-9]{1,2}[月-][0-9]{1,2}日*$", "Dt"), - (r"^第*[一二三四1-4]季度$", "Dt"), - (r"^(20|19)[0-9]{2}年*[一二三四1-4]季度$", "Dt"), - (r"^(20|19)[0-9]{2}[ABCDE]$", "Dt"), - ("^[0-9.,+%/ -]+$", "Nu"), - (r"^[0-9A-Z/\._~-]+$", "Ca"), - (r"^[A-Z]*[a-z' -]+$", "En"), - (r"^[0-9.,+-]+[0-9A-Za-z/$¥%<>()()' -]+$", "NE"), - (r"^.{1}$", "Sg"), - ] - for p, n in patt: - if re.search(p, b["text"].strip()): - return n - tks = [t for t in rag_tokenizer.tokenize(b["text"]).split() if len(t) > 1] - if len(tks) > 3: - if len(tks) < 12: - return "Tx" - else: - return "Lx" - - if len(tks) == 1 and rag_tokenizer.tag(tks[0]) == "nr": - return "Nr" - - return "Ot" - - @staticmethod - def construct_table(boxes, is_english=False, html=True, **kwargs): - cap = "" - i = 0 - while i < len(boxes): - if TableStructureRecognizer.is_caption(boxes[i]): - if is_english: - cap + " " - cap += boxes[i]["text"] - boxes.pop(i) - i -= 1 - i += 1 - - if not boxes: - return [] - for b in boxes: - b["btype"] = TableStructureRecognizer.blockType(b) - max_type = Counter([b["btype"] for b in boxes]).items() - max_type = max(max_type, key=lambda x: x[1])[0] if max_type else "" - logging.debug("MAXTYPE: " + max_type) - - rowh = [b["R_bott"] - b["R_top"] for b in boxes if "R" in b] - rowh = np.min(rowh) if rowh else 0 - boxes = Recognizer.sort_R_firstly(boxes, rowh / 2) - # for b in boxes:print(b) - boxes[0]["rn"] = 0 - rows = [[boxes[0]]] - btm = boxes[0]["bottom"] - for b in boxes[1:]: - b["rn"] = len(rows) - 1 - lst_r = rows[-1] - if lst_r[-1].get("R", "") != b.get("R", "") or (b["top"] >= btm - 3 and lst_r[-1].get("R", "-1") != b.get("R", "-2")): # new row - btm = b["bottom"] - b["rn"] += 1 - rows.append([b]) - continue - btm = (btm + b["bottom"]) / 2.0 - rows[-1].append(b) - - colwm = [b["C_right"] - b["C_left"] for b in boxes if "C" in b] - colwm = np.min(colwm) if colwm else 0 - crosspage = len(set([b["page_number"] for b in boxes])) > 1 - if crosspage: - boxes = Recognizer.sort_X_firstly(boxes, colwm / 2) - else: - boxes = Recognizer.sort_C_firstly(boxes, colwm / 2) - boxes[0]["cn"] = 0 - cols = [[boxes[0]]] - right = boxes[0]["x1"] - for b in boxes[1:]: - b["cn"] = len(cols) - 1 - lst_c = cols[-1] - if (int(b.get("C", "1")) - int(lst_c[-1].get("C", "1")) == 1 and b["page_number"] == lst_c[-1]["page_number"]) or ( - b["x0"] >= right and lst_c[-1].get("C", "-1") != b.get("C", "-2") - ): # new col - right = b["x1"] - b["cn"] += 1 - cols.append([b]) - continue - right = (right + b["x1"]) / 2.0 - cols[-1].append(b) - - tbl = [[[] for _ in range(len(cols))] for _ in range(len(rows))] - for b in boxes: - tbl[b["rn"]][b["cn"]].append(b) - - if len(rows) >= 4: - # remove single in column - j = 0 - while j < len(tbl[0]): - e, ii = 0, 0 - for i in range(len(tbl)): - if tbl[i][j]: - e += 1 - ii = i - if e > 1: - break - if e > 1: - j += 1 - continue - f = (j > 0 and tbl[ii][j - 1] and tbl[ii][j - 1][0].get("text")) or j == 0 - ff = (j + 1 < len(tbl[ii]) and tbl[ii][j + 1] and tbl[ii][j + 1][0].get("text")) or j + 1 >= len(tbl[ii]) - if f and ff: - j += 1 - continue - bx = tbl[ii][j][0] - logging.debug("Relocate column single: " + bx["text"]) - # j column only has one value - left, right = 100000, 100000 - if j > 0 and not f: - for i in range(len(tbl)): - if tbl[i][j - 1]: - left = min(left, np.min([bx["x0"] - a["x1"] for a in tbl[i][j - 1]])) - if j + 1 < len(tbl[0]) and not ff: - for i in range(len(tbl)): - if tbl[i][j + 1]: - right = min(right, np.min([a["x0"] - bx["x1"] for a in tbl[i][j + 1]])) - assert left < 100000 or right < 100000 - if left < right: - for jj in range(j, len(tbl[0])): - for i in range(len(tbl)): - for a in tbl[i][jj]: - a["cn"] -= 1 - if tbl[ii][j - 1]: - tbl[ii][j - 1].extend(tbl[ii][j]) - else: - tbl[ii][j - 1] = tbl[ii][j] - for i in range(len(tbl)): - tbl[i].pop(j) - - else: - for jj in range(j + 1, len(tbl[0])): - for i in range(len(tbl)): - for a in tbl[i][jj]: - a["cn"] -= 1 - if tbl[ii][j + 1]: - tbl[ii][j + 1].extend(tbl[ii][j]) - else: - tbl[ii][j + 1] = tbl[ii][j] - for i in range(len(tbl)): - tbl[i].pop(j) - cols.pop(j) - assert len(cols) == len(tbl[0]), "Column NO. miss matched: %d vs %d" % (len(cols), len(tbl[0])) - - if len(cols) >= 4: - # remove single in row - i = 0 - while i < len(tbl): - e, jj = 0, 0 - for j in range(len(tbl[i])): - if tbl[i][j]: - e += 1 - jj = j - if e > 1: - break - if e > 1: - i += 1 - continue - f = (i > 0 and tbl[i - 1][jj] and tbl[i - 1][jj][0].get("text")) or i == 0 - ff = (i + 1 < len(tbl) and tbl[i + 1][jj] and tbl[i + 1][jj][0].get("text")) or i + 1 >= len(tbl) - if f and ff: - i += 1 - continue - - bx = tbl[i][jj][0] - logging.debug("Relocate row single: " + bx["text"]) - # i row only has one value - up, down = 100000, 100000 - if i > 0 and not f: - for j in range(len(tbl[i - 1])): - if tbl[i - 1][j]: - up = min(up, np.min([bx["top"] - a["bottom"] for a in tbl[i - 1][j]])) - if i + 1 < len(tbl) and not ff: - for j in range(len(tbl[i + 1])): - if tbl[i + 1][j]: - down = min(down, np.min([a["top"] - bx["bottom"] for a in tbl[i + 1][j]])) - assert up < 100000 or down < 100000 - if up < down: - for ii in range(i, len(tbl)): - for j in range(len(tbl[ii])): - for a in tbl[ii][j]: - a["rn"] -= 1 - if tbl[i - 1][jj]: - tbl[i - 1][jj].extend(tbl[i][jj]) - else: - tbl[i - 1][jj] = tbl[i][jj] - tbl.pop(i) - - else: - for ii in range(i + 1, len(tbl)): - for j in range(len(tbl[ii])): - for a in tbl[ii][j]: - a["rn"] -= 1 - if tbl[i + 1][jj]: - tbl[i + 1][jj].extend(tbl[i][jj]) - else: - tbl[i + 1][jj] = tbl[i][jj] - tbl.pop(i) - rows.pop(i) - - # which rows are headers - hdset = set([]) - for i in range(len(tbl)): - cnt, h = 0, 0 - for j, arr in enumerate(tbl[i]): - if not arr: - continue - cnt += 1 - if max_type == "Nu" and arr[0]["btype"] == "Nu": - continue - if any([a.get("H") for a in arr]) or (max_type == "Nu" and arr[0]["btype"] != "Nu"): - h += 1 - if h / cnt > 0.5: - hdset.add(i) - - if html: - return TableStructureRecognizer.__html_table(cap, hdset, TableStructureRecognizer.__cal_spans(boxes, rows, cols, tbl, True)) - - return TableStructureRecognizer.__desc_table(cap, hdset, TableStructureRecognizer.__cal_spans(boxes, rows, cols, tbl, False), is_english) - - @staticmethod - def __html_table(cap, hdset, tbl): - # constrcut HTML - html = "" - if cap: - html += f"" - for i in range(len(tbl)): - row = "" - txts = [] - for j, arr in enumerate(tbl[i]): - if arr is None: - continue - if not arr: - row += "" if i not in hdset else "" - continue - txt = "" - if arr: - h = min(np.min([c["bottom"] - c["top"] for c in arr]) / 2, 10) - txt = " ".join([c["text"] for c in Recognizer.sort_Y_firstly(arr, h)]) - txts.append(txt) - sp = "" - if arr[0].get("colspan"): - sp = "colspan={}".format(arr[0]["colspan"]) - if arr[0].get("rowspan"): - sp += " rowspan={}".format(arr[0]["rowspan"]) - if i in hdset: - row += f"" - else: - row += f"" - - if i in hdset: - if all([t in hdset for t in txts]): - continue - for t in txts: - hdset.add(t) - - if row != "": - row += "" - else: - row = "" - html += "\n" + row - html += "\n
{cap}
" + txt + "" + txt + "
" - return html - - @staticmethod - def __desc_table(cap, hdr_rowno, tbl, is_english): - # get text of every colomn in header row to become header text - clmno = len(tbl[0]) - rowno = len(tbl) - headers = {} - hdrset = set() - lst_hdr = [] - de = "的" if not is_english else " for " - for r in sorted(list(hdr_rowno)): - headers[r] = ["" for _ in range(clmno)] - for i in range(clmno): - if not tbl[r][i]: - continue - txt = " ".join([a["text"].strip() for a in tbl[r][i]]) - headers[r][i] = txt - hdrset.add(txt) - if all([not t for t in headers[r]]): - del headers[r] - hdr_rowno.remove(r) - continue - for j in range(clmno): - if headers[r][j]: - continue - if j >= len(lst_hdr): - break - headers[r][j] = lst_hdr[j] - lst_hdr = headers[r] - for i in range(rowno): - if i not in hdr_rowno: - continue - for j in range(i + 1, rowno): - if j not in hdr_rowno: - break - for k in range(clmno): - if not headers[j - 1][k]: - continue - if headers[j][k].find(headers[j - 1][k]) >= 0: - continue - if len(headers[j][k]) > len(headers[j - 1][k]): - headers[j][k] += (de if headers[j][k] else "") + headers[j - 1][k] - else: - headers[j][k] = headers[j - 1][k] + (de if headers[j - 1][k] else "") + headers[j][k] - - logging.debug(f">>>>>>>>>>>>>>>>>{cap}:SIZE:{rowno}X{clmno} Header: {hdr_rowno}") - row_txt = [] - for i in range(rowno): - if i in hdr_rowno: - continue - rtxt = [] - - def append(delimer): - nonlocal rtxt, row_txt - rtxt = delimer.join(rtxt) - if row_txt and len(row_txt[-1]) + len(rtxt) < 64: - row_txt[-1] += "\n" + rtxt - else: - row_txt.append(rtxt) - - r = 0 - if len(headers.items()): - _arr = [(i - r, r) for r, _ in headers.items() if r < i] - if _arr: - _, r = min(_arr, key=lambda x: x[0]) - - if r not in headers and clmno <= 2: - for j in range(clmno): - if not tbl[i][j]: - continue - txt = "".join([a["text"].strip() for a in tbl[i][j]]) - if txt: - rtxt.append(txt) - if rtxt: - append(":") - continue - - for j in range(clmno): - if not tbl[i][j]: - continue - txt = "".join([a["text"].strip() for a in tbl[i][j]]) - if not txt: - continue - ctt = headers[r][j] if r in headers else "" - if ctt: - ctt += ":" - ctt += txt - if ctt: - rtxt.append(ctt) - - if rtxt: - row_txt.append("; ".join(rtxt)) - - if cap: - if is_english: - from_ = " in " - else: - from_ = "来自" - row_txt = [t + f"\t——{from_}“{cap}”" for t in row_txt] - return row_txt - - @staticmethod - def __cal_spans(boxes, rows, cols, tbl, html=True): - # caculate span - clft = [np.mean([c.get("C_left", c["x0"]) for c in cln]) for cln in cols] - crgt = [np.mean([c.get("C_right", c["x1"]) for c in cln]) for cln in cols] - rtop = [np.mean([c.get("R_top", c["top"]) for c in row]) for row in rows] - rbtm = [np.mean([c.get("R_btm", c["bottom"]) for c in row]) for row in rows] - for b in boxes: - if "SP" not in b: - continue - b["colspan"] = [b["cn"]] - b["rowspan"] = [b["rn"]] - # col span - for j in range(0, len(clft)): - if j == b["cn"]: - continue - if clft[j] + (crgt[j] - clft[j]) / 2 < b["H_left"]: - continue - if crgt[j] - (crgt[j] - clft[j]) / 2 > b["H_right"]: - continue - b["colspan"].append(j) - # row span - for j in range(0, len(rtop)): - if j == b["rn"]: - continue - if rtop[j] + (rbtm[j] - rtop[j]) / 2 < b["H_top"]: - continue - if rbtm[j] - (rbtm[j] - rtop[j]) / 2 > b["H_bott"]: - continue - b["rowspan"].append(j) - - def join(arr): - if not arr: - return "" - return "".join([t["text"] for t in arr]) - - # rm the spaning cells - for i in range(len(tbl)): - for j, arr in enumerate(tbl[i]): - if not arr: - continue - if all(["rowspan" not in a and "colspan" not in a for a in arr]): - continue - rowspan, colspan = [], [] - for a in arr: - if isinstance(a.get("rowspan", 0), list): - rowspan.extend(a["rowspan"]) - if isinstance(a.get("colspan", 0), list): - colspan.extend(a["colspan"]) - rowspan, colspan = set(rowspan), set(colspan) - if len(rowspan) < 2 and len(colspan) < 2: - for a in arr: - if "rowspan" in a: - del a["rowspan"] - if "colspan" in a: - del a["colspan"] - continue - rowspan, colspan = sorted(rowspan), sorted(colspan) - rowspan = list(range(rowspan[0], rowspan[-1] + 1)) - colspan = list(range(colspan[0], colspan[-1] + 1)) - assert i in rowspan, rowspan - assert j in colspan, colspan - arr = [] - for r in rowspan: - for c in colspan: - arr_txt = join(arr) - if tbl[r][c] and join(tbl[r][c]) != arr_txt: - arr.extend(tbl[r][c]) - tbl[r][c] = None if html else arr - for a in arr: - if len(rowspan) > 1: - a["rowspan"] = len(rowspan) - elif "rowspan" in a: - del a["rowspan"] - if len(colspan) > 1: - a["colspan"] = len(colspan) - elif "colspan" in a: - del a["colspan"] - tbl[rowspan[0]][colspan[0]] = arr - - return tbl - - def _run_ascend_tsr(self, image_list, thr=0.2, batch_size=16): - import math - - from ais_bench.infer.interface import InferSession - - model_dir = os.path.join(get_project_base_directory(), "rag/res/deepdoc") - model_file_path = os.path.join(model_dir, "tsr.om") - - if not os.path.exists(model_file_path): - raise ValueError(f"Model file not found: {model_file_path}") - - device_id = int(os.getenv("ASCEND_LAYOUT_RECOGNIZER_DEVICE_ID", 0)) - session = InferSession(device_id=device_id, model_path=model_file_path) - - images = [np.array(im) if not isinstance(im, np.ndarray) else im for im in image_list] - results = [] - - conf_thr = max(thr, 0.08) - - batch_loop_cnt = math.ceil(float(len(images)) / batch_size) - for bi in range(batch_loop_cnt): - s = bi * batch_size - e = min((bi + 1) * batch_size, len(images)) - batch_images = images[s:e] - - inputs_list = self.preprocess(batch_images) - for ins in inputs_list: - feeds = [] - if "image" in ins: - feeds.append(ins["image"]) - else: - feeds.append(ins[self.input_names[0]]) - output_list = session.infer(feeds=feeds, mode="static") - bb = self.postprocess(output_list, ins, conf_thr) - results.append(bb) - return results diff --git a/docker/.env b/docker/.env index 4fa4816..db3a4cc 100644 --- a/docker/.env +++ b/docker/.env @@ -37,9 +37,12 @@ OPENSEARCH_PASSWORD=infini_rag_flow_OS_01 # The port used to expose the Kibana service to the host machine, # allowing EXTERNAL access to the service running inside the Docker container. +# To enable kibana, you need to: +# 1. Ensure that COMPOSE_PROFILES includes kibana, for example: COMPOSE_PROFILES=${DOC_ENGINE},kibana +# 2. Comment out or delete the following configurations of the es service in docker-compose-base.yml: xpack.security.enabled、xpack.security.http.ssl.enabled、xpack.security.transport.ssl.enabled (for details: https://www.elastic.co/docs/deploy-manage/security/self-auto-setup#stack-existing-settings-detected) +# 3. Adjust the es.hosts in conf/service_config.yaml or docker/service_conf.yaml.template to 'https://localhost:1200' +# 4. After the startup is successful, in the es container, execute the command to generate the kibana token: `bin/elasticsearch-create-enrollment-token -s kibana`, then you can use kibana normally KIBANA_PORT=6601 -KIBANA_USER=rag_flow -KIBANA_PASSWORD=infini_rag_flow # The maximum amount of the memory, in bytes, that a specific Docker container can use while running. # Update it according to the available memory in the host machine. @@ -91,15 +94,16 @@ REDIS_PASSWORD=infini_rag_flow # The port used to expose RAGFlow's HTTP API service to the host machine, # allowing EXTERNAL access to the service running inside the Docker container. SVR_HTTP_PORT=9380 +ADMIN_SVR_HTTP_PORT=9381 # The RAGFlow Docker image to download. -# Defaults to the v0.20.5-slim edition, which is the RAGFlow Docker image without embedding models. -RAGFLOW_IMAGE=infiniflow/ragflow:fastapi +# Defaults to the v0.21.1-slim edition, which is the RAGFlow Docker image without embedding models. +RAGFLOW_IMAGE=infiniflow/ragflow:v0.21.1-slim # # To download the RAGFlow Docker image with embedding models, uncomment the following line instead: -# RAGFLOW_IMAGE=infiniflow/ragflow:v0.20.5 +# RAGFLOW_IMAGE=infiniflow/ragflow:v0.21.1 # -# The Docker image of the v0.20.5 edition includes built-in embedding models: +# The Docker image of the v0.21.1 edition includes built-in embedding models: # - BAAI/bge-large-zh-v1.5 # - maidalun1020/bce-embedding-base_v1 # @@ -192,12 +196,8 @@ REGISTER_ENABLED=1 # - For OpenSearch: # COMPOSE_PROFILES=opensearch,sandbox - - POSTGRES_DBNAME=rag_flow POSTGRES_USER=rag_flow POSTGRES_PASSWORD=infini_rag_flow POSTGRES_PORT=5432 -DB_TYPE=postgres - -USE_OCR_HTTP=true \ No newline at end of file +DB_TYPE=postgres \ No newline at end of file diff --git a/docker/README.md b/docker/README.md index 4a4fbb1..0c80dfc 100644 --- a/docker/README.md +++ b/docker/README.md @@ -1,85 +1,269 @@ -# RAGFlow Docker 服务管理 +# README -## 问题解决 +
+📗 Table of Contents -原来的配置每次启动 `docker-compose.yml` 都会重新创建 `docker-compose-base.yml` 中的服务,现在已修改为只启动 ragflow 服务。 +- 🐳 [Docker Compose](#-docker-compose) +- 🐬 [Docker environment variables](#-docker-environment-variables) +- 🐋 [Service configuration](#-service-configuration) +- 📋 [Setup Examples](#-setup-examples) -## Docker Compose 网络命名说明 +
-Docker Compose 会自动为网络名称添加项目前缀: -- **项目名** + **网络名** = 最终网络名称 -- 默认项目名通常是目录名:`ragflow-20250916` -- 最终网络名:`ragflow-20250916_ragflow` +## 🐳 Docker Compose -## 修改内容 +- **docker-compose.yml** + Sets up environment for RAGFlow and its dependencies. +- **docker-compose-base.yml** + Sets up environment for RAGFlow's dependencies: Elasticsearch/[Infinity](https://github.com/infiniflow/infinity), MySQL, MinIO, and Redis. -1. **移除了 `include` 指令**:不再包含 `docker-compose-base.yml` -2. **使用外部网络**:ragflow 服务连接到由 `docker-compose-base.yml` 创建的 `ragflow-20250916_ragflow` 网络 -3. **移除了 `depends_on`**:不再依赖 postgres 健康检查 -4. **网络配置**: - - `docker-compose-base.yml` 创建名为 `ragflow-20250916_ragflow` 的网络 - - `docker-compose.yml` 使用 `external: true` 连接到已存在的网络 -5. **使用项目名**:通过 `-p ragflow` 参数统一项目名 +> [!CAUTION] +> We do not actively maintain **docker-compose-CN-oc9.yml**, **docker-compose-gpu-CN-oc9.yml**, or **docker-compose-gpu.yml**, so use them at your own risk. However, you are welcome to file a pull request to improve any of them. -## 使用方法 +## 🐬 Docker environment variables -### 首次使用(初始化) -```bash -# 1. 启动基础服务(创建网络) -docker-compose -p ragflow -f docker-compose-base.yml up -d +The [.env](./.env) file contains important environment variables for Docker. -# 2. 启动 ragflow 服务 -docker-compose -p ragflow -f docker-compose.yml up -d ragflow -``` +### Elasticsearch -### 日常使用(只启动 ragflow) -```bash -# 使用脚本(推荐) -./start-ragflow.sh +- `STACK_VERSION` + The version of Elasticsearch. Defaults to `8.11.3` +- `ES_PORT` + The port used to expose the Elasticsearch service to the host machine, allowing **external** access to the service running inside the Docker container. Defaults to `1200`. +- `ELASTIC_PASSWORD` + The password for Elasticsearch. -# 或手动启动 -docker-compose -p ragflow -f docker-compose.yml up -d ragflow -``` +### Kibana -### 使用 ragflow.sh(完整管理) -```bash -# 启动 RAGFlow 服务(不重新创建基础服务) -./ragflow.sh start +- `KIBANA_PORT` + The port used to expose the Kibana service to the host machine, allowing **external** access to the service running inside the Docker container. Defaults to `6601`. +- `KIBANA_USER` + The username for Kibana. Defaults to `rag_flow`. +- `KIBANA_PASSWORD` + The password for Kibana. Defaults to `infini_rag_flow`. -# 停止 RAGFlow 服务(保留基础服务) -./ragflow.sh stop +### Resource management -# 重启 RAGFlow 服务 -./ragflow.sh restart +- `MEM_LIMIT` + The maximum amount of the memory, in bytes, that *a specific* Docker container can use while running. Defaults to `8073741824`. -# 查看服务状态 -./ragflow.sh status +### MySQL -# 查看日志 -./ragflow.sh logs -``` +- `MYSQL_PASSWORD` + The password for MySQL. +- `MYSQL_PORT` + The port used to expose the MySQL service to the host machine, allowing **external** access to the MySQL database running inside the Docker container. Defaults to `5455`. -### 手动操作 -```bash -# 只启动基础服务 -docker-compose -f docker-compose-base.yml up -d +### MinIO -# 只启动 ragflow 服务 -docker-compose -f docker-compose.yml up -d ragflow +- `MINIO_CONSOLE_PORT` + The port used to expose the MinIO console interface to the host machine, allowing **external** access to the web-based console running inside the Docker container. Defaults to `9001` +- `MINIO_PORT` + The port used to expose the MinIO API service to the host machine, allowing **external** access to the MinIO object storage service running inside the Docker container. Defaults to `9000`. +- `MINIO_USER` + The username for MinIO. +- `MINIO_PASSWORD` + The password for MinIO. -# 停止 ragflow 服务 -docker-compose -f docker-compose.yml down -``` +### Redis -## 服务说明 +- `REDIS_PORT` + The port used to expose the Redis service to the host machine, allowing **external** access to the Redis service running inside the Docker container. Defaults to `6379`. +- `REDIS_PASSWORD` + The password for Redis. -- **基础服务**:postgres、redis、minio、opensearch -- **应用服务**:ragflow-server -- **网络**:ragflow(外部网络) +### RAGFlow -## 优势 +- `SVR_HTTP_PORT` + The port used to expose RAGFlow's HTTP API service to the host machine, allowing **external** access to the service running inside the Docker container. Defaults to `9380`. +- `RAGFLOW-IMAGE` + The Docker image edition. Available editions: + + - `infiniflow/ragflow:v0.21.1-slim` (default): The RAGFlow Docker image without embedding models. + - `infiniflow/ragflow:v0.21.1`: The RAGFlow Docker image with embedding models including: + - Built-in embedding models: + - `BAAI/bge-large-zh-v1.5` + - `maidalun1020/bce-embedding-base_v1` -1. **快速启动**:只启动需要的服务 -2. **数据持久**:基础服务数据不会丢失 -3. **灵活管理**:可以独立管理各个服务 -4. **资源节约**:避免不必要的服务重建 \ No newline at end of file + +> [!TIP] +> If you cannot download the RAGFlow Docker image, try the following mirrors. +> +> - For the `nightly-slim` edition: +> - `RAGFLOW_IMAGE=swr.cn-north-4.myhuaweicloud.com/infiniflow/ragflow:nightly-slim` or, +> - `RAGFLOW_IMAGE=registry.cn-hangzhou.aliyuncs.com/infiniflow/ragflow:nightly-slim`. +> - For the `nightly` edition: +> - `RAGFLOW_IMAGE=swr.cn-north-4.myhuaweicloud.com/infiniflow/ragflow:nightly` or, +> - `RAGFLOW_IMAGE=registry.cn-hangzhou.aliyuncs.com/infiniflow/ragflow:nightly`. + +### Timezone + +- `TIMEZONE` + The local time zone. Defaults to `'Asia/Shanghai'`. + +### Hugging Face mirror site + +- `HF_ENDPOINT` + The mirror site for huggingface.co. It is disabled by default. You can uncomment this line if you have limited access to the primary Hugging Face domain. + +### MacOS + +- `MACOS` + Optimizations for macOS. It is disabled by default. You can uncomment this line if your OS is macOS. + +### Maximum file size + +- `MAX_CONTENT_LENGTH` + The maximum file size for each uploaded file, in bytes. You can uncomment this line if you wish to change the 128M file size limit. After making the change, ensure you update `client_max_body_size` in nginx/nginx.conf correspondingly. + +### Doc bulk size + +- `DOC_BULK_SIZE` + The number of document chunks processed in a single batch during document parsing. Defaults to `4`. + +### Embedding batch size + +- `EMBEDDING_BATCH_SIZE` + The number of text chunks processed in a single batch during embedding vectorization. Defaults to `16`. + +## 🐋 Service configuration + +[service_conf.yaml](./service_conf.yaml) specifies the system-level configuration for RAGFlow and is used by its API server and task executor. In a dockerized setup, this file is automatically created based on the [service_conf.yaml.template](./service_conf.yaml.template) file (replacing all environment variables by their values). + +- `ragflow` + - `host`: The API server's IP address inside the Docker container. Defaults to `0.0.0.0`. + - `port`: The API server's serving port inside the Docker container. Defaults to `9380`. + +- `mysql` + - `name`: The MySQL database name. Defaults to `rag_flow`. + - `user`: The username for MySQL. + - `password`: The password for MySQL. + - `port`: The MySQL serving port inside the Docker container. Defaults to `3306`. + - `max_connections`: The maximum number of concurrent connections to the MySQL database. Defaults to `100`. + - `stale_timeout`: Timeout in seconds. + +- `minio` + - `user`: The username for MinIO. + - `password`: The password for MinIO. + - `host`: The MinIO serving IP *and* port inside the Docker container. Defaults to `minio:9000`. + +- `oss` + - `access_key`: The access key ID used to authenticate requests to the OSS service. + - `secret_key`: The secret access key used to authenticate requests to the OSS service. + - `endpoint_url`: The URL of the OSS service endpoint. + - `region`: The OSS region where the bucket is located. + - `bucket`: The name of the OSS bucket where files will be stored. When you want to store all files in a specified bucket, you need this configuration item. + - `prefix_path`: Optional. A prefix path to prepend to file names in the OSS bucket, which can help organize files within the bucket. + +- `s3`: + - `access_key`: The access key ID used to authenticate requests to the S3 service. + - `secret_key`: The secret access key used to authenticate requests to the S3 service. + - `endpoint_url`: The URL of the S3-compatible service endpoint. This is necessary when using an S3-compatible protocol instead of the default AWS S3 endpoint. + - `bucket`: The name of the S3 bucket where files will be stored. When you want to store all files in a specified bucket, you need this configuration item. + - `region`: The AWS region where the S3 bucket is located. This is important for directing requests to the correct data center. + - `signature_version`: Optional. The version of the signature to use for authenticating requests. Common versions include `v4`. + - `addressing_style`: Optional. The style of addressing to use for the S3 endpoint. This can be `path` or `virtual`. + - `prefix_path`: Optional. A prefix path to prepend to file names in the S3 bucket, which can help organize files within the bucket. + +- `oauth` + The OAuth configuration for signing up or signing in to RAGFlow using a third-party account. + - ``: Custom channel ID. + - `type`: Authentication type, options include `oauth2`, `oidc`, `github`. Default is `oauth2`, when `issuer` parameter is provided, defaults to `oidc`. + - `icon`: Icon ID, options include `github`, `sso`, default is `sso`. + - `display_name`: Channel name, defaults to the Title Case format of the channel ID. + - `client_id`: Required, unique identifier assigned to the client application. + - `client_secret`: Required, secret key for the client application, used for communication with the authentication server. + - `authorization_url`: Base URL for obtaining user authorization. + - `token_url`: URL for exchanging authorization code and obtaining access token. + - `userinfo_url`: URL for obtaining user information (username, email, etc.). + - `issuer`: Base URL of the identity provider. OIDC clients can dynamically obtain the identity provider's metadata (`authorization_url`, `token_url`, `userinfo_url`) through `issuer`. + - `scope`: Requested permission scope, a space-separated string. For example, `openid profile email`. + - `redirect_uri`: Required, URI to which the authorization server redirects during the authentication flow to return results. Must match the callback URI registered with the authentication server. Format: `https://your-app.com/v1/user/oauth/callback/`. For local configuration, you can directly use `http://127.0.0.1:80/v1/user/oauth/callback/`. + +- `user_default_llm` + The default LLM to use for a new RAGFlow user. It is disabled by default. To enable this feature, uncomment the corresponding lines in **service_conf.yaml.template**. + - `factory`: The LLM supplier. Available options: + - `"OpenAI"` + - `"DeepSeek"` + - `"Moonshot"` + - `"Tongyi-Qianwen"` + - `"VolcEngine"` + - `"ZHIPU-AI"` + - `api_key`: The API key for the specified LLM. You will need to apply for your model API key online. + +> [!TIP] +> If you do not set the default LLM here, configure the default LLM on the **Settings** page in the RAGFlow UI. + + +## 📋 Setup Examples + +### 🔒 HTTPS Setup + +#### Prerequisites + +- A registered domain name pointing to your server +- Port 80 and 443 open on your server +- Docker and Docker Compose installed + +#### Getting and configuring certificates (Let's Encrypt) + +If you want your instance to be available under `https`, follow these steps: + +1. **Install Certbot and obtain certificates** + ```bash + # Ubuntu/Debian + sudo apt update && sudo apt install certbot + + # CentOS/RHEL + sudo yum install certbot + + # Obtain certificates (replace with your actual domain) + sudo certbot certonly --standalone -d your-ragflow-domain.com + ``` + +2. **Locate your certificates** + Once generated, your certificates will be located at: + - Certificate: `/etc/letsencrypt/live/your-ragflow-domain.com/fullchain.pem` + - Private key: `/etc/letsencrypt/live/your-ragflow-domain.com/privkey.pem` + +3. **Update docker-compose.yml** + Add the certificate volumes to the `ragflow` service in your `docker-compose.yml`: + ```yaml + services: + ragflow: + # ...existing configuration... + volumes: + # SSL certificates + - /etc/letsencrypt/live/your-ragflow-domain.com/fullchain.pem:/etc/nginx/ssl/fullchain.pem:ro + - /etc/letsencrypt/live/your-ragflow-domain.com/privkey.pem:/etc/nginx/ssl/privkey.pem:ro + # Switch to HTTPS nginx configuration + - ./nginx/ragflow.https.conf:/etc/nginx/conf.d/ragflow.conf + # ...other existing volumes... + + ``` + +4. **Update nginx configuration** + Edit `nginx/ragflow.https.conf` and replace `my_ragflow_domain.com` with your actual domain name. + +5. **Restart the services** + ```bash + docker-compose down + docker-compose up -d + ``` + + +> [!IMPORTANT] +> - Ensure your domain's DNS A record points to your server's IP address +> - Stop any services running on ports 80/443 before obtaining certificates with `--standalone` + +> [!TIP] +> For development or testing, you can use self-signed certificates, but browsers will show security warnings. + +#### Alternative: Using existing certificates + +If you already have SSL certificates from another provider: + +1. Place your certificates in a directory accessible to Docker +2. Update the volume paths in `docker-compose.yml` to point to your certificate files +3. Ensure the certificate file contains the full certificate chain +4. Follow steps 4-5 from the Let's Encrypt guide above \ No newline at end of file diff --git a/docker/docker-compose-base.yml b/docker/docker-compose-base.yml index 9d434b3..1703257 100644 --- a/docker/docker-compose-base.yml +++ b/docker/docker-compose-base.yml @@ -1,7 +1,44 @@ services: + es01: + container_name: ragflow-es-01 + profiles: + - elasticsearch + image: elasticsearch:${STACK_VERSION} + volumes: + - esdata01:/usr/share/elasticsearch/data + ports: + - ${ES_PORT}:9200 + env_file: .env + environment: + - node.name=es01 + - ELASTIC_PASSWORD=${ELASTIC_PASSWORD} + - bootstrap.memory_lock=false + - discovery.type=single-node + - xpack.security.enabled=true + - xpack.security.http.ssl.enabled=false + - xpack.security.transport.ssl.enabled=false + - cluster.routing.allocation.disk.watermark.low=5gb + - cluster.routing.allocation.disk.watermark.high=3gb + - cluster.routing.allocation.disk.watermark.flood_stage=2gb + - TZ=${TIMEZONE} + mem_limit: ${MEM_LIMIT} + ulimits: + memlock: + soft: -1 + hard: -1 + healthcheck: + test: ["CMD-SHELL", "curl http://localhost:9200"] + interval: 10s + timeout: 10s + retries: 120 + networks: + - ragflow + restart: on-failure opensearch01: container_name: ragflow-opensearch-01 + profiles: + - opensearch image: hub.icert.top/opensearchproject/opensearch:2.19.1 volumes: - osdata01:/usr/share/opensearch/data @@ -22,7 +59,6 @@ services: - cluster.routing.allocation.disk.watermark.flood_stage=2gb - TZ=${TIMEZONE} - http.port=9201 - - OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m mem_limit: ${MEM_LIMIT} ulimits: memlock: @@ -37,29 +73,96 @@ services: - ragflow restart: on-failure - - postgres: - image: postgres:15 - container_name: ragflow-postgres + infinity: + container_name: ragflow-infinity + profiles: + - infinity + image: infiniflow/infinity:v0.6.1 + volumes: + - infinity_data:/var/infinity + - ./infinity_conf.toml:/infinity_conf.toml + command: ["-f", "/infinity_conf.toml"] + ports: + - ${INFINITY_THRIFT_PORT}:23817 + - ${INFINITY_HTTP_PORT}:23820 + - ${INFINITY_PSQL_PORT}:5432 env_file: .env environment: - - POSTGRES_DB=${POSTGRES_DBNAME} - - POSTGRES_USER=${POSTGRES_USER} - - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} - TZ=${TIMEZONE} - ports: - - ${POSTGRES_PORT-5440}:5432 - volumes: - - postgres_data:/var/lib/postgresql/data + mem_limit: ${MEM_LIMIT} + ulimits: + nofile: + soft: 500000 + hard: 500000 networks: - ragflow healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DBNAME}"] + test: ["CMD", "curl", "http://localhost:23820/admin/node/current"] + interval: 10s + timeout: 10s + retries: 120 + restart: on-failure + + sandbox-executor-manager: + container_name: ragflow-sandbox-executor-manager + profiles: + - sandbox + image: ${SANDBOX_EXECUTOR_MANAGER_IMAGE-infiniflow/sandbox-executor-manager:latest} + privileged: true + ports: + - ${SANDBOX_EXECUTOR_MANAGER_PORT-9385}:9385 + env_file: .env + volumes: + - /var/run/docker.sock:/var/run/docker.sock + networks: + - ragflow + security_opt: + - no-new-privileges:true + environment: + - TZ=${TIMEZONE} + - SANDBOX_EXECUTOR_MANAGER_POOL_SIZE=${SANDBOX_EXECUTOR_MANAGER_POOL_SIZE:-3} + - SANDBOX_BASE_PYTHON_IMAGE=${SANDBOX_BASE_PYTHON_IMAGE:-infiniflow/sandbox-base-python:latest} + - SANDBOX_BASE_NODEJS_IMAGE=${SANDBOX_BASE_NODEJS_IMAGE:-infiniflow/sandbox-base-nodejs:latest} + - SANDBOX_ENABLE_SECCOMP=${SANDBOX_ENABLE_SECCOMP:-false} + - SANDBOX_MAX_MEMORY=${SANDBOX_MAX_MEMORY:-256m} + - SANDBOX_TIMEOUT=${SANDBOX_TIMEOUT:-10s} + healthcheck: + test: ["CMD", "curl", "http://localhost:9385/healthz"] interval: 10s timeout: 5s retries: 5 restart: on-failure + mysql: + # mysql:5.7 linux/arm64 image is unavailable. + image: mysql:8.0.39 + container_name: ragflow-mysql + env_file: .env + environment: + - MYSQL_ROOT_PASSWORD=${MYSQL_PASSWORD} + - TZ=${TIMEZONE} + command: + --max_connections=1000 + --character-set-server=utf8mb4 + --collation-server=utf8mb4_unicode_ci + --default-authentication-plugin=mysql_native_password + --tls_version="TLSv1.2,TLSv1.3" + --init-file /data/application/init.sql + --binlog_expire_logs_seconds=604800 + ports: + - ${MYSQL_PORT}:3306 + volumes: + - mysql_data:/var/lib/mysql + - ./init.sql:/data/application/init.sql + networks: + - ragflow + healthcheck: + test: ["CMD", "mysqladmin" ,"ping", "-uroot", "-p${MYSQL_PASSWORD}"] + interval: 10s + timeout: 10s + retries: 3 + restart: on-failure + minio: image: quay.io/minio/minio:RELEASE.2025-06-13T11-33-47Z container_name: ragflow-minio @@ -85,7 +188,7 @@ services: redis: # swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/valkey/valkey:8 - image: valkey/valkey + image: valkey/valkey:8 container_name: ragflow-redis command: redis-server --requirepass ${REDIS_PASSWORD} --maxmemory 128mb --maxmemory-policy allkeys-lru env_file: .env @@ -104,24 +207,47 @@ services: start_period: 10s + kibana: + container_name: ragflow-kibana + profiles: + - kibana + image: kibana:${STACK_VERSION} + ports: + - ${KIBANA_PORT-5601}:5601 + env_file: .env + environment: + - TZ=${TIMEZONE} + volumes: + - kibana_data:/usr/share/kibana/data + depends_on: + es01: + condition: service_started + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:5601/api/status"] + interval: 10s + timeout: 10s + retries: 120 + networks: + - ragflow + restart: on-failure + volumes: esdata01: - name: ragflow_esdata01 + driver: local osdata01: - name: ragflow_osdata01 + driver: local infinity_data: - name: ragflow_infinity_data + driver: local mysql_data: - name: ragflow_mysql_data + driver: local minio_data: - name: ragflow_minio_data + driver: local redis_data: - name: ragflow_redis_data - postgres_data: - name: ragflow_postgres_data + driver: local + kibana_data: + driver: local networks: ragflow: - name: ragflow-20250916_ragflow driver: bridge diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 5e23854..960f850 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -1,6 +1,11 @@ +include: + - ./docker-compose-base.yml # To ensure that the container processes the locally modified `service_conf.yaml.template` instead of the one included in its image, you need to mount the local `service_conf.yaml.template` to the container. services: ragflow: + depends_on: + mysql: + condition: service_healthy image: ${RAGFLOW_IMAGE} # Example configuration to set up an MCP server: # command: @@ -17,14 +22,19 @@ services: # - --no-transport-sse-enabled # Disable legacy SSE endpoints (/sse and /messages/) # - --no-transport-streamable-http-enabled # Disable Streamable HTTP transport (/mcp endpoint) # - --no-json-response # Disable JSON response mode in Streamable HTTP transport (instead of SSE over HTTP) + + # Example configration to start Admin server: + # command: + # - --enable-adminserver container_name: ragflow-server ports: - ${SVR_HTTP_PORT}:9380 - - 8000:80 - - 8443:443 - - 15678:5678 - - 15679:5679 - - 19382:9382 # entry for MCP (host_port:docker_port). The docker_port must match the value you set for `mcp-port` above. + - ${ADMIN_SVR_HTTP_PORT}:9381 + - 80:80 + - 443:443 + - 5678:5678 + - 5679:5679 + - 9382:9382 # entry for MCP (host_port:docker_port). The docker_port must match the value you set for `mcp-port` above. volumes: - ./ragflow-logs:/ragflow/logs - ./nginx/ragflow.conf:/etc/nginx/conf.d/ragflow.conf @@ -38,7 +48,6 @@ services: - TZ=${TIMEZONE} - HF_ENDPOINT=${HF_ENDPOINT-} - MACOS=${MACOS-} - - DB_TYPE=postgres networks: - ragflow restart: on-failure @@ -46,8 +55,25 @@ services: # If you use Docker Desktop, the --add-host flag is optional. This flag ensures that the host's internal IP is exposed to the Prometheus container. extra_hosts: - "host.docker.internal:host-gateway" - -networks: - ragflow: - name: ragflow-20250916_ragflow - external: true + # executor: + # depends_on: + # mysql: + # condition: service_healthy + # image: ${RAGFLOW_IMAGE} + # container_name: ragflow-executor + # volumes: + # - ./ragflow-logs:/ragflow/logs + # - ./nginx/ragflow.conf:/etc/nginx/conf.d/ragflow.conf + # env_file: .env + # environment: + # - TZ=${TIMEZONE} + # - HF_ENDPOINT=${HF_ENDPOINT} + # - MACOS=${MACOS} + # entrypoint: "/ragflow/entrypoint_task_executor.sh 1 3" + # networks: + # - ragflow + # restart: on-failure + # # https://docs.docker.com/engine/daemon/prometheus/#create-a-prometheus-configuration + # # If you're using Docker Desktop, the --add-host flag is optional. This flag makes sure that the host's internal IP gets exposed to the Prometheus container. + # extra_hosts: + # - "host.docker.internal:host-gateway" diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index f359d27..7f660d0 100644 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -11,6 +11,7 @@ function usage() { echo " --disable-webserver Disables the web server (nginx + ragflow_server)." echo " --disable-taskexecutor Disables task executor workers." echo " --enable-mcpserver Enables the MCP server." + echo " --enable-adminserver Enables the Admin server." echo " --consumer-no-beg= Start range for consumers (if using range-based)." echo " --consumer-no-end= End range for consumers (if using range-based)." echo " --workers= Number of task executors to run (if range is not used)." @@ -21,12 +22,14 @@ function usage() { echo " $0 --disable-webserver --consumer-no-beg=0 --consumer-no-end=5" echo " $0 --disable-webserver --workers=2 --host-id=myhost123" echo " $0 --enable-mcpserver" + echo " $0 --enable-adminserver" exit 1 } ENABLE_WEBSERVER=1 # Default to enable web server ENABLE_TASKEXECUTOR=1 # Default to enable task executor ENABLE_MCP_SERVER=0 +ENABLE_ADMIN_SERVER=0 # Default close admin server CONSUMER_NO_BEG=0 CONSUMER_NO_END=0 WORKERS=1 @@ -70,6 +73,10 @@ for arg in "$@"; do ENABLE_MCP_SERVER=1 shift ;; + --enable-adminserver) + ENABLE_ADMIN_SERVER=1 + shift + ;; --mcp-host=*) MCP_HOST="${arg#*=}" shift @@ -155,8 +162,6 @@ function task_exe() { while true; do LD_PRELOAD="$JEMALLOC_PATH" \ "$PY" rag/svr/task_executor.py "${host_id}_${consumer_id}" - echo "task_executor exited. Sleeping 5s before restart." - sleep 5 done } @@ -183,10 +188,16 @@ if [[ "${ENABLE_WEBSERVER}" -eq 1 ]]; then echo "Starting ragflow_server..." while true; do - "$PY" api/ragflow_server_fastapi.py + "$PY" api/ragflow_server.py done & fi +if [[ "${ENABLE_ADMIN_SERVER}" -eq 1 ]]; then + echo "Starting admin_server..." + while true; do + "$PY" admin/server/admin_server.py + done & +fi if [[ "${ENABLE_MCP_SERVER}" -eq 1 ]]; then start_mcp_server diff --git a/docker/infinity_conf.toml b/docker/infinity_conf.toml index c332cf6..d195621 100644 --- a/docker/infinity_conf.toml +++ b/docker/infinity_conf.toml @@ -1,5 +1,5 @@ [general] -version = "0.6.0" +version = "0.6.1" time_zone = "utc-8" [network] diff --git a/docker/ragflow.sh b/docker/ragflow.sh deleted file mode 100644 index 364d391..0000000 --- a/docker/ragflow.sh +++ /dev/null @@ -1,63 +0,0 @@ -#!/bin/bash - -# RAGFlow 服务管理脚本 - -case "$1" in - "start") - echo "启动 RAGFlow 服务..." - ./start-ragflow.sh - ;; - "stop") - echo "停止 RAGFlow 服务..." - ./stop-ragflow.sh - ;; - "restart") - echo "重启 RAGFlow 服务..." - ./stop-ragflow.sh - sleep 5 - ./start-ragflow.sh - ;; - "status") - echo "检查服务状态..." - echo "=== RAGFlow 服务 ===" - docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" | grep -E "(ragflow-server|ragflow-postgres|ragflow-redis|ragflow-minio|ragflow-opensearch)" - ;; - "logs") - echo "查看 RAGFlow 日志..." - docker-compose -f docker-compose.yml logs -f ragflow - ;; - "init") - echo "初始化所有服务(仅首次使用)..." - docker-compose -f docker-compose-base.yml up -d - echo "等待基础服务启动..." - sleep 30 - docker-compose -f docker-compose.yml up -d ragflow - echo "所有服务启动完成!" - ;; - "clean") - echo "清理所有服务(包括数据)..." - read -p "确定要删除所有数据吗?(y/N): " -n 1 -r - echo - if [[ $REPLY =~ ^[Yy]$ ]]; then - docker-compose -f docker-compose.yml down - docker-compose -f docker-compose-base.yml down -v - docker network rm ragflow 2>/dev/null || true - echo "所有服务已清理" - else - echo "操作已取消" - fi - ;; - *) - echo "用法: $0 {start|stop|restart|status|logs|init|clean}" - echo "" - echo "命令说明:" - echo " start - 启动 RAGFlow 服务(不重新创建基础服务)" - echo " stop - 停止 RAGFlow 服务(保留基础服务)" - echo " restart - 重启 RAGFlow 服务" - echo " status - 查看服务状态" - echo " logs - 查看 RAGFlow 日志" - echo " init - 初始化所有服务(仅首次使用)" - echo " clean - 清理所有服务(包括数据)" - exit 1 - ;; -esac diff --git a/docker/service_conf.yaml.template b/docker/service_conf.yaml.template index 54dc60c..b5121d6 100644 --- a/docker/service_conf.yaml.template +++ b/docker/service_conf.yaml.template @@ -32,14 +32,14 @@ redis: db: 1 password: '${REDIS_PASSWORD:-infini_rag_flow}' host: '${REDIS_HOST:-redis}:6379' -postgres: - name: '${POSTGRES_DBNAME:-rag_flow}' - user: '${POSTGRES_USER:-rag_flow}' - password: '${POSTGRES_PASSWORD:-infini_rag_flow}' - host: '${POSTGRES_HOST:-postgres}' - port: 5432 - max_connections: 100 - stale_timeout: 30 +# postgres: +# name: '${POSTGRES_DBNAME:-rag_flow}' +# user: '${POSTGRES_USER:-rag_flow}' +# password: '${POSTGRES_PASSWORD:-infini_rag_flow}' +# host: '${POSTGRES_HOST:-postgres}' +# port: 5432 +# max_connections: 100 +# stale_timeout: 30 # s3: # access_key: 'access_key' # secret_key: 'secret_key' diff --git a/docker/start-ragflow.sh b/docker/start-ragflow.sh deleted file mode 100644 index d9ff309..0000000 --- a/docker/start-ragflow.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/bash - -# 启动脚本:只启动 ragflow 服务,不重新创建基础服务 - -echo "检查基础服务是否运行..." - -# 检查基础服务是否在运行 -if ! docker ps --format "table {{.Names}}" | grep -q "ragflow-postgres\|ragflow-redis\|ragflow-minio\|ragflow-opensearch-01"; then - echo "基础服务未运行,正在启动基础服务..." - docker-compose -p ragflow -f docker-compose-base.yml up -d - echo "等待基础服务启动完成..." - sleep 30 -else - echo "基础服务已在运行" -fi - -# 检查网络是否存在 -if ! docker network ls --format "{{.Name}}" | grep -q "ragflow-20250916_ragflow"; then - echo "ragflow 网络不存在,请先运行基础服务创建网络" - exit 1 -fi - -echo "启动 ragflow 服务..." -docker compose -p ragflow -f docker-compose.yml up -d ragflow - -echo "ragflow 服务启动完成!" -echo "访问地址: http://localhost:${SVR_HTTP_PORT:-9380}" diff --git a/docker/stop-ragflow.sh b/docker/stop-ragflow.sh deleted file mode 100644 index a22ad1c..0000000 --- a/docker/stop-ragflow.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -# 停止脚本:只停止 ragflow 服务,保留基础服务 - -echo "停止 ragflow 服务..." -docker compose -p ragflow -f docker-compose.yml down - -# 等待一段时间确保完全停止 -sleep 10 - -echo "ragflow 服务已停止" -echo "基础服务(postgres、redis、minio、opensearch)仍在运行" diff --git a/download_deps.py b/download_deps.py index d140be3..a3fa49f 100644 --- a/download_deps.py +++ b/download_deps.py @@ -16,7 +16,7 @@ import os import urllib.request import argparse -def get_urls(use_china_mirrors=False) -> Union[str, list[str]]: +def get_urls(use_china_mirrors=False) -> list[Union[str, list[str]]]: if use_china_mirrors: return [ "http://mirrors.tuna.tsinghua.edu.cn/ubuntu/pool/main/o/openssl/libssl1.1_1.1.1f-1ubuntu2_amd64.deb", diff --git a/example/http/dataset_example.sh b/example/http/dataset_example.sh new file mode 100644 index 0000000..492d902 --- /dev/null +++ b/example/http/dataset_example.sh @@ -0,0 +1,52 @@ +# +# Copyright 2024 The InfiniFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Create a dataset +echo -e "\n-- Create a dataset" +curl --request POST \ + --url http://localhost:9380/api/v1/datasets \ + --header 'Content-Type: application/json' \ + --header 'Authorization: Bearer ragflow-IzZmY1MGVhYTBhMjExZWZiYTdjMDI0Mm' \ + --data '{ + "name": "test" + }' + +# Update the dataset +echo -e "\n-- Update the dataset" +curl --request PUT \ + --url http://localhost:9380/api/v1/datasets/2e898768a0bc11efb46a0242ac120006 \ + --header 'Content-Type: application/json' \ + --header 'Authorization: Bearer ragflow-IzZmY1MGVhYTBhMjExZWZiYTdjMDI0Mm' \ + --data ' + { + "name": "updated_dataset" + }' + +# List datasets +echo -e "\n-- List datasets" +curl --request GET \ + --url http://127.0.0.1:9380/api/v1/datasets \ + --header 'Authorization: Bearer ragflow-IzZmY1MGVhYTBhMjExZWZiYTdjMDI0Mm' + +# Delete datasets +echo -e "\n-- Delete datasets" +curl --request DELETE \ + --url http://localhost:9380/api/v1/datasets \ + --header 'Content-Type: application/json' \ + --header 'Authorization: Bearer ragflow-IzZmY1MGVhYTBhMjExZWZiYTdjMDI0Mm' \ + --data '{ + "ids": ["301298b8a0bc11efa0440242ac120006"] + }' diff --git a/example/sdk/dataset_example.py b/example/sdk/dataset_example.py new file mode 100644 index 0000000..3a0504d --- /dev/null +++ b/example/sdk/dataset_example.py @@ -0,0 +1,53 @@ +# +# Copyright 2024 The InfiniFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +''' +The example is about CRUD operations (Create, Read, Update, Delete) on a dataset. +''' + +from ragflow_sdk import RAGFlow +import sys + +HOST_ADDRESS = "http://127.0.0.1" +API_KEY = "ragflow-IzZmY1MGVhYTBhMjExZWZiYTdjMDI0Mm" + +try: + # create a ragflow instance + ragflow_instance = RAGFlow(api_key=API_KEY, base_url=HOST_ADDRESS) + + # crate a dataset instance + dataset_instance = ragflow_instance.create_dataset(name="dataset_instance") + + # update the dataset instance + updated_message = {"name":"updated_dataset"} + updated_dataset = dataset_instance.update(updated_message) + + # get the dataset (list datasets) + print(dataset_instance) + print(updated_dataset) + + # delete the dataset (delete datasets) + to_be_deleted_datasets = [dataset_instance.id] + ragflow_instance.delete_datasets(ids=to_be_deleted_datasets) + + print("test done") + sys.exit(0) + +except Exception as e: + print(str(e)) + sys.exit(-1) + + diff --git a/graphrag/entity_resolution.py b/graphrag/entity_resolution.py index 01478d7..11bacd1 100644 --- a/graphrag/entity_resolution.py +++ b/graphrag/entity_resolution.py @@ -60,7 +60,7 @@ class EntityResolution(Extractor): self._llm = llm_invoker self._resolution_prompt = ENTITY_RESOLUTION_PROMPT self._record_delimiter_key = "record_delimiter" - self._entity_index_dilimiter_key = "entity_index_delimiter" + self._entity_index_delimiter_key = "entity_index_delimiter" self._resolution_result_delimiter_key = "resolution_result_delimiter" self._input_text_key = "input_text" @@ -77,7 +77,7 @@ class EntityResolution(Extractor): **prompt_variables, self._record_delimiter_key: prompt_variables.get(self._record_delimiter_key) or DEFAULT_RECORD_DELIMITER, - self._entity_index_dilimiter_key: prompt_variables.get(self._entity_index_dilimiter_key) + self._entity_index_delimiter_key: prompt_variables.get(self._entity_index_delimiter_key) or DEFAULT_ENTITY_INDEX_DELIMITER, self._resolution_result_delimiter_key: prompt_variables.get(self._resolution_result_delimiter_key) or DEFAULT_RESOLUTION_RESULT_DELIMITER, @@ -185,7 +185,7 @@ class EntityResolution(Extractor): result = self._process_results(len(candidate_resolution_i[1]), response, self.prompt_variables.get(self._record_delimiter_key, DEFAULT_RECORD_DELIMITER), - self.prompt_variables.get(self._entity_index_dilimiter_key, + self.prompt_variables.get(self._entity_index_delimiter_key, DEFAULT_ENTITY_INDEX_DELIMITER), self.prompt_variables.get(self._resolution_result_delimiter_key, DEFAULT_RESOLUTION_RESULT_DELIMITER)) diff --git a/graphrag/general/extractor.py b/graphrag/general/extractor.py index df8af1c..28b591d 100644 --- a/graphrag/general/extractor.py +++ b/graphrag/general/extractor.py @@ -105,16 +105,36 @@ class Extractor: async def extract_all(doc_id, chunks, max_concurrency=MAX_CONCURRENT_PROCESS_AND_EXTRACT_CHUNK): out_results = [] + error_count = 0 + max_errors = 3 + limiter = trio.Semaphore(max_concurrency) async def worker(chunk_key_dp: tuple[str, str], idx: int, total: int): + nonlocal error_count async with limiter: - await self._process_single_content(chunk_key_dp, idx, total, out_results) + try: + await self._process_single_content(chunk_key_dp, idx, total, out_results) + except Exception as e: + error_count += 1 + error_msg = f"Error processing chunk {idx+1}/{total}: {str(e)}" + logging.warning(error_msg) + if self.callback: + self.callback(msg=error_msg) + + if error_count > max_errors: + raise Exception(f"Maximum error count ({max_errors}) reached. Last errors: {str(e)}") async with trio.open_nursery() as nursery: for i, ck in enumerate(chunks): nursery.start_soon(worker, (doc_id, ck), i, len(chunks)) + if error_count > 0: + warning_msg = f"Completed with {error_count} errors (out of {len(chunks)} chunks processed)" + logging.warning(warning_msg) + if self.callback: + self.callback(msg=warning_msg) + return out_results out_results = await extract_all(doc_id, chunks, max_concurrency=MAX_CONCURRENT_PROCESS_AND_EXTRACT_CHUNK) @@ -129,8 +149,8 @@ class Extractor: maybe_edges[tuple(sorted(k))].extend(v) sum_token_count += token_count now = trio.current_time() - if callback: - callback(msg=f"Entities and relationships extraction done, {len(maybe_nodes)} nodes, {len(maybe_edges)} edges, {sum_token_count} tokens, {now - start_ts:.2f}s.") + if self.callback: + self.callback(msg=f"Entities and relationships extraction done, {len(maybe_nodes)} nodes, {len(maybe_edges)} edges, {sum_token_count} tokens, {now - start_ts:.2f}s.") start_ts = now logging.info("Entities merging...") all_entities_data = [] @@ -138,8 +158,8 @@ class Extractor: for en_nm, ents in maybe_nodes.items(): nursery.start_soon(self._merge_nodes, en_nm, ents, all_entities_data) now = trio.current_time() - if callback: - callback(msg=f"Entities merging done, {now - start_ts:.2f}s.") + if self.callback: + self.callback(msg=f"Entities merging done, {now - start_ts:.2f}s.") start_ts = now logging.info("Relationships merging...") @@ -148,8 +168,8 @@ class Extractor: for (src, tgt), rels in maybe_edges.items(): nursery.start_soon(self._merge_edges, src, tgt, rels, all_relationships_data) now = trio.current_time() - if callback: - callback(msg=f"Relationships merging done, {now - start_ts:.2f}s.") + if self.callback: + self.callback(msg=f"Relationships merging done, {now - start_ts:.2f}s.") if not len(all_entities_data) and not len(all_relationships_data): logging.warning("Didn't extract any entities and relationships, maybe your LLM is not working") @@ -227,7 +247,7 @@ class Extractor: async def _handle_entity_relation_summary(self, entity_or_relation_name: str, description: str) -> str: summary_max_tokens = 512 use_description = truncate(description, summary_max_tokens) - description_list = (use_description.split(GRAPH_FIELD_SEP),) + description_list = use_description.split(GRAPH_FIELD_SEP) if len(description_list) <= 12: return use_description prompt_template = SUMMARIZE_DESCRIPTIONS_PROMPT diff --git a/graphrag/general/index.py b/graphrag/general/index.py index 6d0df65..7cb47de 100644 --- a/graphrag/general/index.py +++ b/graphrag/general/index.py @@ -55,7 +55,7 @@ async def run_graphrag( start = trio.current_time() tenant_id, kb_id, doc_id = row["tenant_id"], str(row["kb_id"]), row["doc_id"] chunks = [] - for d in settings.retrievaler.chunk_list(doc_id, tenant_id, [kb_id], fields=["content_with_weight", "doc_id"], sort_by_position=True): + for d in settings.retriever.chunk_list(doc_id, tenant_id, [kb_id], fields=["content_with_weight", "doc_id"], sort_by_position=True): chunks.append(d["content_with_weight"]) with trio.fail_after(max(120, len(chunks) * 60 * 10) if enable_timeout_assertion else 10000000000): @@ -170,7 +170,7 @@ async def run_graphrag_for_kb( chunks = [] current_chunk = "" - for d in settings.retrievaler.chunk_list( + for d in settings.retriever.chunk_list( doc_id, tenant_id, [kb_id], diff --git a/graphrag/general/smoke.py b/graphrag/general/smoke.py index 3f282fb..5f9fe14 100644 --- a/graphrag/general/smoke.py +++ b/graphrag/general/smoke.py @@ -62,7 +62,7 @@ async def main(): chunks = [ d["content_with_weight"] - for d in settings.retrievaler.chunk_list( + for d in settings.retriever.chunk_list( args.doc_id, args.tenant_id, [kb_id], diff --git a/graphrag/light/smoke.py b/graphrag/light/smoke.py index 504f09c..f8f505f 100644 --- a/graphrag/light/smoke.py +++ b/graphrag/light/smoke.py @@ -63,7 +63,7 @@ async def main(): chunks = [ d["content_with_weight"] - for d in settings.retrievaler.chunk_list( + for d in settings.retriever.chunk_list( args.doc_id, args.tenant_id, [kb_id], diff --git a/graphrag/utils.py b/graphrag/utils.py index 6abe5f9..877380d 100644 --- a/graphrag/utils.py +++ b/graphrag/utils.py @@ -92,10 +92,7 @@ def dict_has_keys_with_types(data: dict, expected_fields: list[tuple[str, type]] def get_llm_cache(llmnm, txt, history, genconf): hasher = xxhash.xxh64() - hasher.update(str(llmnm).encode("utf-8")) - hasher.update(str(txt).encode("utf-8")) - hasher.update(str(history).encode("utf-8")) - hasher.update(str(genconf).encode("utf-8")) + hasher.update((str(llmnm)+str(txt)+str(history)+str(genconf)).encode("utf-8")) k = hasher.hexdigest() bin = REDIS_CONN.get(k) @@ -106,11 +103,7 @@ def get_llm_cache(llmnm, txt, history, genconf): def set_llm_cache(llmnm, txt, v, history, genconf): hasher = xxhash.xxh64() - hasher.update(str(llmnm).encode("utf-8")) - hasher.update(str(txt).encode("utf-8")) - hasher.update(str(history).encode("utf-8")) - hasher.update(str(genconf).encode("utf-8")) - + hasher.update((str(llmnm)+str(txt)+str(history)+str(genconf)).encode("utf-8")) k = hasher.hexdigest() REDIS_CONN.set(k, v.encode("utf-8"), 24 * 3600) @@ -341,7 +334,7 @@ def get_relation(tenant_id, kb_id, from_ent_name, to_ent_name, size=1): ents = list(set(ents)) conds = {"fields": ["content_with_weight"], "size": size, "from_entity_kwd": ents, "to_entity_kwd": ents, "knowledge_graph_kwd": ["relation"]} res = [] - es_res = settings.retrievaler.search(conds, search.index_name(tenant_id), [kb_id] if isinstance(kb_id, str) else kb_id) + es_res = settings.retriever.search(conds, search.index_name(tenant_id), [kb_id] if isinstance(kb_id, str) else kb_id) for id in es_res.ids: try: if size == 1: @@ -398,7 +391,7 @@ async def does_graph_contains(tenant_id, kb_id, doc_id): async def get_graph_doc_ids(tenant_id, kb_id) -> list[str]: conds = {"fields": ["source_id"], "removed_kwd": "N", "size": 1, "knowledge_graph_kwd": ["graph"]} - res = await trio.to_thread.run_sync(lambda: settings.retrievaler.search(conds, search.index_name(tenant_id), [kb_id])) + res = await trio.to_thread.run_sync(lambda: settings.retriever.search(conds, search.index_name(tenant_id), [kb_id])) doc_ids = [] if res.total == 0: return doc_ids @@ -409,7 +402,7 @@ async def get_graph_doc_ids(tenant_id, kb_id) -> list[str]: async def get_graph(tenant_id, kb_id, exclude_rebuild=None): conds = {"fields": ["content_with_weight", "removed_kwd", "source_id"], "size": 1, "knowledge_graph_kwd": ["graph"]} - res = await trio.to_thread.run_sync(settings.retrievaler.search, conds, search.index_name(tenant_id), [kb_id]) + res = await trio.to_thread.run_sync(settings.retriever.search, conds, search.index_name(tenant_id), [kb_id]) if not res.total == 0: for id in res.ids: try: @@ -562,7 +555,7 @@ def merge_tuples(list1, list2): async def get_entity_type2samples(idxnms, kb_ids: list): - es_res = await trio.to_thread.run_sync(lambda: settings.retrievaler.search({"knowledge_graph_kwd": "ty2ents", "kb_id": kb_ids, "size": 10000, "fields": ["content_with_weight"]}, idxnms, kb_ids)) + es_res = await trio.to_thread.run_sync(lambda: settings.retriever.search({"knowledge_graph_kwd": "ty2ents", "kb_id": kb_ids, "size": 10000, "fields": ["content_with_weight"]}, idxnms, kb_ids)) res = defaultdict(list) for id in es_res.ids: diff --git a/helm/.helmignore b/helm/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/helm/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/helm/Chart.yaml b/helm/Chart.yaml new file mode 100644 index 0000000..4dd5af1 --- /dev/null +++ b/helm/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: ragflow +description: A Helm chart for deploying RAGFlow on Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "dev" diff --git a/helm/templates/_helpers.tpl b/helm/templates/_helpers.tpl new file mode 100644 index 0000000..8afe440 --- /dev/null +++ b/helm/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "ragflow.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "ragflow.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "ragflow.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "ragflow.labels" -}} +helm.sh/chart: {{ include "ragflow.chart" . }} +{{ include "ragflow.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "ragflow.selectorLabels" -}} +app.kubernetes.io/name: {{ include "ragflow.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "ragflow.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "ragflow.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/helm/templates/elasticsearch-config.yaml b/helm/templates/elasticsearch-config.yaml new file mode 100644 index 0000000..e2c03fc --- /dev/null +++ b/helm/templates/elasticsearch-config.yaml @@ -0,0 +1,17 @@ +{{- if eq .Values.env.DOC_ENGINE "elasticsearch" -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "ragflow.fullname" . }}-es-config +data: + node.name: "es01" + bootstrap.memory_lock: "false" + discovery.type: "single-node" + xpack.security.enabled: "true" + xpack.security.http.ssl.enabled: "false" + xpack.security.transport.ssl.enabled: "false" + cluster.routing.allocation.disk.watermark.low: 5gb + cluster.routing.allocation.disk.watermark.high: 3gb + cluster.routing.allocation.disk.watermark.flood_stage: 2gb + TZ: {{ .Values.env.TIMEZONE }} +{{- end -}} diff --git a/helm/templates/elasticsearch.yaml b/helm/templates/elasticsearch.yaml new file mode 100644 index 0000000..47dd6bb --- /dev/null +++ b/helm/templates/elasticsearch.yaml @@ -0,0 +1,131 @@ +{{- if eq .Values.env.DOC_ENGINE "elasticsearch" -}} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "ragflow.fullname" . }}-es-data + annotations: + "helm.sh/resource-policy": keep + labels: + {{- include "ragflow.labels" . | nindent 4 }} + app.kubernetes.io/component: elasticsearch +spec: + {{- with .Values.elasticsearch.storage.className }} + storageClassName: {{ . }} + {{- end }} + accessModes: + - ReadWriteOnce + resources: + requests: + storage: {{ .Values.elasticsearch.storage.capacity }} +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "ragflow.fullname" . }}-es + labels: + {{- include "ragflow.labels" . | nindent 4 }} + app.kubernetes.io/component: elasticsearch +spec: + replicas: 1 + selector: + matchLabels: + {{- include "ragflow.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: elasticsearch + {{- with .Values.elasticsearch.deployment.strategy }} + strategy: + {{- . | toYaml | nindent 4 }} + {{- end }} + template: + metadata: + labels: + {{- include "ragflow.labels" . | nindent 8 }} + app.kubernetes.io/component: elasticsearch + annotations: + checksum/config-es: {{ include (print $.Template.BasePath "/elasticsearch-config.yaml") . | sha256sum }} + checksum/config-env: {{ include (print $.Template.BasePath "/env.yaml") . | sha256sum }} + spec: + {{- if or .Values.imagePullSecrets .Values.elasticsearch.image.pullSecrets }} + imagePullSecrets: + {{- with .Values.imagePullSecrets }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.elasticsearch.image.pullSecrets }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- end }} + initContainers: + - name: fix-data-volume-permissions + image: {{ .Values.elasticsearch.initContainers.alpine.repository }}:{{ .Values.elasticsearch.initContainers.alpine.tag }} + {{- with .Values.elasticsearch.initContainers.alpine.pullPolicy }} + imagePullPolicy: {{ . }} + {{- end }} + command: + - sh + - -c + - "chown -R 1000:0 /usr/share/elasticsearch/data" + volumeMounts: + - mountPath: /usr/share/elasticsearch/data + name: es-data + - name: sysctl + image: {{ .Values.elasticsearch.initContainers.busybox.repository }}:{{ .Values.elasticsearch.initContainers.busybox.tag }} + {{- with .Values.elasticsearch.initContainers.busybox.pullPolicy }} + imagePullPolicy: {{ . }} + {{- end }} + securityContext: + privileged: true + runAsUser: 0 + command: ["sysctl", "-w", "vm.max_map_count=262144"] + containers: + - name: elasticsearch + image: {{ .Values.elasticsearch.image.repository }}:{{ .Values.elasticsearch.image.tag }} + {{- with .Values.elasticsearch.image.pullPolicy }} + imagePullPolicy: {{ . }} + {{- end }} + envFrom: + - secretRef: + name: {{ include "ragflow.fullname" . }}-env-config + - configMapRef: + name: {{ include "ragflow.fullname" . }}-es-config + ports: + - containerPort: 9200 + name: http + - containerPort: 9300 + name: transport + volumeMounts: + - mountPath: /usr/share/elasticsearch/data + name: es-data + {{- with .Values.elasticsearch.deployment.resources }} + resources: + {{- . | toYaml | nindent 10 }} + {{- end }} + securityContext: + capabilities: + add: + - "IPC_LOCK" + runAsUser: 1000 + # NOTE: fsGroup doesn't seem to + # work so use init container instead + # fsGroup: 1000 + allowPrivilegeEscalation: false + volumes: + - name: es-data + persistentVolumeClaim: + claimName: {{ include "ragflow.fullname" . }}-es-data +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "ragflow.fullname" . }}-es + labels: + {{- include "ragflow.labels" . | nindent 4 }} + app.kubernetes.io/component: elasticsearch +spec: + selector: + {{- include "ragflow.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: elasticsearch + ports: + - protocol: TCP + port: 9200 + targetPort: http + type: {{ .Values.elasticsearch.service.type }} +{{- end -}} diff --git a/helm/templates/env.yaml b/helm/templates/env.yaml new file mode 100644 index 0000000..bcee9fd --- /dev/null +++ b/helm/templates/env.yaml @@ -0,0 +1,53 @@ +{{- /* +TODO: Split env vars into separate secrets so that each pod + only gets passed the secrets it really needs. +*/}} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "ragflow.fullname" . }}-env-config +type: Opaque +stringData: + {{- range $key, $val := .Values.env }} + {{- if $val }} + {{ $key }}: {{ quote $val }} + {{- end }} + {{- end }} + {{- /* + Use host names derived from internal cluster DNS + */}} + REDIS_HOST: {{ printf "%s-redis.%s.svc" (include "ragflow.fullname" .) .Release.Namespace }} + MYSQL_HOST: {{ printf "%s-mysql.%s.svc" (include "ragflow.fullname" .) .Release.Namespace }} + MINIO_HOST: {{ printf "%s-minio.%s.svc" (include "ragflow.fullname" .) .Release.Namespace }} + {{- /* + Fail if passwords are not provided in release values + */}} + REDIS_PASSWORD: {{ .Values.env.REDIS_PASSWORD | required "REDIS_PASSWORD is required" }} + {{- /* + NOTE: MySQL uses MYSQL_ROOT_PASSWORD env var but Ragflow container expects + MYSQL_PASSWORD so we need to define both as the same value here. + */}} + {{- with .Values.env.MYSQL_PASSWORD | required "MYSQL_PASSWORD is required" }} + MYSQL_PASSWORD: {{ . }} + MYSQL_ROOT_PASSWORD: {{ . }} + {{- end }} + {{- with .Values.env.MINIO_PASSWORD | required "MINIO_PASSWORD is required" }} + MINIO_PASSWORD: {{ . }} + MINIO_ROOT_PASSWORD: {{ . }} + {{- end }} + {{- /* + Only provide env vars for enabled doc engine + */}} + {{- if eq .Values.env.DOC_ENGINE "elasticsearch" }} + ES_HOST: {{ printf "%s-es.%s.svc" (include "ragflow.fullname" .) .Release.Namespace }} + ELASTIC_PASSWORD: {{ .Values.env.ELASTIC_PASSWORD | required "ELASTIC_PASSWORD is required" }} + {{- else if eq .Values.env.DOC_ENGINE "infinity" }} + INFINITY_HOST: {{ printf "%s-infinity.%s.svc" (include "ragflow.fullname" .) .Release.Namespace }} + {{- else if eq .Values.env.DOC_ENGINE "opensearch" }} + OS_HOST: {{ printf "%s-opensearch.%s.svc" (include "ragflow.fullname" .) .Release.Namespace }} + OS_PORT: "9201" + OPENSEARCH_PASSWORD: {{ .Values.env.OPENSEARCH_PASSWORD | required "OPENSEARCH_PASSWORD is required" }} + OPENSEARCH_INITIAL_ADMIN_PASSWORD: {{ .Values.env.OPENSEARCH_PASSWORD | required "OPENSEARCH_PASSWORD is required" }} + {{- else }} + {{ fail "env.DOC_ENGINE must be either 'elasticsearch', 'opensearch' or 'infinity'" }} + {{- end }} diff --git a/helm/templates/infinity.yaml b/helm/templates/infinity.yaml new file mode 100644 index 0000000..db0d913 --- /dev/null +++ b/helm/templates/infinity.yaml @@ -0,0 +1,122 @@ +{{- if eq .Values.env.DOC_ENGINE "infinity" -}} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "ragflow.fullname" . }}-infinity + annotations: + "helm.sh/resource-policy": keep + labels: + {{- include "ragflow.labels" . | nindent 4 }} + app.kubernetes.io/component: infinity +spec: + {{- with .Values.infinity.storage.className }} + storageClassName: {{ . }} + {{- end }} + accessModes: + - ReadWriteOnce + resources: + requests: + storage: {{ .Values.infinity.storage.capacity }} +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "ragflow.fullname" . }}-infinity + labels: + {{- include "ragflow.labels" . | nindent 4 }} + app.kubernetes.io/component: infinity +spec: + replicas: 1 + selector: + matchLabels: + {{- include "ragflow.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: infinity + {{- with .Values.infinity.deployment.strategy }} + strategy: + {{- . | toYaml | nindent 4 }} + {{- end }} + template: + metadata: + labels: + {{- include "ragflow.labels" . | nindent 8 }} + app.kubernetes.io/component: infinity + annotations: + checksum/config: {{ include (print $.Template.BasePath "/env.yaml") . | sha256sum }} + spec: + {{- if or .Values.imagePullSecrets .Values.infinity.image.pullSecrets }} + imagePullSecrets: + {{- with .Values.imagePullSecrets }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.infinity.image.pullSecrets }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- end }} + containers: + - name: infinity + image: {{ .Values.infinity.image.repository }}:{{ .Values.infinity.image.tag }} + {{- with .Values.infinity.image.pullPolicy }} + imagePullPolicy: {{ . }} + {{- end }} + envFrom: + - secretRef: + name: {{ include "ragflow.fullname" . }}-env-config + ports: + - containerPort: 23817 + name: thrift + - containerPort: 23820 + name: http + - containerPort: 5432 + name: psql + volumeMounts: + - mountPath: /var/infinity + name: infinity-data + {{- with .Values.infinity.deployment.resources }} + resources: + {{- . | toYaml | nindent 10 }} + {{- end }} + securityContext: + capabilities: + add: + - "NET_BIND_SERVICE" + seccompProfile: + type: RuntimeDefault + livenessProbe: + httpGet: + path: /admin/node/current + port: 23820 + initialDelaySeconds: 60 + periodSeconds: 10 + timeoutSeconds: 10 + failureThreshold: 120 + volumes: + - name: infinity-data + persistentVolumeClaim: + claimName: {{ include "ragflow.fullname" . }}-infinity +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "ragflow.fullname" . }}-infinity + labels: + {{- include "ragflow.labels" . | nindent 4 }} + app.kubernetes.io/component: infinity +spec: + selector: + {{- include "ragflow.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: infinity + ports: + - protocol: TCP + port: 23817 + targetPort: thrift + name: thrift + - protocol: TCP + port: 23820 + targetPort: http + name: http + - protocol: TCP + port: 5432 + targetPort: psql + name: psql + type: {{ .Values.infinity.service.type }} +{{- end -}} diff --git a/helm/templates/ingress.yaml b/helm/templates/ingress.yaml new file mode 100644 index 0000000..6547b52 --- /dev/null +++ b/helm/templates/ingress.yaml @@ -0,0 +1,43 @@ +{{- if .Values.ingress.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "ragflow.fullname" . }} + labels: + {{- include "ragflow.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- with .Values.ingress.className }} + ingressClassName: {{ . }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- with .pathType }} + pathType: {{ . }} + {{- end }} + backend: + service: + name: {{ $.Release.Name }} + port: + name: http + {{- end }} + {{- end }} +{{- end }} diff --git a/helm/templates/minio.yaml b/helm/templates/minio.yaml new file mode 100644 index 0000000..ff8d0ca --- /dev/null +++ b/helm/templates/minio.yaml @@ -0,0 +1,105 @@ +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "ragflow.fullname" . }}-minio + annotations: + "helm.sh/resource-policy": keep + labels: + {{- include "ragflow.labels" . | nindent 4 }} + app.kubernetes.io/component: minio +spec: + {{- with .Values.minio.storage.className }} + storageClassName: {{ . }} + {{- end }} + accessModes: + - ReadWriteOnce + resources: + requests: + storage: {{ .Values.minio.storage.capacity }} +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "ragflow.fullname" . }}-minio + labels: + {{- include "ragflow.labels" . | nindent 4 }} + app.kubernetes.io/component: minio + annotations: + checksum/config: {{ include (print $.Template.BasePath "/env.yaml") . | sha256sum }} +spec: + replicas: 1 + selector: + matchLabels: + {{- include "ragflow.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: minio + {{- with .Values.minio.deployment.strategy }} + strategy: + {{- . | toYaml | nindent 4 }} + {{- end }} + template: + metadata: + labels: + {{- include "ragflow.labels" . | nindent 8 }} + app.kubernetes.io/component: minio + spec: + {{- if or .Values.imagePullSecrets .Values.minio.image.pullSecrets }} + imagePullSecrets: + {{- with .Values.imagePullSecrets }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.minio.image.pullSecrets }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- end }} + containers: + - name: minio + image: {{ .Values.minio.image.repository }}:{{ .Values.minio.image.tag }} + {{- with .Values.minio.image.pullPolicy }} + imagePullPolicy: {{ . }} + {{- end }} + envFrom: + - secretRef: + name: {{ include "ragflow.fullname" . }}-env-config + args: + - server + - "--console-address=:9001" + - "/data" + ports: + - containerPort: 9000 + name: s3 + - containerPort: 9001 + name: console + {{- with .Values.minio.deployment.resources }} + resources: + {{- . | toYaml | nindent 10 }} + {{- end }} + volumeMounts: + - mountPath: /data + name: minio-data + volumes: + - name: minio-data + persistentVolumeClaim: + claimName: {{ include "ragflow.fullname" . }}-minio +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "ragflow.fullname" . }}-minio + labels: + {{- include "ragflow.labels" . | nindent 4 }} + app.kubernetes.io/component: minio +spec: + selector: + {{- include "ragflow.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: minio + ports: + - name: s3 + protocol: TCP + port: 9000 + targetPort: s3 + - name: console + protocol: TCP + port: 9001 + targetPort: console + type: {{ .Values.minio.service.type }} diff --git a/helm/templates/mysql-config.yaml b/helm/templates/mysql-config.yaml new file mode 100644 index 0000000..dd85010 --- /dev/null +++ b/helm/templates/mysql-config.yaml @@ -0,0 +1,9 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: mysql-init-script +data: + init.sql: |- + CREATE DATABASE IF NOT EXISTS rag_flow; + USE rag_flow; diff --git a/helm/templates/mysql.yaml b/helm/templates/mysql.yaml new file mode 100644 index 0000000..a5998d8 --- /dev/null +++ b/helm/templates/mysql.yaml @@ -0,0 +1,110 @@ +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "ragflow.fullname" . }}-mysql + annotations: + "helm.sh/resource-policy": keep + labels: + {{- include "ragflow.labels" . | nindent 4 }} + app.kubernetes.io/component: mysql +spec: + {{- with .Values.mysql.storage.className }} + storageClassName: {{ . }} + {{- end }} + accessModes: + - ReadWriteOnce + resources: + requests: + storage: {{ .Values.mysql.storage.capacity }} +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "ragflow.fullname" . }}-mysql + labels: + {{- include "ragflow.labels" . | nindent 4 }} + app.kubernetes.io/component: mysql +spec: + replicas: 1 + selector: + matchLabels: + {{- include "ragflow.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: mysql + {{- with .Values.mysql.deployment.strategy }} + strategy: + {{- . | toYaml | nindent 4 }} + {{- end }} + template: + metadata: + labels: + {{- include "ragflow.labels" . | nindent 8 }} + app.kubernetes.io/component: mysql + annotations: + checksum/config-mysql: {{ include (print $.Template.BasePath "/mysql-config.yaml") . | sha256sum }} + checksum/config-env: {{ include (print $.Template.BasePath "/env.yaml") . | sha256sum }} + spec: + {{- if or .Values.imagePullSecrets .Values.mysql.image.pullSecrets }} + imagePullSecrets: + {{- with .Values.imagePullSecrets }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.mysql.image.pullSecrets }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- end }} + containers: + - name: mysql + image: {{ .Values.mysql.image.repository }}:{{ .Values.mysql.image.tag }} + {{- with .Values.mysql.image.pullPolicy }} + imagePullPolicy: {{ . }} + {{- end }} + envFrom: + - secretRef: + name: {{ include "ragflow.fullname" . }}-env-config + args: + - --max_connections=1000 + - --character-set-server=utf8mb4 + - --collation-server=utf8mb4_general_ci + - --default-authentication-plugin=mysql_native_password + - --tls_version=TLSv1.2,TLSv1.3 + - --init-file=/data/application/init.sql + - --disable-log-bin + ports: + - containerPort: 3306 + name: mysql + {{- with .Values.mysql.deployment.resources }} + resources: + {{- . | toYaml | nindent 10 }} + {{- end }} + volumeMounts: + - mountPath: /var/lib/mysql + name: mysql-data + - mountPath: /data/application/init.sql + subPath: init.sql + readOnly: true + name: init-script-volume + volumes: + - name: mysql-data + persistentVolumeClaim: + claimName: {{ include "ragflow.fullname" . }}-mysql + - name: init-script-volume + configMap: + name: mysql-init-script +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "ragflow.fullname" . }}-mysql + labels: + {{- include "ragflow.labels" . | nindent 4 }} + app.kubernetes.io/component: mysql +spec: + selector: + {{- include "ragflow.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: mysql + ports: + - protocol: TCP + port: 3306 + targetPort: mysql + type: {{ .Values.mysql.service.type }} diff --git a/helm/templates/opensearch-config.yaml b/helm/templates/opensearch-config.yaml new file mode 100644 index 0000000..64147af --- /dev/null +++ b/helm/templates/opensearch-config.yaml @@ -0,0 +1,18 @@ +{{- if eq .Values.env.DOC_ENGINE "opensearch" -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "ragflow.fullname" . }}-opensearch-config +data: + node.name: opensearch01 + bootstrap.memory_lock: "false" + discovery.type: single-node + plugins.security.disabled: "false" + plugins.security.ssl.http.enabled: "false" + plugins.security.ssl.transport.enabled: "true" + cluster.routing.allocation.disk.watermark.low: 5gb + cluster.routing.allocation.disk.watermark.high: 3gb + cluster.routing.allocation.disk.watermark.flood_stage: 2gb + TZ: {{ .Values.env.TIMEZONE }} + http.port: "9201" +{{- end -}} diff --git a/helm/templates/opensearch.yaml b/helm/templates/opensearch.yaml new file mode 100644 index 0000000..062d7d7 --- /dev/null +++ b/helm/templates/opensearch.yaml @@ -0,0 +1,135 @@ +{{- if eq .Values.env.DOC_ENGINE "opensearch" -}} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "ragflow.fullname" . }}-opensearch-data + annotations: + "helm.sh/resource-policy": keep + labels: + {{- include "ragflow.labels" . | nindent 4 }} + app.kubernetes.io/component: opensearch +spec: + {{- with .Values.opensearch.storage.className }} + storageClassName: {{ . }} + {{- end }} + accessModes: + - ReadWriteOnce + resources: + requests: + storage: {{ .Values.opensearch.storage.capacity }} +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "ragflow.fullname" . }}-opensearch + labels: + {{- include "ragflow.labels" . | nindent 4 }} + app.kubernetes.io/component: opensearch +spec: + replicas: 1 + selector: + matchLabels: + {{- include "ragflow.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: opensearch + {{- with .Values.opensearch.deployment.strategy }} + strategy: + {{- . | toYaml | nindent 4 }} + {{- end }} + template: + metadata: + labels: + {{- include "ragflow.labels" . | nindent 8 }} + app.kubernetes.io/component: opensearch + annotations: + checksum/config-opensearch: {{ include (print $.Template.BasePath "/opensearch-config.yaml") . | sha256sum }} + checksum/config-env: {{ include (print $.Template.BasePath "/env.yaml") . | sha256sum }} + spec: + {{- if or .Values.imagePullSecrets .Values.opensearch.image.pullSecrets }} + imagePullSecrets: + {{- with .Values.imagePullSecrets }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.opensearch.image.pullSecrets }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- end }} + initContainers: + - name: fix-data-volume-permissions + image: {{ .Values.opensearch.initContainers.alpine.repository }}:{{ .Values.opensearch.initContainers.alpine.tag }} + {{- with .Values.opensearch.initContainers.alpine.pullPolicy }} + imagePullPolicy: {{ . }} + {{- end }} + command: + - sh + - -c + - "chown -R 1000:0 /usr/share/opensearch/data" + volumeMounts: + - mountPath: /usr/share/opensearch/data + name: opensearch-data + - name: sysctl + image: {{ .Values.opensearch.initContainers.busybox.repository }}:{{ .Values.opensearch.initContainers.busybox.tag }} + {{- with .Values.opensearch.initContainers.busybox.pullPolicy }} + imagePullPolicy: {{ . }} + {{- end }} + securityContext: + privileged: true + runAsUser: 0 + command: ["sysctl", "-w", "vm.max_map_count=262144"] + containers: + - name: opensearch + image: {{ .Values.opensearch.image.repository }}:{{ .Values.opensearch.image.tag }} + {{- with .Values.opensearch.image.pullPolicy }} + imagePullPolicy: {{ . }} + {{- end }} + envFrom: + - secretRef: + name: {{ include "ragflow.fullname" . }}-env-config + - configMapRef: + name: {{ include "ragflow.fullname" . }}-opensearch-config + ports: + - containerPort: 9201 + name: http + volumeMounts: + - mountPath: /usr/share/opensearch/data + name: opensearch-data + {{- with .Values.opensearch.deployment.resources }} + resources: + {{- . | toYaml | nindent 10 }} + {{- end }} + securityContext: + capabilities: + add: + - "IPC_LOCK" + runAsUser: 1000 + allowPrivilegeEscalation: false + livenessProbe: + exec: + command: + - sh + - -c + - curl -u admin:$OPENSEARCH_PASSWORD localhost:9201 + initialDelaySeconds: 30 + periodSeconds: 10 + failureThreshold: 6 + volumes: + - name: opensearch-data + persistentVolumeClaim: + claimName: {{ include "ragflow.fullname" . }}-opensearch-data +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "ragflow.fullname" . }}-opensearch + labels: + {{- include "ragflow.labels" . | nindent 4 }} + app.kubernetes.io/component: opensearch +spec: + selector: + {{- include "ragflow.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: opensearch + ports: + - protocol: TCP + port: 9201 + targetPort: http + type: {{ .Values.opensearch.service.type }} +{{- end -}} diff --git a/helm/templates/ragflow.yaml b/helm/templates/ragflow.yaml new file mode 100644 index 0000000..efa0ced --- /dev/null +++ b/helm/templates/ragflow.yaml @@ -0,0 +1,119 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "ragflow.fullname" . }} + labels: + {{- include "ragflow.labels" . | nindent 4 }} + app.kubernetes.io/component: ragflow +spec: + replicas: 1 + selector: + matchLabels: + {{- include "ragflow.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: ragflow + {{- with .Values.ragflow.deployment.strategy }} + strategy: + {{- . | toYaml | nindent 4 }} + {{- end }} + template: + metadata: + labels: + {{- include "ragflow.labels" . | nindent 8 }} + app.kubernetes.io/component: ragflow + annotations: + checksum/config-env: {{ include (print $.Template.BasePath "/env.yaml") . | sha256sum }} + checksum/config-ragflow: {{ include (print $.Template.BasePath "/ragflow_config.yaml") . | sha256sum }} + spec: + {{- if or .Values.imagePullSecrets .Values.ragflow.image.pullSecrets }} + imagePullSecrets: + {{- with .Values.imagePullSecrets }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.ragflow.image.pullSecrets }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- end }} + containers: + - name: ragflow + image: {{ .Values.ragflow.image.repository }}:{{ .Values.ragflow.image.tag }} + {{- with .Values.ragflow.image.pullPolicy }} + imagePullPolicy: {{ . }} + {{- end }} + ports: + - containerPort: 80 + name: http + - containerPort: 9380 + name: http-api + volumeMounts: + - mountPath: /etc/nginx/conf.d/ragflow.conf + subPath: ragflow.conf + name: nginx-config-volume + - mountPath: /etc/nginx/proxy.conf + subPath: proxy.conf + name: nginx-config-volume + - mountPath: /etc/nginx/nginx.conf + subPath: nginx.conf + name: nginx-config-volume + {{- with .Values.ragflow.service_conf }} + - mountPath: /ragflow/conf/local.service_conf.yaml + subPath: local.service_conf.yaml + name: service-conf-volume + {{- end }} + {{- with .Values.ragflow.llm_factories }} + - mountPath: /ragflow/conf/llm_factories.json + subPath: llm_factories.json + name: service-conf-volume + {{- end }} + envFrom: + - secretRef: + name: {{ include "ragflow.fullname" . }}-env-config + {{- with .Values.ragflow.deployment.resources }} + resources: + {{- . | toYaml | nindent 10 }} + {{- end }} + volumes: + - name: nginx-config-volume + configMap: + name: nginx-config + - name: service-conf-volume + configMap: + name: ragflow-service-config +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "ragflow.fullname" . }} + labels: + {{- include "ragflow.labels" . | nindent 4 }} + app.kubernetes.io/component: ragflow +spec: + selector: + {{- include "ragflow.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: ragflow + ports: + - protocol: TCP + port: 80 + targetPort: http + name: http + type: {{ .Values.ragflow.service.type }} +--- +{{- if .Values.ragflow.api.service.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-api + labels: + {{- include "ragflow.labels" . | nindent 4 }} + app.kubernetes.io/component: ragflow +spec: + selector: + {{- include "ragflow.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: ragflow + ports: + - protocol: TCP + port: 80 + targetPort: http-api + name: http-api + type: {{ .Values.ragflow.api.service.type }} +{{- end }} diff --git a/helm/templates/ragflow_config.yaml b/helm/templates/ragflow_config.yaml new file mode 100644 index 0000000..533bd3d --- /dev/null +++ b/helm/templates/ragflow_config.yaml @@ -0,0 +1,89 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: ragflow-service-config +data: + {{- with .Values.ragflow.service_conf }} + local.service_conf.yaml: | + {{- . | toYaml | nindent 4 }} + {{- end }} + {{- with .Values.ragflow.llm_factories }} + llm_factories.json: | + {{- . | toPrettyJson | nindent 4 }} + {{- end }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: nginx-config +data: + ragflow.conf: | + server { + listen 80; + server_name _; + root /ragflow/web/dist; + + gzip on; + gzip_min_length 1k; + gzip_comp_level 9; + gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png; + gzip_vary on; + gzip_disable "MSIE [1-6]\."; + + location ~ ^/(v1|api) { + proxy_pass http://localhost:9380; + include proxy.conf; + } + + location / { + index index.html; + try_files $uri $uri/ /index.html; + } + + # Cache-Control: max-age~@~AExpires + location ~ ^/static/(css|js|media)/ { + expires 10y; + access_log off; + } + } + proxy.conf: | + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_buffering off; + proxy_read_timeout 3600s; + proxy_send_timeout 3600s; + nginx.conf: | + user root; + worker_processes auto; + + error_log /var/log/nginx/error.log notice; + pid /var/run/nginx.pid; + + events { + worker_connections 1024; + } + + http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + #tcp_nopush on; + + keepalive_timeout 65; + + #gzip on; + client_max_body_size 128M; + + include /etc/nginx/conf.d/ragflow.conf; + } diff --git a/helm/templates/redis.yaml b/helm/templates/redis.yaml new file mode 100644 index 0000000..2d382f3 --- /dev/null +++ b/helm/templates/redis.yaml @@ -0,0 +1,133 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "ragflow.fullname" . }}-redis + annotations: + "helm.sh/resource-policy": keep + labels: + {{- include "ragflow.labels" . | nindent 4 }} + app.kubernetes.io/component: redis +spec: + ports: + - port: 6379 + name: redis + protocol: TCP + clusterIP: None # Headless service for StatefulSet + selector: + {{- include "ragflow.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: redis +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "ragflow.fullname" . }}-redis + labels: + {{- include "ragflow.labels" . | nindent 4 }} + app.kubernetes.io/component: redis +spec: + serviceName: {{ include "ragflow.fullname" . }}-redis + replicas: 1 + selector: + matchLabels: + {{- include "ragflow.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: redis + template: + metadata: + labels: + {{- include "ragflow.labels" . | nindent 8 }} + app.kubernetes.io/component: redis + annotations: + checksum/config-env: {{ include (print $.Template.BasePath "/env.yaml") . | sha256sum }} + spec: + {{- if or .Values.imagePullSecrets .Values.redis.image.pullSecrets }} + imagePullSecrets: + {{- with .Values.imagePullSecrets }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.redis.image.pullSecrets }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- end }} + terminationGracePeriodSeconds: 60 + containers: + - name: redis + image: {{ .Values.redis.image.repository }}:{{ .Values.redis.image.tag }} + {{- with .Values.redis.image.pullPolicy }} + imagePullPolicy: {{ . }} + {{- end }} + command: + - "sh" + - "-c" + - "exec redis-server --requirepass ${REDIS_PASSWORD} --maxmemory 128mb --maxmemory-policy allkeys-lru" + envFrom: + - secretRef: + name: {{ include "ragflow.fullname" . }}-env-config + ports: + - containerPort: 6379 + name: redis + {{- if .Values.redis.persistence.enabled }} + volumeMounts: + - name: redis-data + mountPath: /data + {{- end }} + {{- with .Values.redis.deployment.resources }} + resources: + {{- . | toYaml | nindent 12 }} + {{- end }} + {{- if .Values.redis.persistence.enabled }} + {{- with .Values.redis.persistence.retentionPolicy }} + persistentVolumeClaimRetentionPolicy: + {{- with .whenDeleted }} + whenDeleted: {{ . }} + {{- end }} + {{- with .whenScaled }} + whenScaled: {{ . }} + {{- end }} + {{- end }} + volumeClaimTemplates: + - metadata: + name: redis-data + labels: + {{- include "ragflow.selectorLabels" . | nindent 10 }} + app.kubernetes.io/component: redis + spec: + accessModes: + - ReadWriteOnce + {{- with .Values.redis.storage.className }} + storageClassName: {{ . }} + {{- end }} + resources: + requests: + storage: {{ .Values.redis.storage.capacity }} + {{- end }} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "ragflow.fullname" . }}-redis-svc + labels: + {{- include "ragflow.labels" . | nindent 4 }} + app.kubernetes.io/component: redis +spec: + ports: + - port: 6379 + targetPort: redis + protocol: TCP + selector: + {{- include "ragflow.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: redis +--- +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ include "ragflow.fullname" . }}-redis-pdb + labels: + {{- include "ragflow.labels" . | nindent 4 }} + app.kubernetes.io/component: redis +spec: + minAvailable: 1 + selector: + matchLabels: + {{- include "ragflow.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: redis diff --git a/helm/templates/tests/test-connection.yaml b/helm/templates/tests/test-connection.yaml new file mode 100644 index 0000000..f3d3a48 --- /dev/null +++ b/helm/templates/tests/test-connection.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "ragflow.fullname" . }}-test-connection" + labels: + {{- include "ragflow.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: busybox + command: + - 'wget' + args: + - {{ printf "%s.%s.svc" (include "ragflow.fullname" .) .Release.Namespace }} + restartPolicy: Never diff --git a/helm/values.yaml b/helm/values.yaml new file mode 100644 index 0000000..a2e6bbf --- /dev/null +++ b/helm/values.yaml @@ -0,0 +1,234 @@ +# Based on docker compose .env file + +# Global image pull secrets configuration +imagePullSecrets: [] + +env: + # The type of doc engine to use. + # Available options: + # - `elasticsearch` (default) + # - `infinity` (https://github.com/infiniflow/infinity) + # - `opensearch` (https://github.com/opensearch-project/OpenSearch) + # DOC_ENGINE: elasticsearch + DOC_ENGINE: infinity + # DOC_ENGINE: opensearch + + # The version of Elasticsearch. + STACK_VERSION: "8.11.3" + + # The password for Elasticsearch + ELASTIC_PASSWORD: infini_rag_flow_helm + + # The password for OpenSearch. + # At least one uppercase letter, one lowercase letter, one digit, and one special character + OPENSEARCH_PASSWORD: infini_rag_flow_OS_01 + + # The password for MySQL + MYSQL_PASSWORD: infini_rag_flow_helm + # The database of the MySQL service to use + MYSQL_DBNAME: rag_flow + + # The username for MinIO. + MINIO_ROOT_USER: rag_flow + # The password for MinIO + MINIO_PASSWORD: infini_rag_flow_helm + + # The password for Redis + REDIS_PASSWORD: infini_rag_flow_helm + + # The local time zone. + TIMEZONE: "Asia/Shanghai" + + # Uncomment the following line if you have limited access to huggingface.co: + # HF_ENDPOINT: https://hf-mirror.com + + # The maximum file size for each uploaded file, in bytes. + # You can uncomment this line and update the value if you wish to change 128M file size limit + # MAX_CONTENT_LENGTH: "134217728" + # After making the change, ensure you update `client_max_body_size` in nginx/nginx.conf correspondingly. + + # The number of document chunks processed in a single batch during document parsing. + DOC_BULK_SIZE: 4 + + # The number of text chunks processed in a single batch during embedding vectorization. + EMBEDDING_BATCH_SIZE: 16 + +ragflow: + image: + repository: infiniflow/ragflow + tag: v0.21.1-slim + pullPolicy: IfNotPresent + pullSecrets: [] + # Optional service configuration overrides + # to be written to local.service_conf.yaml + # inside the RAGFlow container + # https://ragflow.io/docs/dev/configurations#service-configuration + service_conf: + + # Optional yaml formatted override for the + # llm_factories.json file inside the RAGFlow + # container. + llm_factories: + # factory_llm_infos: + # - name: OpenAI-API-Compatible + # logo: "" + # tags: "LLM,TEXT EMBEDDING,SPEECH2TEXT,MODERATION" + # status: "1" + # llm: + # - llm_name: my-custom-llm + # tags: "LLM,CHAT," + # max_tokens: 100000 + # model_type: chat + # is_tools: false + + # Kubernetes configuration + deployment: + strategy: + resources: + service: + # Use LoadBalancer to expose the web interface externally + type: ClusterIP + api: + service: + enabled: true + type: ClusterIP + +infinity: + image: + repository: infiniflow/infinity + tag: v0.6.1 + pullPolicy: IfNotPresent + pullSecrets: [] + storage: + className: + capacity: 5Gi + deployment: + strategy: + resources: + service: + type: ClusterIP + +elasticsearch: + image: + repository: elasticsearch + tag: "8.11.3" + pullPolicy: IfNotPresent + pullSecrets: [] + initContainers: + alpine: + repository: alpine + tag: latest + pullPolicy: IfNotPresent + busybox: + repository: busybox + tag: latest + pullPolicy: IfNotPresent + storage: + className: + capacity: 20Gi + deployment: + strategy: + resources: + requests: + cpu: "4" + memory: "16Gi" + service: + type: ClusterIP + +opensearch: + image: + repository: opensearchproject/opensearch + tag: 2.19.1 + pullPolicy: IfNotPresent + pullSecrets: [] + initContainers: + alpine: + repository: alpine + tag: latest + pullPolicy: IfNotPresent + busybox: + repository: busybox + tag: latest + pullPolicy: IfNotPresent + storage: + className: + capacity: 20Gi + deployment: + strategy: + resources: + requests: + cpu: "4" + memory: "16Gi" + service: + type: ClusterIP + +minio: + image: + repository: quay.io/minio/minio + tag: RELEASE.2023-12-20T01-00-02Z + pullPolicy: IfNotPresent + pullSecrets: [] + storage: + className: + capacity: 5Gi + deployment: + strategy: + resources: + service: + type: ClusterIP + +mysql: + image: + repository: mysql + tag: 8.0.39 + pullPolicy: IfNotPresent + pullSecrets: [] + storage: + className: + capacity: 5Gi + deployment: + strategy: + resources: + service: + type: ClusterIP + +redis: + image: + repository: valkey/valkey + tag: 8 + pullPolicy: IfNotPresent + pullSecrets: [] + storage: + className: + capacity: 5Gi + persistence: + enabled: true + # Set's the retention policy for the persistent storage (only available in k8s 1.32 or later) + # https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#persistentvolumeclaim-retention + # retentionPolicy: + # whenDeleted: Delete + # whenScaled: Delete + deployment: + strategy: + resources: + service: + type: ClusterIP + + +# This block is for setting up web service ingress. For more information, see: +# https://kubernetes.io/docs/concepts/services-networking/ingress/ +ingress: + enabled: false + className: "" + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + - host: chart-example.local + paths: + - path: / + pathType: ImplementationSpecific + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local diff --git a/main-ocr.py b/main-ocr.py deleted file mode 100644 index 19522f3..0000000 --- a/main-ocr.py +++ /dev/null @@ -1,191 +0,0 @@ -# -# Copyright 2025 The InfiniFlow Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -""" -OCR PDF处理服务的主程序入口 -独立运行,不依赖RAGFlow的其他部分 -""" - -import argparse -import logging -import os -import sys -import signal -from pathlib import Path - -# 确保项目根目录在 sys.path 中 -_current_file = Path(__file__).resolve() -_project_root = _current_file.parent.parent -if str(_project_root) not in sys.path: - sys.path.insert(0, str(_project_root)) - -import uvicorn -from fastapi import FastAPI -from fastapi.middleware.cors import CORSMiddleware - -from ocr.api import ocr_router -from ocr.config import MODEL_DIR - -# 配置日志 -logging.basicConfig( - level=logging.INFO, - format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', - handlers=[ - logging.StreamHandler(sys.stdout) - ] -) - -logger = logging.getLogger(__name__) - - -def create_app() -> FastAPI: - """创建FastAPI应用实例""" - app = FastAPI( - title="OCR PDF Parser API", - description="独立的OCR PDF处理服务,提供PDF文档的OCR识别功能", - version="1.0.0", - docs_url="/apidocs", # Swagger UI 文档地址 - redoc_url="/redoc", # ReDoc 文档地址(备用) - openapi_url="/openapi.json" # OpenAPI JSON schema 地址 - ) - - # 添加CORS中间件 - app.add_middleware( - CORSMiddleware, - allow_origins=["*"], # 生产环境中应该设置具体的域名 - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], - ) - - # 注册OCR路由 - app.include_router(ocr_router) - - # 根路径 - @app.get("/") - async def root(): - return { - "service": "OCR PDF Parser", - "version": "1.0.0", - "docs": "/apidocs", - "health": "/api/v1/ocr/health" - } - - return app - - -def signal_handler(sig, frame): - """信号处理器,用于优雅关闭""" - logger.info("Received shutdown signal, exiting...") - sys.exit(0) - - -def main(): - """主函数""" - parser = argparse.ArgumentParser(description="OCR PDF处理服务") - parser.add_argument( - "--host", - type=str, - default="0.0.0.0", - help="服务器监听地址 (default: 0.0.0.0)" - ) - parser.add_argument( - "--port", - type=int, - default=8000, - help="服务器端口 (default: 8000)" - ) - parser.add_argument( - "--reload", - action="store_true", - help="开发模式:自动重载代码" - ) - parser.add_argument( - "--workers", - type=int, - default=1, - help="工作进程数 (default: 1)" - ) - parser.add_argument( - "--log-level", - type=str, - default="info", - choices=["critical", "error", "warning", "info", "debug", "trace"], - help="日志级别 (default: info)" - ) - parser.add_argument( - "--model-dir", - type=str, - default=None, - help=f"OCR模型目录路径 (default: {MODEL_DIR})" - ) - - args = parser.parse_args() - - # 设置模型目录(如果提供) - if args.model_dir: - os.environ["OCR_MODEL_DIR"] = args.model_dir - logger.info(f"Using custom model directory: {args.model_dir}") - - # 检查模型目录 - model_dir = os.environ.get("OCR_MODEL_DIR", MODEL_DIR) - if model_dir and not os.path.exists(model_dir): - logger.warning(f"Model directory does not exist: {model_dir}") - logger.info("Models will be downloaded on first use") - - # 注册信号处理器 - signal.signal(signal.SIGINT, signal_handler) - signal.signal(signal.SIGTERM, signal_handler) - - # 显示启动信息 - logger.info("=" * 60) - logger.info("OCR PDF Parser Service") - logger.info("=" * 60) - logger.info(f"Host: {args.host}") - logger.info(f"Port: {args.port}") - logger.info(f"Model Directory: {model_dir}") - logger.info(f"Workers: {args.workers}") - logger.info(f"Reload: {args.reload}") - logger.info(f"Log Level: {args.log_level}") - logger.info("=" * 60) - logger.info(f"API Documentation (Swagger): http://{args.host}:{args.port}/apidocs") - logger.info(f"API Documentation (ReDoc): http://{args.host}:{args.port}/redoc") - logger.info(f"Health Check: http://{args.host}:{args.port}/api/v1/ocr/health") - logger.info("=" * 60) - - # 创建应用 - app = create_app() - - # 启动服务器 - try: - uvicorn.run( - app, - host=args.host, - port=args.port, - log_level=args.log_level, - reload=args.reload, - workers=args.workers if not args.reload else 1, # reload模式不支持多进程 - access_log=True - ) - except KeyboardInterrupt: - logger.info("Server stopped by user") - except Exception as e: - logger.error(f"Server error: {e}", exc_info=True) - sys.exit(1) - - -if __name__ == "__main__": - main() - diff --git a/main.py b/main.py deleted file mode 100644 index 5596b44..0000000 --- a/main.py +++ /dev/null @@ -1,16 +0,0 @@ -# This is a sample Python script. - -# Press Shift+F10 to execute it or replace it with your code. -# Press Double Shift to search everywhere for classes, files, tool windows, actions, and settings. - - -def print_hi(name): - # Use a breakpoint in the code line below to debug your script. - print(f'Hi, {name}') # Press Ctrl+F8 to toggle the breakpoint. - - -# Press the green button in the gutter to run the script. -if __name__ == '__main__': - print_hi('PyCharm') - -# See PyCharm help at https://www.jetbrains.com/help/pycharm/ diff --git a/ocr/__init__.py b/ocr/__init__.py deleted file mode 100644 index 5cf99ac..0000000 --- a/ocr/__init__.py +++ /dev/null @@ -1,41 +0,0 @@ -# -# Copyright 2025 The InfiniFlow Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -""" -独立的 OCR 模块 - -此模块从 RAGFlow 项目中提取,已经移除了对 RAGFlow 特定模块的依赖。 -可以直接作为独立模块使用。 - -使用方法: - from ocr import OCR, SimplePdfParser - import cv2 - - ocr = OCR() - img = cv2.imread("image.jpg") - results = ocr(img) - - parser = SimplePdfParser() - result = parser.parse_pdf("document.pdf") -""" - -# 处理导入问题:支持直接运行和模块导入 -import sys -from pathlib import Path - - -__all__ = ['OCR', 'TextDetector', 'TextRecognizer', 'SimplePdfParser'] - - diff --git a/ocr/api.py b/ocr/api.py deleted file mode 100644 index 0ff0e61..0000000 --- a/ocr/api.py +++ /dev/null @@ -1,525 +0,0 @@ -# -# Copyright 2025 The InfiniFlow Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -""" -OCR PDF处理的FastAPI路由 -提供HTTP接口用于PDF的OCR识别 -""" - -import asyncio -import logging -import os -import sys -import tempfile -from pathlib import Path -from typing import Optional - -from fastapi import APIRouter, File, Form, HTTPException, UploadFile -from fastapi.responses import JSONResponse -from pydantic import BaseModel, Field - -from ocr import SimplePdfParser -from ocr.config import MODEL_DIR - -logger = logging.getLogger(__name__) - -ocr_router = APIRouter(prefix="/api/v1/ocr", tags=["OCR"]) - -# 全局解析器实例(懒加载) -_parser_instance: Optional[SimplePdfParser] = None - - -def get_parser() -> SimplePdfParser: - """获取全局解析器实例(单例模式)""" - global _parser_instance - if _parser_instance is None: - logger.info(f"Initializing OCR parser with model_dir={MODEL_DIR}") - _parser_instance = SimplePdfParser(model_dir=MODEL_DIR) - return _parser_instance - - -class ParseResponse(BaseModel): - """解析响应模型""" - success: bool - message: str - data: Optional[dict] = None - - -@ocr_router.get( - "/health", - summary="健康检查", - description="检查OCR服务的健康状态和配置信息", - response_description="返回服务状态和模型目录信息" -) -async def health_check(): - """ - 健康检查端点 - - 用于检查OCR服务的运行状态和配置信息。 - - Returns: - dict: 包含服务状态和模型目录的信息 - """ - return { - "status": "healthy", - "service": "OCR PDF Parser", - "model_dir": MODEL_DIR - } - - -@ocr_router.post( - "/parse", - response_model=ParseResponse, - summary="上传并解析PDF文件", - description="上传PDF文件并通过OCR识别提取文本内容", - response_description="返回OCR识别结果" -) -async def parse_pdf_endpoint( - file: UploadFile = File(..., description="PDF文件,支持上传任意PDF文档"), - page_from: int = Form(1, ge=1, description="起始页码(从1开始,默认为1)"), - page_to: int = Form(0, ge=0, description="结束页码(0表示解析到最后一页,默认为0)"), - zoomin: int = Form(3, ge=1, le=5, description="图像放大倍数(1-5,数值越大识别精度越高但速度越慢,默认为3)") -): - """ - 上传并解析PDF文件 - - 通过上传PDF文件,使用OCR技术识别并提取其中的文本内容。 - 支持指定解析的页码范围,以及调整图像放大倍数以平衡识别精度和速度。 - - Args: - file: 上传的PDF文件(multipart/form-data格式) - page_from: 起始页码(从1开始,最小值为1) - page_to: 结束页码(0表示解析到最后一页,最小值为0) - zoomin: 图像放大倍数(1-5之间,数值越大识别精度越高但处理速度越慢) - - Returns: - ParseResponse: 包含解析结果的响应对象,包括: - - success: 是否成功 - - message: 操作结果消息 - - data: OCR识别的文本内容和元数据 - - Raises: - HTTPException: 400 - 如果文件不是PDF格式或文件为空 - HTTPException: 500 - 如果解析过程中发生错误 - """ - if not file.filename.lower().endswith('.pdf'): - raise HTTPException(status_code=400, detail="只支持PDF文件") - - # 保存上传的文件到临时目录 - temp_file = None - try: - # 读取文件内容 - content = await file.read() - if not content: - raise HTTPException(status_code=400, detail="文件为空") - - # 创建临时文件 - with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') as tmp: - tmp.write(content) - temp_file = tmp.name - - logger.info(f"Parsing PDF file: {file.filename}, pages {page_from}-{page_to or 'end'}, zoomin={zoomin}") - - # 解析PDF(parse_pdf是同步方法,使用to_thread在线程池中执行) - parser = get_parser() - result = await asyncio.to_thread( - parser.parse_pdf, - temp_file, - zoomin, - page_from - 1, # 转换为从0开始的索引 - (page_to - 1) if page_to > 0 else 299, # 转换为从0开始的索引 - None # callback - ) - - return ParseResponse( - success=True, - message=f"成功解析PDF: {file.filename}", - data=result - ) - - except Exception as e: - logger.error(f"Error parsing PDF: {str(e)}", exc_info=True) - raise HTTPException( - status_code=500, - detail=f"解析PDF时发生错误: {str(e)}" - ) - - finally: - # 清理临时文件 - if temp_file and os.path.exists(temp_file): - try: - os.unlink(temp_file) - except Exception as e: - logger.warning(f"Failed to delete temp file {temp_file}: {e}") - - -@ocr_router.post( - "/parse/bytes", - response_model=ParseResponse, - summary="通过二进制数据解析PDF", - description="直接通过二进制数据解析PDF文件,无需上传文件", - response_description="返回OCR识别结果" -) -async def parse_pdf_bytes( - pdf_bytes: bytes = File(..., description="PDF文件的二进制数据(multipart/form-data格式)"), - filename: str = Form("document.pdf", description="文件名(仅用于日志记录,不影响解析)"), - page_from: int = Form(1, ge=1, description="起始页码(从1开始,默认为1)"), - page_to: int = Form(0, ge=0, description="结束页码(0表示解析到最后一页,默认为0)"), - zoomin: int = Form(3, ge=1, le=5, description="图像放大倍数(1-5,数值越大识别精度越高但速度越慢,默认为3)") -): - """ - 直接通过二进制数据解析PDF - - 适用于已获取PDF二进制数据的场景,无需文件上传步骤。 - 直接将PDF的二进制数据提交即可进行OCR识别。 - - Args: - pdf_bytes: PDF文件的二进制数据(以文件形式提交) - filename: 文件名(仅用于日志记录,不影响实际解析过程) - page_from: 起始页码(从1开始,最小值为1) - page_to: 结束页码(0表示解析到最后一页,最小值为0) - zoomin: 图像放大倍数(1-5之间,数值越大识别精度越高但处理速度越慢) - - Returns: - ParseResponse: 包含解析结果的响应对象 - - Raises: - HTTPException: 400 - 如果PDF数据为空 - HTTPException: 500 - 如果解析过程中发生错误 - """ - if not pdf_bytes: - raise HTTPException(status_code=400, detail="PDF数据为空") - - # 保存到临时文件 - temp_file = None - try: - with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') as tmp: - tmp.write(pdf_bytes) - temp_file = tmp.name - - logger.info(f"Parsing PDF bytes (filename: {filename}), pages {page_from}-{page_to or 'end'}, zoomin={zoomin}") - - # 解析PDF(parse_pdf是同步方法,使用to_thread在线程池中执行) - parser = get_parser() - result = await asyncio.to_thread( - parser.parse_pdf, - temp_file, - zoomin, - page_from - 1, # 转换为从0开始的索引 - (page_to - 1) if page_to > 0 else 299, # 转换为从0开始的索引 - None # callback - ) - - return ParseResponse( - success=True, - message=f"成功解析PDF: {filename}", - data=result - ) - - except Exception as e: - logger.error(f"Error parsing PDF bytes: {str(e)}", exc_info=True) - raise HTTPException( - status_code=500, - detail=f"解析PDF时发生错误: {str(e)}" - ) - - finally: - # 清理临时文件 - if temp_file and os.path.exists(temp_file): - try: - os.unlink(temp_file) - except Exception as e: - logger.warning(f"Failed to delete temp file {temp_file}: {e}") - - -@ocr_router.post( - "/parse/path", - response_model=ParseResponse, - summary="通过文件路径解析PDF", - description="通过服务器本地文件路径解析PDF文件", - response_description="返回OCR识别结果" -) -async def parse_pdf_path( - file_path: str = Form(..., description="PDF文件在服务器上的本地路径(必须是可访问的绝对路径)"), - page_from: int = Form(1, ge=1, description="起始页码(从1开始,默认为1)"), - page_to: int = Form(0, ge=0, description="结束页码(0表示解析到最后一页,默认为0)"), - zoomin: int = Form(3, ge=1, le=5, description="图像放大倍数(1-5,数值越大识别精度越高但速度越慢,默认为3)") -): - """ - 通过文件路径解析PDF - - 适用于PDF文件已经存在于服务器上的场景。 - 通过提供文件路径直接进行OCR识别,无需上传文件。 - - Args: - file_path: PDF文件在服务器上的本地路径(必须是服务器可访问的绝对路径) - page_from: 起始页码(从1开始,最小值为1) - page_to: 结束页码(0表示解析到最后一页,最小值为0) - zoomin: 图像放大倍数(1-5之间,数值越大识别精度越高但处理速度越慢) - - Returns: - ParseResponse: 包含解析结果的响应对象 - - Raises: - HTTPException: 400 - 如果文件不是PDF格式 - HTTPException: 404 - 如果文件不存在 - HTTPException: 500 - 如果解析过程中发生错误 - - Note: - 此端点需要确保提供的文件路径在服务器上可访问。 - 建议仅在内网环境或受信任的环境中使用,避免路径遍历安全风险。 - """ - if not os.path.exists(file_path): - raise HTTPException(status_code=404, detail=f"文件不存在: {file_path}") - - if not file_path.lower().endswith('.pdf'): - raise HTTPException(status_code=400, detail="只支持PDF文件") - - try: - logger.info(f"Parsing PDF from path: {file_path}, pages {page_from}-{page_to or 'end'}, zoomin={zoomin}") - - # 解析PDF(parse_pdf是同步方法,使用to_thread在线程池中执行) - parser = get_parser() - result = await asyncio.to_thread( - parser.parse_pdf, - file_path, - zoomin, - page_from - 1, # 转换为从0开始的索引 - (page_to - 1) if page_to > 0 else 299, # 转换为从0开始的索引 - None # callback - ) - - return ParseResponse( - success=True, - message=f"成功解析PDF: {file_path}", - data=result - ) - - except Exception as e: - logger.error(f"Error parsing PDF from path: {str(e)}", exc_info=True) - raise HTTPException( - status_code=500, - detail=f"解析PDF时发生错误: {str(e)}" - ) - - -@ocr_router.post( - "/parse_into_bboxes", - summary="解析PDF并返回边界框", - description="解析PDF文件并返回文本边界框信息,用于文档结构化处理", - response_description="返回包含文本边界框的列表" -) -async def parse_into_bboxes_endpoint( - pdf_bytes: bytes = File(..., description="PDF文件的二进制数据"), - filename: str = Form("document.pdf", description="文件名(仅用于日志)"), - zoomin: int = Form(3, ge=1, le=5, description="图像放大倍数(1-5,默认为3)") -): - """ - 解析PDF并返回边界框 - - 此接口用于将PDF文档解析为结构化文本边界框,每个边界框包含: - - 文本内容 - - 页面编号 - - 坐标信息(x0, x1, top, bottom) - - 布局类型(如 text, table, figure 等) - - 图像数据(如果有) - - Args: - pdf_bytes: PDF文件的二进制数据 - filename: 文件名(仅用于日志记录) - zoomin: 图像放大倍数(1-5之间) - - Returns: - dict: 包含解析结果的对象,data字段为边界框列表 - - Raises: - HTTPException: 400 - 如果PDF数据为空 - HTTPException: 500 - 如果解析过程中发生错误 - """ - if not pdf_bytes: - raise HTTPException(status_code=400, detail="PDF数据为空") - - temp_file = None - try: - # 保存到临时文件 - with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') as tmp: - tmp.write(pdf_bytes) - temp_file = tmp.name - - logger.info(f"Parsing PDF into bboxes: {filename}, zoomin={zoomin}") - - # 定义一个简单的callback包装器,用于处理进度回调(记录日志) - def progress_callback(prog, msg): - logger.info(f"Progress: {prog:.2%} - {msg}") - - parser = get_parser() - result = await asyncio.to_thread( - parser.parse_into_bboxes, - temp_file, - progress_callback, - zoomin - ) - - # 将图像数据转换为base64或None - processed_result = [] - for bbox in result: - processed_bbox = dict(bbox) - # 如果有图像,转换为base64(如果需要的话,可以在这里处理) - # 但为了保持兼容性,我们保留原始格式 - processed_result.append(processed_bbox) - - return ParseResponse( - success=True, - message=f"成功解析PDF为边界框: {filename}", - data={"bboxes": processed_result} - ) - - except Exception as e: - logger.error(f"Error parsing PDF into bboxes: {str(e)}", exc_info=True) - raise HTTPException( - status_code=500, - detail=f"解析PDF为边界框时发生错误: {str(e)}" - ) - - finally: - # 清理临时文件 - if temp_file and os.path.exists(temp_file): - try: - os.unlink(temp_file) - except Exception as e: - logger.warning(f"Failed to delete temp file {temp_file}: {e}") - - -class TextRequest(BaseModel): - """文本处理请求模型""" - text: str = Field(..., description="需要处理的文本内容") - - -class RemoveTagResponse(BaseModel): - """移除标签响应模型""" - success: bool - message: str - text: Optional[str] = None - - -@ocr_router.post( - "/remove_tag", - response_model=RemoveTagResponse, - summary="移除文本中的位置标签", - description="从文本中移除PDF解析生成的位置标签(格式:@@页码\t坐标##)", - response_description="返回移除标签后的文本" -) -async def remove_tag_endpoint(request: TextRequest): - """ - 移除文本中的位置标签 - - 此接口用于从包含位置标签的文本中移除标签信息。 - 位置标签格式为:@@页码\t坐标##,例如:@@1\t100.0\t200.0\t50.0\t60.0## - - Args: - request: 包含待处理文本的请求对象 - - Returns: - RemoveTagResponse: 包含处理结果的响应对象 - - Raises: - HTTPException: 400 - 如果文本为空 - """ - if not request.text: - raise HTTPException(status_code=400, detail="文本内容不能为空") - - try: - cleaned_text = SimplePdfParser.remove_tag(request.text) - - return RemoveTagResponse( - success=True, - message="成功移除文本标签", - text=cleaned_text - ) - - except Exception as e: - logger.error(f"Error removing tag: {str(e)}", exc_info=True) - raise HTTPException( - status_code=500, - detail=f"移除标签时发生错误: {str(e)}" - ) - - -class ExtractPositionsResponse(BaseModel): - """提取位置信息响应模型""" - success: bool - message: str - positions: Optional[list] = None - - -@ocr_router.post( - "/extract_positions", - response_model=ExtractPositionsResponse, - summary="从文本中提取位置信息", - description="从包含位置标签的文本中提取所有位置坐标信息", - response_description="返回提取到的位置信息列表" -) -async def extract_positions_endpoint(request: TextRequest): - """ - 从文本中提取位置信息 - - 此接口用于从包含位置标签的文本中提取所有位置坐标信息。 - 位置标签格式为:@@页码\t坐标## - - 返回的位置信息格式为: - [ - ([页码列表], left, right, top, bottom), - ... - ] - - Args: - request: 包含待处理文本的请求对象 - - Returns: - ExtractPositionsResponse: 包含提取结果的响应对象 - - Raises: - HTTPException: 400 - 如果文本为空 - """ - if not request.text: - raise HTTPException(status_code=400, detail="文本内容不能为空") - - try: - positions = SimplePdfParser.extract_positions(request.text) - - # 将位置信息转换为可序列化的格式 - serializable_positions = [ - { - "page_numbers": pos[0], - "left": pos[1], - "right": pos[2], - "top": pos[3], - "bottom": pos[4] - } - for pos in positions - ] - - return ExtractPositionsResponse( - success=True, - message=f"成功提取 {len(positions)} 个位置信息", - positions=serializable_positions - ) - - except Exception as e: - logger.error(f"Error extracting positions: {str(e)}", exc_info=True) - raise HTTPException( - status_code=500, - detail=f"提取位置信息时发生错误: {str(e)}" - ) \ No newline at end of file diff --git a/ocr/client.py b/ocr/client.py deleted file mode 100644 index 526a20e..0000000 --- a/ocr/client.py +++ /dev/null @@ -1,239 +0,0 @@ -# -# Copyright 2025 The InfiniFlow Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -""" -OCR HTTP 客户端工具类 -用于通过 HTTP 接口调用 OCR 服务 -""" - -import logging -import os -from typing import Optional, Callable, List, Tuple, Any - -try: - import httpx - HAS_HTTPX = True -except ImportError: - HAS_HTTPX = False - import aiohttp - -logger = logging.getLogger(__name__) - - -class OCRClient: - """OCR HTTP 客户端,用于调用 OCR API""" - - def __init__(self, base_url: Optional[str] = None, timeout: float = 300.0): - """ - 初始化 OCR 客户端 - - Args: - base_url: OCR 服务的基础 URL,如果不提供则从环境变量 OCR_SERVICE_URL 获取, - 如果仍未设置则默认为 http://localhost:8000/api/v1/ocr - timeout: 请求超时时间(秒),默认 300 秒 - """ - self.base_url = base_url or os.getenv("OCR_SERVICE_URL", "http://localhost:8000/api/v1/ocr") - self.timeout = timeout - # 移除末尾的斜杠 - if self.base_url.endswith('/'): - self.base_url = self.base_url.rstrip('/') - - async def _make_request(self, method: str, endpoint: str, **kwargs) -> dict: - """内部方法:发送 HTTP 请求""" - url = f"{self.base_url}{endpoint}" - - if HAS_HTTPX: - async with httpx.AsyncClient(timeout=self.timeout) as client: - response = await client.request(method, url, **kwargs) - response.raise_for_status() - return response.json() - else: - async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=self.timeout)) as session: - async with session.request(method, url, **kwargs) as response: - response.raise_for_status() - return await response.json() - - async def remove_tag(self, text: str) -> str: - """ - 移除文本中的位置标签 - - Args: - text: 包含位置标签的文本 - - Returns: - 移除标签后的文本 - """ - response = await self._make_request( - "POST", - "/remove_tag", - json={"text": text} - ) - if response.get("success") and response.get("text") is not None: - return response["text"] - raise Exception(f"移除标签失败: {response.get('message', '未知错误')}") - - def remove_tag_sync(self, text: str) -> str: - """ - 同步版本的 remove_tag(用于同步代码) - - Args: - text: 包含位置标签的文本 - - Returns: - 移除标签后的文本 - """ - import asyncio - try: - loop = asyncio.get_event_loop() - return loop.run_until_complete(self.remove_tag(text)) - except RuntimeError: - # 如果没有事件循环,创建一个新的 - return asyncio.run(self.remove_tag(text)) - - async def extract_positions(self, text: str) -> List[Tuple[List[int], float, float, float, float]]: - """ - 从文本中提取位置信息 - - Args: - text: 包含位置标签的文本 - - Returns: - 位置信息列表,格式为 [(页码列表, left, right, top, bottom), ...] - """ - response = await self._make_request( - "POST", - "/extract_positions", - json={"text": text} - ) - if response.get("success") and response.get("positions") is not None: - # 将响应格式转换为原始格式 - positions = [] - for pos in response["positions"]: - positions.append(( - pos["page_numbers"], - pos["left"], - pos["right"], - pos["top"], - pos["bottom"] - )) - return positions - raise Exception(f"提取位置信息失败: {response.get('message', '未知错误')}") - - def extract_positions_sync(self, text: str) -> List[Tuple[List[int], float, float, float, float]]: - """ - 同步版本的 extract_positions(用于同步代码) - - Args: - text: 包含位置标签的文本 - - Returns: - 位置信息列表 - """ - import asyncio - try: - loop = asyncio.get_event_loop() - return loop.run_until_complete(self.extract_positions(text)) - except RuntimeError: - return asyncio.run(self.extract_positions(text)) - - async def parse_into_bboxes( - self, - pdf_bytes: bytes, - callback: Optional[Callable[[float, str], None]] = None, - zoomin: int = 3, - filename: str = "document.pdf" - ) -> List[dict]: - """ - 解析 PDF 并返回边界框 - - Args: - pdf_bytes: PDF 文件的二进制数据 - callback: 进度回调函数 (progress: float, message: str) -> None - zoomin: 图像放大倍数(1-5,默认为3) - filename: 文件名(仅用于日志) - - Returns: - 边界框列表 - """ - if HAS_HTTPX: - async with httpx.AsyncClient(timeout=self.timeout) as client: - # 注意:httpx 需要将文件和数据合并到 files 参数中 - form_data = {"filename": filename, "zoomin": str(zoomin)} - form_files = {"pdf_bytes": (filename, pdf_bytes, "application/pdf")} - response = await client.post( - f"{self.base_url}/parse_into_bboxes", - files=form_files, - data=form_data - ) - response.raise_for_status() - result = response.json() - else: - async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=self.timeout)) as session: - form_data = aiohttp.FormData() - form_data.add_field('pdf_bytes', pdf_bytes, filename=filename, content_type='application/pdf') - form_data.add_field('filename', filename) - form_data.add_field('zoomin', str(zoomin)) - - async with session.post( - f"{self.base_url}/parse_into_bboxes", - data=form_data - ) as response: - response.raise_for_status() - result = await response.json() - - if result.get("success") and result.get("data") and result["data"].get("bboxes"): - return result["data"]["bboxes"] - raise Exception(f"解析 PDF 失败: {result.get('message', '未知错误')}") - - def parse_into_bboxes_sync( - self, - pdf_bytes: bytes, - callback: Optional[Callable[[float, str], None]] = None, - zoomin: int = 3, - filename: str = "document.pdf" - ) -> List[dict]: - """ - 同步版本的 parse_into_bboxes(用于同步代码) - - Args: - pdf_bytes: PDF 文件的二进制数据 - callback: 进度回调函数(注意:HTTP 调用中无法实时传递回调,此参数将被忽略) - zoomin: 图像放大倍数(1-5,默认为3) - filename: 文件名(仅用于日志) - - Returns: - 边界框列表 - """ - if callback: - logger.warning("HTTP 调用中无法使用 callback,将忽略回调函数") - import asyncio - try: - loop = asyncio.get_event_loop() - return loop.run_until_complete(self.parse_into_bboxes(pdf_bytes, None, zoomin, filename)) - except RuntimeError: - return asyncio.run(self.parse_into_bboxes(pdf_bytes, None, zoomin, filename)) - - -# 全局客户端实例(懒加载) -_global_client: Optional[OCRClient] = None - - -def get_ocr_client() -> OCRClient: - """获取全局 OCR 客户端实例(单例模式)""" - global _global_client - if _global_client is None: - _global_client = OCRClient() - return _global_client - diff --git a/ocr/config.py b/ocr/config.py deleted file mode 100644 index d8d4d1a..0000000 --- a/ocr/config.py +++ /dev/null @@ -1,42 +0,0 @@ -# -# Copyright 2025 The InfiniFlow Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -""" -OCR 模块配置文件 -""" -import os -import logging - -# 并行设备数量(GPU数量,0表示使用CPU) -PARALLEL_DEVICES = 0 -try: - import torch.cuda - PARALLEL_DEVICES = torch.cuda.device_count() - logging.info(f"found {PARALLEL_DEVICES} gpus") -except Exception: - logging.info("can't import package 'torch', using CPU mode") - -# 模型目录 -# 可以从环境变量获取,或使用默认路径 -MODEL_DIR = os.getenv("OCR_MODEL_DIR", None) -if MODEL_DIR is None: - # 默认模型目录:当前项目根目录下的 models/deepdoc 目录 - # 如果不存在,将在 OCR 类初始化时尝试从 HuggingFace 下载 - _base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - MODEL_DIR = os.path.join(_base_dir, "models", "deepdoc") - # 如果目录不存在,设置为 None,让 OCR 类处理下载逻辑 - if not os.path.exists(MODEL_DIR): - MODEL_DIR = None - diff --git a/ocr/ocr.py b/ocr/ocr.py deleted file mode 100644 index 268e4c9..0000000 --- a/ocr/ocr.py +++ /dev/null @@ -1,785 +0,0 @@ -# -# Copyright 2025 The InfiniFlow Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -import gc -import logging -import copy -import time -import os -import sys -from pathlib import Path - -from huggingface_hub import snapshot_download - -# 处理导入问题:支持直接运行和模块导入 -try: - _package = __package__ -except NameError: - _package = None - -if _package is None: - # 直接运行时,添加父目录到路径并使用绝对导入 - parent_dir = Path(__file__).parent.parent - if str(parent_dir) not in sys.path: - sys.path.insert(0, str(parent_dir)) - from ocr.utils import get_project_base_directory - from ocr.config import PARALLEL_DEVICES, MODEL_DIR - from ocr.operators import * # noqa: F403 - import ocr.operators as operators - from ocr.postprocess import build_post_process -else: - # 作为模块导入时使用相对导入 - from utils import get_project_base_directory - from config import PARALLEL_DEVICES, MODEL_DIR - from operators import * # noqa: F403 - import operators - from postprocess import build_post_process - -import math -import numpy as np -import cv2 -import onnxruntime as ort - -loaded_models = {} - -def transform(data, ops=None): - """ transform """ - if ops is None: - ops = [] - for op in ops: - data = op(data) - if data is None: - return None - return data - - -def create_operators(op_param_list, global_config=None): - """ - create operators based on the config - - Args: - params(list): a dict list, used to create some operators - """ - assert isinstance( - op_param_list, list), ('operator config should be a list') - ops = [] - for operator in op_param_list: - assert isinstance(operator, - dict) and len(operator) == 1, "yaml format error" - op_name = list(operator)[0] - param = {} if operator[op_name] is None else operator[op_name] - if global_config is not None: - param.update(global_config) - op = getattr(operators, op_name)(**param) - ops.append(op) - return ops - - -def load_model(model_dir, nm, device_id: int | None = None): - model_file_path = os.path.join(model_dir, nm + ".onnx") - model_cached_tag = model_file_path + str(device_id) if device_id is not None else model_file_path - - global loaded_models - loaded_model = loaded_models.get(model_cached_tag) - if loaded_model: - logging.info(f"load_model {model_file_path} reuses cached model") - return loaded_model - - if not os.path.exists(model_file_path): - raise ValueError("not find model file path {}".format( - model_file_path)) - - def cuda_is_available(): - try: - import torch - if torch.cuda.is_available() and torch.cuda.device_count() > device_id: - return True - except Exception: - return False - return False - - options = ort.SessionOptions() - options.enable_cpu_mem_arena = False - options.execution_mode = ort.ExecutionMode.ORT_SEQUENTIAL - options.intra_op_num_threads = 2 - options.inter_op_num_threads = 2 - - # https://github.com/microsoft/onnxruntime/issues/9509#issuecomment-951546580 - # Shrink GPU memory after execution - run_options = ort.RunOptions() - if cuda_is_available(): - cuda_provider_options = { - "device_id": device_id, # Use specific GPU - "gpu_mem_limit": 512 * 1024 * 1024, # Limit gpu memory - "arena_extend_strategy": "kNextPowerOfTwo", # gpu memory allocation strategy - } - sess = ort.InferenceSession( - model_file_path, - options=options, - providers=['CUDAExecutionProvider'], - provider_options=[cuda_provider_options] - ) - run_options.add_run_config_entry("memory.enable_memory_arena_shrinkage", "gpu:" + str(device_id)) - logging.info(f"load_model {model_file_path} uses GPU") - else: - sess = ort.InferenceSession( - model_file_path, - options=options, - providers=['CPUExecutionProvider']) - run_options.add_run_config_entry("memory.enable_memory_arena_shrinkage", "cpu") - logging.info(f"load_model {model_file_path} uses CPU") - loaded_model = (sess, run_options) - loaded_models[model_cached_tag] = loaded_model - return loaded_model - - -class TextRecognizer: - def __init__(self, model_dir, device_id: int | None = None): - self.rec_image_shape = [int(v) for v in "3, 48, 320".split(",")] - self.rec_batch_num = 16 - postprocess_params = { - 'name': 'CTCLabelDecode', - "character_dict_path": os.path.join(model_dir, "ocr.res"), - "use_space_char": True - } - self.postprocess_op = build_post_process(postprocess_params) - self.predictor, self.run_options = load_model(model_dir, 'rec', device_id) - self.input_tensor = self.predictor.get_inputs()[0] - - def resize_norm_img(self, img, max_wh_ratio): - imgC, imgH, imgW = self.rec_image_shape - - assert imgC == img.shape[2] - imgW = int((imgH * max_wh_ratio)) - w = self.input_tensor.shape[3:][0] - if isinstance(w, str): - pass - elif w is not None and w > 0: - imgW = w - h, w = img.shape[:2] - ratio = w / float(h) - if math.ceil(imgH * ratio) > imgW: - resized_w = imgW - else: - resized_w = int(math.ceil(imgH * ratio)) - - resized_image = cv2.resize(img, (resized_w, imgH)) - resized_image = resized_image.astype('float32') - resized_image = resized_image.transpose((2, 0, 1)) / 255 - resized_image -= 0.5 - resized_image /= 0.5 - padding_im = np.zeros((imgC, imgH, imgW), dtype=np.float32) - padding_im[:, :, 0:resized_w] = resized_image - return padding_im - - def resize_norm_img_vl(self, img, image_shape): - - imgC, imgH, imgW = image_shape - img = img[:, :, ::-1] # bgr2rgb - resized_image = cv2.resize( - img, (imgW, imgH), interpolation=cv2.INTER_LINEAR) - resized_image = resized_image.astype('float32') - resized_image = resized_image.transpose((2, 0, 1)) / 255 - return resized_image - - def resize_norm_img_srn(self, img, image_shape): - imgC, imgH, imgW = image_shape - - img_black = np.zeros((imgH, imgW)) - im_hei = img.shape[0] - im_wid = img.shape[1] - - if im_wid <= im_hei * 1: - img_new = cv2.resize(img, (imgH * 1, imgH)) - elif im_wid <= im_hei * 2: - img_new = cv2.resize(img, (imgH * 2, imgH)) - elif im_wid <= im_hei * 3: - img_new = cv2.resize(img, (imgH * 3, imgH)) - else: - img_new = cv2.resize(img, (imgW, imgH)) - - img_np = np.asarray(img_new) - img_np = cv2.cvtColor(img_np, cv2.COLOR_BGR2GRAY) - img_black[:, 0:img_np.shape[1]] = img_np - img_black = img_black[:, :, np.newaxis] - - row, col, c = img_black.shape - c = 1 - - return np.reshape(img_black, (c, row, col)).astype(np.float32) - - def srn_other_inputs(self, image_shape, num_heads, max_text_length): - - imgC, imgH, imgW = image_shape - feature_dim = int((imgH / 8) * (imgW / 8)) - - encoder_word_pos = np.array(range(0, feature_dim)).reshape( - (feature_dim, 1)).astype('int64') - gsrm_word_pos = np.array(range(0, max_text_length)).reshape( - (max_text_length, 1)).astype('int64') - - gsrm_attn_bias_data = np.ones((1, max_text_length, max_text_length)) - gsrm_slf_attn_bias1 = np.triu(gsrm_attn_bias_data, 1).reshape( - [-1, 1, max_text_length, max_text_length]) - gsrm_slf_attn_bias1 = np.tile( - gsrm_slf_attn_bias1, - [1, num_heads, 1, 1]).astype('float32') * [-1e9] - - gsrm_slf_attn_bias2 = np.tril(gsrm_attn_bias_data, -1).reshape( - [-1, 1, max_text_length, max_text_length]) - gsrm_slf_attn_bias2 = np.tile( - gsrm_slf_attn_bias2, - [1, num_heads, 1, 1]).astype('float32') * [-1e9] - - encoder_word_pos = encoder_word_pos[np.newaxis, :] - gsrm_word_pos = gsrm_word_pos[np.newaxis, :] - - return [ - encoder_word_pos, gsrm_word_pos, gsrm_slf_attn_bias1, - gsrm_slf_attn_bias2 - ] - - def process_image_srn(self, img, image_shape, num_heads, max_text_length): - norm_img = self.resize_norm_img_srn(img, image_shape) - norm_img = norm_img[np.newaxis, :] - - [encoder_word_pos, gsrm_word_pos, gsrm_slf_attn_bias1, gsrm_slf_attn_bias2] = \ - self.srn_other_inputs(image_shape, num_heads, max_text_length) - - gsrm_slf_attn_bias1 = gsrm_slf_attn_bias1.astype(np.float32) - gsrm_slf_attn_bias2 = gsrm_slf_attn_bias2.astype(np.float32) - encoder_word_pos = encoder_word_pos.astype(np.int64) - gsrm_word_pos = gsrm_word_pos.astype(np.int64) - - return (norm_img, encoder_word_pos, gsrm_word_pos, gsrm_slf_attn_bias1, - gsrm_slf_attn_bias2) - - def resize_norm_img_sar(self, img, image_shape, - width_downsample_ratio=0.25): - imgC, imgH, imgW_min, imgW_max = image_shape - h = img.shape[0] - w = img.shape[1] - valid_ratio = 1.0 - # make sure new_width is an integral multiple of width_divisor. - width_divisor = int(1 / width_downsample_ratio) - # resize - ratio = w / float(h) - resize_w = math.ceil(imgH * ratio) - if resize_w % width_divisor != 0: - resize_w = round(resize_w / width_divisor) * width_divisor - if imgW_min is not None: - resize_w = max(imgW_min, resize_w) - if imgW_max is not None: - valid_ratio = min(1.0, 1.0 * resize_w / imgW_max) - resize_w = min(imgW_max, resize_w) - resized_image = cv2.resize(img, (resize_w, imgH)) - resized_image = resized_image.astype('float32') - # norm - if image_shape[0] == 1: - resized_image = resized_image / 255 - resized_image = resized_image[np.newaxis, :] - else: - resized_image = resized_image.transpose((2, 0, 1)) / 255 - resized_image -= 0.5 - resized_image /= 0.5 - resize_shape = resized_image.shape - padding_im = -1.0 * np.ones((imgC, imgH, imgW_max), dtype=np.float32) - padding_im[:, :, 0:resize_w] = resized_image - pad_shape = padding_im.shape - - return padding_im, resize_shape, pad_shape, valid_ratio - - def resize_norm_img_spin(self, img): - img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) - # return padding_im - img = cv2.resize(img, tuple([100, 32]), cv2.INTER_CUBIC) - img = np.array(img, np.float32) - img = np.expand_dims(img, -1) - img = img.transpose((2, 0, 1)) - mean = [127.5] - std = [127.5] - mean = np.array(mean, dtype=np.float32) - std = np.array(std, dtype=np.float32) - mean = np.float32(mean.reshape(1, -1)) - stdinv = 1 / np.float32(std.reshape(1, -1)) - img -= mean - img *= stdinv - return img - - def resize_norm_img_svtr(self, img, image_shape): - - imgC, imgH, imgW = image_shape - resized_image = cv2.resize( - img, (imgW, imgH), interpolation=cv2.INTER_LINEAR) - resized_image = resized_image.astype('float32') - resized_image = resized_image.transpose((2, 0, 1)) / 255 - resized_image -= 0.5 - resized_image /= 0.5 - return resized_image - - def resize_norm_img_abinet(self, img, image_shape): - - imgC, imgH, imgW = image_shape - - resized_image = cv2.resize( - img, (imgW, imgH), interpolation=cv2.INTER_LINEAR) - resized_image = resized_image.astype('float32') - resized_image = resized_image / 255. - - mean = np.array([0.485, 0.456, 0.406]) - std = np.array([0.229, 0.224, 0.225]) - resized_image = ( - resized_image - mean[None, None, ...]) / std[None, None, ...] - resized_image = resized_image.transpose((2, 0, 1)) - resized_image = resized_image.astype('float32') - - return resized_image - - def norm_img_can(self, img, image_shape): - - img = cv2.cvtColor( - img, cv2.COLOR_BGR2GRAY) # CAN only predict gray scale image - - if self.rec_image_shape[0] == 1: - h, w = img.shape - _, imgH, imgW = self.rec_image_shape - if h < imgH or w < imgW: - padding_h = max(imgH - h, 0) - padding_w = max(imgW - w, 0) - img_padded = np.pad(img, ((0, padding_h), (0, padding_w)), - 'constant', - constant_values=(255)) - img = img_padded - - img = np.expand_dims(img, 0) / 255.0 # h,w,c -> c,h,w - img = img.astype('float32') - - return img - - def close(self): - # close session and release manually - logging.info('Close text recognizer.') - if hasattr(self, "predictor"): - del self.predictor - gc.collect() - - def __call__(self, img_list): - img_num = len(img_list) - # Calculate the aspect ratio of all text bars - width_list = [] - for img in img_list: - width_list.append(img.shape[1] / float(img.shape[0])) - # Sorting can speed up the recognition process - indices = np.argsort(np.array(width_list)) - rec_res = [['', 0.0]] * img_num - batch_num = self.rec_batch_num - st = time.time() - - for beg_img_no in range(0, img_num, batch_num): - end_img_no = min(img_num, beg_img_no + batch_num) - norm_img_batch = [] - imgC, imgH, imgW = self.rec_image_shape[:3] - max_wh_ratio = imgW / imgH - # max_wh_ratio = 0 - for ino in range(beg_img_no, end_img_no): - h, w = img_list[indices[ino]].shape[0:2] - wh_ratio = w * 1.0 / h - max_wh_ratio = max(max_wh_ratio, wh_ratio) - for ino in range(beg_img_no, end_img_no): - norm_img = self.resize_norm_img(img_list[indices[ino]], - max_wh_ratio) - norm_img = norm_img[np.newaxis, :] - norm_img_batch.append(norm_img) - norm_img_batch = np.concatenate(norm_img_batch) - norm_img_batch = norm_img_batch.copy() - - input_dict = {} - input_dict[self.input_tensor.name] = norm_img_batch - for i in range(100000): - try: - outputs = self.predictor.run(None, input_dict, self.run_options) - break - except Exception as e: - if i >= 3: - raise e - time.sleep(5) - preds = outputs[0] - rec_result = self.postprocess_op(preds) - for rno in range(len(rec_result)): - rec_res[indices[beg_img_no + rno]] = rec_result[rno] - - return rec_res, time.time() - st - - def __del__(self): - self.close() - - -class TextDetector: - def __init__(self, model_dir, device_id: int | None = None): - pre_process_list = [{ - 'DetResizeForTest': { - 'limit_side_len': 960, - 'limit_type': "max", - } - }, { - 'NormalizeImage': { - 'std': [0.229, 0.224, 0.225], - 'mean': [0.485, 0.456, 0.406], - 'scale': '1./255.', - 'order': 'hwc' - } - }, { - 'ToCHWImage': None - }, { - 'KeepKeys': { - 'keep_keys': ['image', 'shape'] - } - }] - postprocess_params = {"name": "DBPostProcess", "thresh": 0.3, "box_thresh": 0.5, "max_candidates": 1000, - "unclip_ratio": 1.5, "use_dilation": False, "score_mode": "fast", "box_type": "quad"} - - self.postprocess_op = build_post_process(postprocess_params) - self.predictor, self.run_options = load_model(model_dir, 'det', device_id) - self.input_tensor = self.predictor.get_inputs()[0] - - img_h, img_w = self.input_tensor.shape[2:] - if isinstance(img_h, str) or isinstance(img_w, str): - pass - elif img_h is not None and img_w is not None and img_h > 0 and img_w > 0: - pre_process_list[0] = { - 'DetResizeForTest': { - 'image_shape': [img_h, img_w] - } - } - self.preprocess_op = create_operators(pre_process_list) - - def order_points_clockwise(self, pts): - rect = np.zeros((4, 2), dtype="float32") - s = pts.sum(axis=1) - rect[0] = pts[np.argmin(s)] - rect[2] = pts[np.argmax(s)] - tmp = np.delete(pts, (np.argmin(s), np.argmax(s)), axis=0) - diff = np.diff(np.array(tmp), axis=1) - rect[1] = tmp[np.argmin(diff)] - rect[3] = tmp[np.argmax(diff)] - return rect - - def clip_det_res(self, points, img_height, img_width): - for pno in range(points.shape[0]): - points[pno, 0] = int(min(max(points[pno, 0], 0), img_width - 1)) - points[pno, 1] = int(min(max(points[pno, 1], 0), img_height - 1)) - return points - - def filter_tag_det_res(self, dt_boxes, image_shape): - img_height, img_width = image_shape[0:2] - dt_boxes_new = [] - for box in dt_boxes: - if isinstance(box, list): - box = np.array(box) - box = self.order_points_clockwise(box) - box = self.clip_det_res(box, img_height, img_width) - rect_width = int(np.linalg.norm(box[0] - box[1])) - rect_height = int(np.linalg.norm(box[0] - box[3])) - if rect_width <= 3 or rect_height <= 3: - continue - dt_boxes_new.append(box) - dt_boxes = np.array(dt_boxes_new) - return dt_boxes - - def filter_tag_det_res_only_clip(self, dt_boxes, image_shape): - img_height, img_width = image_shape[0:2] - dt_boxes_new = [] - for box in dt_boxes: - if isinstance(box, list): - box = np.array(box) - box = self.clip_det_res(box, img_height, img_width) - dt_boxes_new.append(box) - dt_boxes = np.array(dt_boxes_new) - return dt_boxes - - def close(self): - logging.info("Close text detector.") - if hasattr(self, "predictor"): - del self.predictor - gc.collect() - - def __call__(self, img): - ori_im = img.copy() - data = {'image': img} - - st = time.time() - data = transform(data, self.preprocess_op) - img, shape_list = data - if img is None: - return None, 0 - img = np.expand_dims(img, axis=0) - shape_list = np.expand_dims(shape_list, axis=0) - img = img.copy() - input_dict = {} - input_dict[self.input_tensor.name] = img - for i in range(100000): - try: - outputs = self.predictor.run(None, input_dict, self.run_options) - break - except Exception as e: - if i >= 3: - raise e - time.sleep(5) - - post_result = self.postprocess_op({"maps": outputs[0]}, shape_list) - dt_boxes = post_result[0]['points'] - dt_boxes = self.filter_tag_det_res(dt_boxes, ori_im.shape) - - return dt_boxes, time.time() - st - - def __del__(self): - self.close() - - -class OCR: - def __init__(self, model_dir=None): - """ - If you have trouble downloading HuggingFace models, -_^ this might help!! - - For Linux: - export HF_ENDPOINT=https://hf-mirror.com - - For Windows: - Good luck - ^_- - - """ - if not model_dir: - try: - # 使用配置中的 MODEL_DIR,如果不存在则尝试默认路径 - if MODEL_DIR and os.path.exists(MODEL_DIR): - model_dir = MODEL_DIR - else: - model_dir = os.path.join( - get_project_base_directory(), - "models", "deepdoc") - - # Append muti-gpus task to the list - if PARALLEL_DEVICES > 0: - self.text_detector = [] - self.text_recognizer = [] - for device_id in range(PARALLEL_DEVICES): - self.text_detector.append(TextDetector(model_dir, device_id)) - self.text_recognizer.append(TextRecognizer(model_dir, device_id)) - else: - self.text_detector = [TextDetector(model_dir)] - self.text_recognizer = [TextRecognizer(model_dir)] - - except Exception: - # 如果模型目录不存在,尝试从 HuggingFace 下载 - default_model_dir = os.path.join( - get_project_base_directory(), "models", "deepdoc") - model_dir = snapshot_download(repo_id="InfiniFlow/deepdoc", - local_dir=default_model_dir, - local_dir_use_symlinks=False) - - if PARALLEL_DEVICES > 0: - self.text_detector = [] - self.text_recognizer = [] - for device_id in range(PARALLEL_DEVICES): - self.text_detector.append(TextDetector(model_dir, device_id)) - self.text_recognizer.append(TextRecognizer(model_dir, device_id)) - else: - self.text_detector = [TextDetector(model_dir)] - self.text_recognizer = [TextRecognizer(model_dir)] - else: - # 如果指定了 model_dir,直接使用 - if PARALLEL_DEVICES > 0: - self.text_detector = [] - self.text_recognizer = [] - for device_id in range(PARALLEL_DEVICES): - self.text_detector.append(TextDetector(model_dir, device_id)) - self.text_recognizer.append(TextRecognizer(model_dir, device_id)) - else: - self.text_detector = [TextDetector(model_dir)] - self.text_recognizer = [TextRecognizer(model_dir)] - - self.drop_score = 0.5 - self.crop_image_res_index = 0 - - def get_rotate_crop_image(self, img, points): - ''' - img_height, img_width = img.shape[0:2] - left = int(np.min(points[:, 0])) - right = int(np.max(points[:, 0])) - top = int(np.min(points[:, 1])) - bottom = int(np.max(points[:, 1])) - img_crop = img[top:bottom, left:right, :].copy() - points[:, 0] = points[:, 0] - left - points[:, 1] = points[:, 1] - top - ''' - assert len(points) == 4, "shape of points must be 4*2" - img_crop_width = int( - max( - np.linalg.norm(points[0] - points[1]), - np.linalg.norm(points[2] - points[3]))) - img_crop_height = int( - max( - np.linalg.norm(points[0] - points[3]), - np.linalg.norm(points[1] - points[2]))) - pts_std = np.float32([[0, 0], [img_crop_width, 0], - [img_crop_width, img_crop_height], - [0, img_crop_height]]) - M = cv2.getPerspectiveTransform(points, pts_std) - dst_img = cv2.warpPerspective( - img, - M, (img_crop_width, img_crop_height), - borderMode=cv2.BORDER_REPLICATE, - flags=cv2.INTER_CUBIC) - dst_img_height, dst_img_width = dst_img.shape[0:2] - if dst_img_height * 1.0 / dst_img_width >= 1.5: - # Try original orientation - rec_result = self.text_recognizer[0]([dst_img]) - text, score = rec_result[0][0] - best_score = score - best_img = dst_img - - # Try clockwise 90° rotation - rotated_cw = np.rot90(dst_img, k=3) - rec_result = self.text_recognizer[0]([rotated_cw]) - rotated_cw_text, rotated_cw_score = rec_result[0][0] - if rotated_cw_score > best_score: - best_score = rotated_cw_score - best_img = rotated_cw - - # Try counter-clockwise 90° rotation - rotated_ccw = np.rot90(dst_img, k=1) - rec_result = self.text_recognizer[0]([rotated_ccw]) - rotated_ccw_text, rotated_ccw_score = rec_result[0][0] - if rotated_ccw_score > best_score: - best_img = rotated_ccw - - # Use the best image - dst_img = best_img - return dst_img - - def sorted_boxes(self, dt_boxes): - """ - Sort text boxes in order from top to bottom, left to right - args: - dt_boxes(array):detected text boxes with shape [4, 2] - return: - sorted boxes(array) with shape [4, 2] - """ - num_boxes = dt_boxes.shape[0] - sorted_boxes = sorted(dt_boxes, key=lambda x: (x[0][1], x[0][0])) - _boxes = list(sorted_boxes) - - for i in range(num_boxes - 1): - for j in range(i, -1, -1): - if abs(_boxes[j + 1][0][1] - _boxes[j][0][1]) < 10 and \ - (_boxes[j + 1][0][0] < _boxes[j][0][0]): - tmp = _boxes[j] - _boxes[j] = _boxes[j + 1] - _boxes[j + 1] = tmp - else: - break - return _boxes - - def detect(self, img, device_id: int | None = None): - if device_id is None: - device_id = 0 - - time_dict = {'det': 0, 'rec': 0, 'cls': 0, 'all': 0} - - if img is None: - return None, None, time_dict - - start = time.time() - dt_boxes, elapse = self.text_detector[device_id](img) - time_dict['det'] = elapse - - if dt_boxes is None: - end = time.time() - time_dict['all'] = end - start - return None, None, time_dict - - return zip(self.sorted_boxes(dt_boxes), [ - ("", 0) for _ in range(len(dt_boxes))]) - - def recognize(self, ori_im, box, device_id: int | None = None): - if device_id is None: - device_id = 0 - - img_crop = self.get_rotate_crop_image(ori_im, box) - - rec_res, elapse = self.text_recognizer[device_id]([img_crop]) - text, score = rec_res[0] - if score < self.drop_score: - return "" - return text - - def recognize_batch(self, img_list, device_id: int | None = None): - if device_id is None: - device_id = 0 - rec_res, elapse = self.text_recognizer[device_id](img_list) - texts = [] - for i in range(len(rec_res)): - text, score = rec_res[i] - if score < self.drop_score: - text = "" - texts.append(text) - return texts - - def __call__(self, img, device_id = 0, cls=True): - time_dict = {'det': 0, 'rec': 0, 'cls': 0, 'all': 0} - if device_id is None: - device_id = 0 - - if img is None: - return None, None, time_dict - - start = time.time() - ori_im = img.copy() - dt_boxes, elapse = self.text_detector[device_id](img) - time_dict['det'] = elapse - - if dt_boxes is None: - end = time.time() - time_dict['all'] = end - start - return None, None, time_dict - - img_crop_list = [] - - dt_boxes = self.sorted_boxes(dt_boxes) - - for bno in range(len(dt_boxes)): - tmp_box = copy.deepcopy(dt_boxes[bno]) - img_crop = self.get_rotate_crop_image(ori_im, tmp_box) - img_crop_list.append(img_crop) - - rec_res, elapse = self.text_recognizer[device_id](img_crop_list) - - time_dict['rec'] = elapse - - filter_boxes, filter_rec_res = [], [] - for box, rec_result in zip(dt_boxes, rec_res): - text, score = rec_result - if score >= self.drop_score: - filter_boxes.append(box) - filter_rec_res.append(rec_result) - end = time.time() - time_dict['all'] = end - start - - # for bno in range(len(img_crop_list)): - # print(f"{bno}, {rec_res[bno]}") - - return list(zip([a.tolist() for a in filter_boxes], filter_rec_res)) - diff --git a/ocr/operators.py b/ocr/operators.py deleted file mode 100644 index d7ff403..0000000 --- a/ocr/operators.py +++ /dev/null @@ -1,726 +0,0 @@ -# -# Copyright 2024 The InfiniFlow Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -import logging -import sys -import six -import cv2 -import numpy as np -import math -from PIL import Image - - -class DecodeImage: - """ decode image """ - - def __init__(self, - img_mode='RGB', - channel_first=False, - ignore_orientation=False, - **kwargs): - self.img_mode = img_mode - self.channel_first = channel_first - self.ignore_orientation = ignore_orientation - - def __call__(self, data): - img = data['image'] - if six.PY2: - assert isinstance(img, str) and len( - img) > 0, "invalid input 'img' in DecodeImage" - else: - assert isinstance(img, bytes) and len( - img) > 0, "invalid input 'img' in DecodeImage" - img = np.frombuffer(img, dtype='uint8') - if self.ignore_orientation: - img = cv2.imdecode(img, cv2.IMREAD_IGNORE_ORIENTATION | - cv2.IMREAD_COLOR) - else: - img = cv2.imdecode(img, 1) - if img is None: - return None - if self.img_mode == 'GRAY': - img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) - elif self.img_mode == 'RGB': - assert img.shape[2] == 3, 'invalid shape of image[%s]' % ( - img.shape) - img = img[:, :, ::-1] - - if self.channel_first: - img = img.transpose((2, 0, 1)) - - data['image'] = img - return data - - -class StandardizeImag: - """normalize image - Args: - mean (list): im - mean - std (list): im / std - is_scale (bool): whether need im / 255 - norm_type (str): type in ['mean_std', 'none'] - """ - - def __init__(self, mean, std, is_scale=True, norm_type='mean_std'): - self.mean = mean - self.std = std - self.is_scale = is_scale - self.norm_type = norm_type - - def __call__(self, im, im_info): - """ - Args: - im (np.ndarray): image (np.ndarray) - im_info (dict): info of image - Returns: - im (np.ndarray): processed image (np.ndarray) - im_info (dict): info of processed image - """ - im = im.astype(np.float32, copy=False) - if self.is_scale: - scale = 1.0 / 255.0 - im *= scale - - if self.norm_type == 'mean_std': - mean = np.array(self.mean)[np.newaxis, np.newaxis, :] - std = np.array(self.std)[np.newaxis, np.newaxis, :] - im -= mean - im /= std - return im, im_info - - -class NormalizeImage: - """ normalize image such as subtract mean, divide std - """ - - def __init__(self, scale=None, mean=None, std=None, order='chw', **kwargs): - if isinstance(scale, str): - scale = eval(scale) - self.scale = np.float32(scale if scale is not None else 1.0 / 255.0) - mean = mean if mean is not None else [0.485, 0.456, 0.406] - std = std if std is not None else [0.229, 0.224, 0.225] - - shape = (3, 1, 1) if order == 'chw' else (1, 1, 3) - self.mean = np.array(mean).reshape(shape).astype('float32') - self.std = np.array(std).reshape(shape).astype('float32') - - def __call__(self, data): - img = data['image'] - from PIL import Image - if isinstance(img, Image.Image): - img = np.array(img) - assert isinstance(img, - np.ndarray), "invalid input 'img' in NormalizeImage" - data['image'] = ( - img.astype('float32') * self.scale - self.mean) / self.std - return data - - -class ToCHWImage: - """ convert hwc image to chw image - """ - - def __init__(self, **kwargs): - pass - - def __call__(self, data): - img = data['image'] - from PIL import Image - if isinstance(img, Image.Image): - img = np.array(img) - data['image'] = img.transpose((2, 0, 1)) - return data - - -class KeepKeys: - def __init__(self, keep_keys, **kwargs): - self.keep_keys = keep_keys - - def __call__(self, data): - data_list = [] - for key in self.keep_keys: - data_list.append(data[key]) - return data_list - - -class Pad: - def __init__(self, size=None, size_div=32, **kwargs): - if size is not None and not isinstance(size, (int, list, tuple)): - raise TypeError("Type of target_size is invalid. Now is {}".format( - type(size))) - if isinstance(size, int): - size = [size, size] - self.size = size - self.size_div = size_div - - def __call__(self, data): - - img = data['image'] - img_h, img_w = img.shape[0], img.shape[1] - if self.size: - resize_h2, resize_w2 = self.size - assert ( - img_h < resize_h2 and img_w < resize_w2 - ), '(h, w) of target size should be greater than (img_h, img_w)' - else: - resize_h2 = max( - int(math.ceil(img.shape[0] / self.size_div) * self.size_div), - self.size_div) - resize_w2 = max( - int(math.ceil(img.shape[1] / self.size_div) * self.size_div), - self.size_div) - img = cv2.copyMakeBorder( - img, - 0, - resize_h2 - img_h, - 0, - resize_w2 - img_w, - cv2.BORDER_CONSTANT, - value=0) - data['image'] = img - return data - - -class LinearResize: - """resize image by target_size and max_size - Args: - target_size (int): the target size of image - keep_ratio (bool): whether keep_ratio or not, default true - interp (int): method of resize - """ - - def __init__(self, target_size, keep_ratio=True, interp=cv2.INTER_LINEAR): - if isinstance(target_size, int): - target_size = [target_size, target_size] - self.target_size = target_size - self.keep_ratio = keep_ratio - self.interp = interp - - def __call__(self, im, im_info): - """ - Args: - im (np.ndarray): image (np.ndarray) - im_info (dict): info of image - Returns: - im (np.ndarray): processed image (np.ndarray) - im_info (dict): info of processed image - """ - assert len(self.target_size) == 2 - assert self.target_size[0] > 0 and self.target_size[1] > 0 - _im_channel = im.shape[2] - im_scale_y, im_scale_x = self.generate_scale(im) - im = cv2.resize( - im, - None, - None, - fx=im_scale_x, - fy=im_scale_y, - interpolation=self.interp) - im_info['im_shape'] = np.array(im.shape[:2]).astype('float32') - im_info['scale_factor'] = np.array( - [im_scale_y, im_scale_x]).astype('float32') - return im, im_info - - def generate_scale(self, im): - """ - Args: - im (np.ndarray): image (np.ndarray) - Returns: - im_scale_x: the resize ratio of X - im_scale_y: the resize ratio of Y - """ - origin_shape = im.shape[:2] - _im_c = im.shape[2] - if self.keep_ratio: - im_size_min = np.min(origin_shape) - im_size_max = np.max(origin_shape) - target_size_min = np.min(self.target_size) - target_size_max = np.max(self.target_size) - im_scale = float(target_size_min) / float(im_size_min) - if np.round(im_scale * im_size_max) > target_size_max: - im_scale = float(target_size_max) / float(im_size_max) - im_scale_x = im_scale - im_scale_y = im_scale - else: - resize_h, resize_w = self.target_size - im_scale_y = resize_h / float(origin_shape[0]) - im_scale_x = resize_w / float(origin_shape[1]) - return im_scale_y, im_scale_x - - -class Resize: - def __init__(self, size=(640, 640), **kwargs): - self.size = size - - def resize_image(self, img): - resize_h, resize_w = self.size - ori_h, ori_w = img.shape[:2] # (h, w, c) - ratio_h = float(resize_h) / ori_h - ratio_w = float(resize_w) / ori_w - img = cv2.resize(img, (int(resize_w), int(resize_h))) - return img, [ratio_h, ratio_w] - - def __call__(self, data): - img = data['image'] - if 'polys' in data: - text_polys = data['polys'] - - img_resize, [ratio_h, ratio_w] = self.resize_image(img) - if 'polys' in data: - new_boxes = [] - for box in text_polys: - new_box = [] - for cord in box: - new_box.append([cord[0] * ratio_w, cord[1] * ratio_h]) - new_boxes.append(new_box) - data['polys'] = np.array(new_boxes, dtype=np.float32) - data['image'] = img_resize - return data - - -class DetResizeForTest: - def __init__(self, **kwargs): - super(DetResizeForTest, self).__init__() - self.resize_type = 0 - self.keep_ratio = False - if 'image_shape' in kwargs: - self.image_shape = kwargs['image_shape'] - self.resize_type = 1 - if 'keep_ratio' in kwargs: - self.keep_ratio = kwargs['keep_ratio'] - elif 'limit_side_len' in kwargs: - self.limit_side_len = kwargs['limit_side_len'] - self.limit_type = kwargs.get('limit_type', 'min') - elif 'resize_long' in kwargs: - self.resize_type = 2 - self.resize_long = kwargs.get('resize_long', 960) - else: - self.limit_side_len = 736 - self.limit_type = 'min' - - def __call__(self, data): - img = data['image'] - src_h, src_w, _ = img.shape - if sum([src_h, src_w]) < 64: - img = self.image_padding(img) - - if self.resize_type == 0: - # img, shape = self.resize_image_type0(img) - img, [ratio_h, ratio_w] = self.resize_image_type0(img) - elif self.resize_type == 2: - img, [ratio_h, ratio_w] = self.resize_image_type2(img) - else: - # img, shape = self.resize_image_type1(img) - img, [ratio_h, ratio_w] = self.resize_image_type1(img) - data['image'] = img - data['shape'] = np.array([src_h, src_w, ratio_h, ratio_w]) - return data - - def image_padding(self, im, value=0): - h, w, c = im.shape - im_pad = np.zeros((max(32, h), max(32, w), c), np.uint8) + value - im_pad[:h, :w, :] = im - return im_pad - - def resize_image_type1(self, img): - resize_h, resize_w = self.image_shape - ori_h, ori_w = img.shape[:2] # (h, w, c) - if self.keep_ratio is True: - resize_w = ori_w * resize_h / ori_h - N = math.ceil(resize_w / 32) - resize_w = N * 32 - ratio_h = float(resize_h) / ori_h - ratio_w = float(resize_w) / ori_w - img = cv2.resize(img, (int(resize_w), int(resize_h))) - # return img, np.array([ori_h, ori_w]) - return img, [ratio_h, ratio_w] - - def resize_image_type0(self, img): - """ - resize image to a size multiple of 32 which is required by the network - args: - img(array): array with shape [h, w, c] - return(tuple): - img, (ratio_h, ratio_w) - """ - limit_side_len = self.limit_side_len - h, w, c = img.shape - - # limit the max side - if self.limit_type == 'max': - if max(h, w) > limit_side_len: - if h > w: - ratio = float(limit_side_len) / h - else: - ratio = float(limit_side_len) / w - else: - ratio = 1. - elif self.limit_type == 'min': - if min(h, w) < limit_side_len: - if h < w: - ratio = float(limit_side_len) / h - else: - ratio = float(limit_side_len) / w - else: - ratio = 1. - elif self.limit_type == 'resize_long': - ratio = float(limit_side_len) / max(h, w) - else: - raise Exception('not support limit type, image ') - resize_h = int(h * ratio) - resize_w = int(w * ratio) - - resize_h = max(int(round(resize_h / 32) * 32), 32) - resize_w = max(int(round(resize_w / 32) * 32), 32) - - try: - if int(resize_w) <= 0 or int(resize_h) <= 0: - return None, (None, None) - img = cv2.resize(img, (int(resize_w), int(resize_h))) - except BaseException: - logging.exception("{} {} {}".format(img.shape, resize_w, resize_h)) - sys.exit(0) - ratio_h = resize_h / float(h) - ratio_w = resize_w / float(w) - return img, [ratio_h, ratio_w] - - def resize_image_type2(self, img): - h, w, _ = img.shape - - resize_w = w - resize_h = h - - if resize_h > resize_w: - ratio = float(self.resize_long) / resize_h - else: - ratio = float(self.resize_long) / resize_w - - resize_h = int(resize_h * ratio) - resize_w = int(resize_w * ratio) - - max_stride = 128 - resize_h = (resize_h + max_stride - 1) // max_stride * max_stride - resize_w = (resize_w + max_stride - 1) // max_stride * max_stride - img = cv2.resize(img, (int(resize_w), int(resize_h))) - ratio_h = resize_h / float(h) - ratio_w = resize_w / float(w) - - return img, [ratio_h, ratio_w] - - -class E2EResizeForTest: - def __init__(self, **kwargs): - super(E2EResizeForTest, self).__init__() - self.max_side_len = kwargs['max_side_len'] - self.valid_set = kwargs['valid_set'] - - def __call__(self, data): - img = data['image'] - src_h, src_w, _ = img.shape - if self.valid_set == 'totaltext': - im_resized, [ratio_h, ratio_w] = self.resize_image_for_totaltext( - img, max_side_len=self.max_side_len) - else: - im_resized, (ratio_h, ratio_w) = self.resize_image( - img, max_side_len=self.max_side_len) - data['image'] = im_resized - data['shape'] = np.array([src_h, src_w, ratio_h, ratio_w]) - return data - - def resize_image_for_totaltext(self, im, max_side_len=512): - h, w, _ = im.shape - resize_w = w - resize_h = h - ratio = 1.25 - if h * ratio > max_side_len: - ratio = float(max_side_len) / resize_h - resize_h = int(resize_h * ratio) - resize_w = int(resize_w * ratio) - - max_stride = 128 - resize_h = (resize_h + max_stride - 1) // max_stride * max_stride - resize_w = (resize_w + max_stride - 1) // max_stride * max_stride - im = cv2.resize(im, (int(resize_w), int(resize_h))) - ratio_h = resize_h / float(h) - ratio_w = resize_w / float(w) - return im, (ratio_h, ratio_w) - - def resize_image(self, im, max_side_len=512): - """ - resize image to a size multiple of max_stride which is required by the network - :param im: the resized image - :param max_side_len: limit of max image size to avoid out of memory in gpu - :return: the resized image and the resize ratio - """ - h, w, _ = im.shape - - resize_w = w - resize_h = h - - # Fix the longer side - if resize_h > resize_w: - ratio = float(max_side_len) / resize_h - else: - ratio = float(max_side_len) / resize_w - - resize_h = int(resize_h * ratio) - resize_w = int(resize_w * ratio) - - max_stride = 128 - resize_h = (resize_h + max_stride - 1) // max_stride * max_stride - resize_w = (resize_w + max_stride - 1) // max_stride * max_stride - im = cv2.resize(im, (int(resize_w), int(resize_h))) - ratio_h = resize_h / float(h) - ratio_w = resize_w / float(w) - - return im, (ratio_h, ratio_w) - - -class KieResize: - def __init__(self, **kwargs): - super(KieResize, self).__init__() - self.max_side, self.min_side = kwargs['img_scale'][0], kwargs[ - 'img_scale'][1] - - def __call__(self, data): - img = data['image'] - points = data['points'] - src_h, src_w, _ = img.shape - im_resized, scale_factor, [ratio_h, ratio_w - ], [new_h, new_w] = self.resize_image(img) - resize_points = self.resize_boxes(img, points, scale_factor) - data['ori_image'] = img - data['ori_boxes'] = points - data['points'] = resize_points - data['image'] = im_resized - data['shape'] = np.array([new_h, new_w]) - return data - - def resize_image(self, img): - norm_img = np.zeros([1024, 1024, 3], dtype='float32') - scale = [512, 1024] - h, w = img.shape[:2] - max_long_edge = max(scale) - max_short_edge = min(scale) - scale_factor = min(max_long_edge / max(h, w), - max_short_edge / min(h, w)) - resize_w, resize_h = int(w * float(scale_factor) + 0.5), int(h * float( - scale_factor) + 0.5) - max_stride = 32 - resize_h = (resize_h + max_stride - 1) // max_stride * max_stride - resize_w = (resize_w + max_stride - 1) // max_stride * max_stride - im = cv2.resize(img, (resize_w, resize_h)) - new_h, new_w = im.shape[:2] - w_scale = new_w / w - h_scale = new_h / h - scale_factor = np.array( - [w_scale, h_scale, w_scale, h_scale], dtype=np.float32) - norm_img[:new_h, :new_w, :] = im - return norm_img, scale_factor, [h_scale, w_scale], [new_h, new_w] - - def resize_boxes(self, im, points, scale_factor): - points = points * scale_factor - img_shape = im.shape[:2] - points[:, 0::2] = np.clip(points[:, 0::2], 0, img_shape[1]) - points[:, 1::2] = np.clip(points[:, 1::2], 0, img_shape[0]) - return points - - -class SRResize: - def __init__(self, - imgH=32, - imgW=128, - down_sample_scale=4, - keep_ratio=False, - min_ratio=1, - mask=False, - infer_mode=False, - **kwargs): - self.imgH = imgH - self.imgW = imgW - self.keep_ratio = keep_ratio - self.min_ratio = min_ratio - self.down_sample_scale = down_sample_scale - self.mask = mask - self.infer_mode = infer_mode - - def __call__(self, data): - imgH = self.imgH - imgW = self.imgW - images_lr = data["image_lr"] - transform2 = ResizeNormalize( - (imgW // self.down_sample_scale, imgH // self.down_sample_scale)) - images_lr = transform2(images_lr) - data["img_lr"] = images_lr - if self.infer_mode: - return data - - images_HR = data["image_hr"] - _label_strs = data["label"] - transform = ResizeNormalize((imgW, imgH)) - images_HR = transform(images_HR) - data["img_hr"] = images_HR - return data - - -class ResizeNormalize: - def __init__(self, size, interpolation=Image.BICUBIC): - self.size = size - self.interpolation = interpolation - - def __call__(self, img): - img = img.resize(self.size, self.interpolation) - img_numpy = np.array(img).astype("float32") - img_numpy = img_numpy.transpose((2, 0, 1)) / 255 - return img_numpy - - -class GrayImageChannelFormat: - """ - format gray scale image's channel: (3,h,w) -> (1,h,w) - Args: - inverse: inverse gray image - """ - - def __init__(self, inverse=False, **kwargs): - self.inverse = inverse - - def __call__(self, data): - img = data['image'] - img_single_channel = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) - img_expanded = np.expand_dims(img_single_channel, 0) - - if self.inverse: - data['image'] = np.abs(img_expanded - 1) - else: - data['image'] = img_expanded - - data['src_image'] = img - return data - - -class Permute: - """permute image - Args: - to_bgr (bool): whether convert RGB to BGR - channel_first (bool): whether convert HWC to CHW - """ - - def __init__(self, ): - super(Permute, self).__init__() - - def __call__(self, im, im_info): - """ - Args: - im (np.ndarray): image (np.ndarray) - im_info (dict): info of image - Returns: - im (np.ndarray): processed image (np.ndarray) - im_info (dict): info of processed image - """ - im = im.transpose((2, 0, 1)).copy() - return im, im_info - - -class PadStride: - """ padding image for model with FPN, instead PadBatch(pad_to_stride) in original config - Args: - stride (bool): model with FPN need image shape % stride == 0 - """ - - def __init__(self, stride=0): - self.coarsest_stride = stride - - def __call__(self, im, im_info): - """ - Args: - im (np.ndarray): image (np.ndarray) - im_info (dict): info of image - Returns: - im (np.ndarray): processed image (np.ndarray) - im_info (dict): info of processed image - """ - coarsest_stride = self.coarsest_stride - if coarsest_stride <= 0: - return im, im_info - im_c, im_h, im_w = im.shape - pad_h = int(np.ceil(float(im_h) / coarsest_stride) * coarsest_stride) - pad_w = int(np.ceil(float(im_w) / coarsest_stride) * coarsest_stride) - padding_im = np.zeros((im_c, pad_h, pad_w), dtype=np.float32) - padding_im[:, :im_h, :im_w] = im - return padding_im, im_info - - -def decode_image(im_file, im_info): - """read rgb image - Args: - im_file (str|np.ndarray): input can be image path or np.ndarray - im_info (dict): info of image - Returns: - im (np.ndarray): processed image (np.ndarray) - im_info (dict): info of processed image - """ - if isinstance(im_file, str): - with open(im_file, 'rb') as f: - im_read = f.read() - data = np.frombuffer(im_read, dtype='uint8') - im = cv2.imdecode(data, 1) # BGR mode, but need RGB mode - im = cv2.cvtColor(im, cv2.COLOR_BGR2RGB) - else: - im = im_file - im_info['im_shape'] = np.array(im.shape[:2], dtype=np.float32) - im_info['scale_factor'] = np.array([1., 1.], dtype=np.float32) - return im, im_info - - -def preprocess(im, preprocess_ops): - # process image by preprocess_ops - im_info = { - 'scale_factor': np.array( - [1., 1.], dtype=np.float32), - 'im_shape': None, - } - im, im_info = decode_image(im, im_info) - for operator in preprocess_ops: - im, im_info = operator(im, im_info) - return im, im_info - - -def nms(bboxes, scores, iou_thresh): - import numpy as np - x1 = bboxes[:, 0] - y1 = bboxes[:, 1] - x2 = bboxes[:, 2] - y2 = bboxes[:, 3] - areas = (y2 - y1) * (x2 - x1) - - indices = [] - index = scores.argsort()[::-1] - while index.size > 0: - i = index[0] - indices.append(i) - x11 = np.maximum(x1[i], x1[index[1:]]) - y11 = np.maximum(y1[i], y1[index[1:]]) - x22 = np.minimum(x2[i], x2[index[1:]]) - y22 = np.minimum(y2[i], y2[index[1:]]) - w = np.maximum(0, x22 - x11 + 1) - h = np.maximum(0, y22 - y11 + 1) - overlaps = w * h - ious = overlaps / (areas[i] + areas[index[1:]] - overlaps) - idx = np.where(ious <= iou_thresh)[0] - index = index[idx + 1] - return indices - diff --git a/ocr/pdf_parser.py b/ocr/pdf_parser.py deleted file mode 100644 index 9a954a1..0000000 --- a/ocr/pdf_parser.py +++ /dev/null @@ -1,1319 +0,0 @@ -# -# Copyright 2025 The InfiniFlow Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -""" -简化的PDF解析器,只使用OCR处理PDF文档 - -从 RAGFlow 的 RAGFlowPdfParser 中提取OCR相关功能,移除了: -- 布局识别(Layout Recognition) -- 表格结构识别(Table Structure Recognition) -- 文本合并和语义分析 -- RAG相关功能 - -只保留: -- PDF转图片 -- OCR文本检测和识别 -- 基本的文本和位置信息返回 -""" -from pathlib import Path -from tkinter import Image -from rag.app.picture import vision_llm_chunk as picture_vision_llm_chunk -from pypdf import PdfReader as pdf2_read -from huggingface_hub import snapshot_download -import logging -import os -import random -import re -import sys -import threading -from copy import deepcopy -from io import BytesIO -from timeit import default_timer as timer - -import numpy as np -import pdfplumber -import trio -import xgboost as xgb -from agent import settings -from deepdoc.vision import AscendLayoutRecognizer, TableStructureRecognizer, Recognizer -from deepdoc.vision.layout_recognizer import LayoutRecognizer -from ocr.utils import get_project_base_directory -from rag.nlp import rag_tokenizer -from rag.prompts.generator import vision_llm_describe_prompt - -# 处理导入问题:支持直接运行和模块导入 -try: - _package = __package__ -except NameError: - _package = None - -if _package is None: - # 直接运行时,添加父目录到路径并使用绝对导入 - parent_dir = Path(__file__).parent.parent - if str(parent_dir) not in sys.path: - sys.path.insert(0, str(parent_dir)) - from ocr.config import PARALLEL_DEVICES - from ocr.ocr import OCR -else: - # 作为模块导入时使用相对导入 - from config import PARALLEL_DEVICES - from ocr import OCR - -LOCK_KEY_pdfplumber = "global_shared_lock_pdfplumber" -if LOCK_KEY_pdfplumber not in sys.modules: - sys.modules[LOCK_KEY_pdfplumber] = threading.Lock() - - -class SimplePdfParser: - def __init__(self, **kwargs): - """ - If you have trouble downloading HuggingFace models, -_^ this might help!! - - For Linux: - export HF_ENDPOINT=https://hf-mirror.com - - For Windows: - Good luck - ^_- - - """ - - self.ocr = OCR() - self.parallel_limiter = None - if PARALLEL_DEVICES > 1: - self.parallel_limiter = [trio.CapacityLimiter(1) for _ in range(PARALLEL_DEVICES)] - - layout_recognizer_type = os.getenv("LAYOUT_RECOGNIZER_TYPE", "onnx").lower() - if layout_recognizer_type not in ["onnx", "ascend"]: - raise RuntimeError("Unsupported layout recognizer type.") - - if hasattr(self, "model_speciess"): - recognizer_domain = "layout." + self.model_speciess - else: - recognizer_domain = "layout" - - if layout_recognizer_type == "ascend": - logging.debug("Using Ascend LayoutRecognizer") - self.layouter = AscendLayoutRecognizer(recognizer_domain) - else: # onnx - logging.debug("Using Onnx LayoutRecognizer") - self.layouter = LayoutRecognizer(recognizer_domain) - self.tbl_det = TableStructureRecognizer() - - self.updown_cnt_mdl = xgb.Booster() - if not settings.LIGHTEN: - try: - import torch.cuda - - if torch.cuda.is_available(): - self.updown_cnt_mdl.set_param({"device": "cuda"}) - except Exception: - logging.exception("RAGFlowPdfParser __init__") - try: - model_dir = os.path.join(get_project_base_directory(), "rag/res/deepdoc") - self.updown_cnt_mdl.load_model(os.path.join(model_dir, "updown_concat_xgb.model")) - except Exception: - model_dir = snapshot_download(repo_id="InfiniFlow/text_concat_xgb_v1.0", local_dir=os.path.join(get_project_base_directory(), "rag/res/deepdoc"), local_dir_use_symlinks=False) - self.updown_cnt_mdl.load_model(os.path.join(model_dir, "updown_concat_xgb.model")) - - self.page_from = 0 - self.column_num = 1 - - def __char_width(self, c): - return (c["x1"] - c["x0"]) // max(len(c["text"]), 1) - - def __height(self, c): - return c["bottom"] - c["top"] - - def _x_dis(self, a, b): - return min(abs(a["x1"] - b["x0"]), abs(a["x0"] - b["x1"]), abs(a["x0"] + a["x1"] - b["x0"] - b["x1"]) / 2) - - def _y_dis(self, a, b): - return (b["top"] + b["bottom"] - a["top"] - a["bottom"]) / 2 - - def _match_proj(self, b): - proj_patt = [ - r"第[零一二三四五六七八九十百]+章", - r"第[零一二三四五六七八九十百]+[条节]", - r"[零一二三四五六七八九十百]+[、是  ]", - r"[\((][零一二三四五六七八九十百]+[)\)]", - r"[\((][0-9]+[)\)]", - r"[0-9]+(、|\.[  ]|)|\.[^0-9./a-zA-Z_%><-]{4,})", - r"[0-9]+\.[0-9.]+(、|\.[  ])", - r"[⚫•➢①② ]", - ] - return any([re.match(p, b["text"]) for p in proj_patt]) - - def _updown_concat_features(self, up, down): - w = max(self.__char_width(up), self.__char_width(down)) - h = max(self.__height(up), self.__height(down)) - y_dis = self._y_dis(up, down) - LEN = 6 - tks_down = rag_tokenizer.tokenize(down["text"][:LEN]).split() - tks_up = rag_tokenizer.tokenize(up["text"][-LEN:]).split() - tks_all = up["text"][-LEN:].strip() + (" " if re.match(r"[a-zA-Z0-9]+", up["text"][-1] + down["text"][0]) else "") + down["text"][:LEN].strip() - tks_all = rag_tokenizer.tokenize(tks_all).split() - fea = [ - up.get("R", -1) == down.get("R", -1), - y_dis / h, - down["page_number"] - up["page_number"], - up["layout_type"] == down["layout_type"], - up["layout_type"] == "text", - down["layout_type"] == "text", - up["layout_type"] == "table", - down["layout_type"] == "table", - True if re.search(r"([。?!;!?;+))]|[a-z]\.)$", up["text"]) else False, - True if re.search(r"[,:‘“、0-9(+-]$", up["text"]) else False, - True if re.search(r"(^.?[/,?;:\],。;:’”?!》】)-])", down["text"]) else False, - True if re.match(r"[\((][^\(\)()]+[)\)]$", up["text"]) else False, - True if re.search(r"[,,][^。.]+$", up["text"]) else False, - True if re.search(r"[,,][^。.]+$", up["text"]) else False, - True if re.search(r"[\((][^\))]+$", up["text"]) and re.search(r"[\))]", down["text"]) else False, - self._match_proj(down), - True if re.match(r"[A-Z]", down["text"]) else False, - True if re.match(r"[A-Z]", up["text"][-1]) else False, - True if re.match(r"[a-z0-9]", up["text"][-1]) else False, - True if re.match(r"[0-9.%,-]+$", down["text"]) else False, - up["text"].strip()[-2:] == down["text"].strip()[-2:] if len(up["text"].strip()) > 1 and len(down["text"].strip()) > 1 else False, - up["x0"] > down["x1"], - abs(self.__height(up) - self.__height(down)) / min(self.__height(up), self.__height(down)), - self._x_dis(up, down) / max(w, 0.000001), - (len(up["text"]) - len(down["text"])) / max(len(up["text"]), len(down["text"])), - len(tks_all) - len(tks_up) - len(tks_down), - len(tks_down) - len(tks_up), - tks_down[-1] == tks_up[-1] if tks_down and tks_up else False, - max(down["in_row"], up["in_row"]), - abs(down["in_row"] - up["in_row"]), - len(tks_down) == 1 and rag_tokenizer.tag(tks_down[0]).find("n") >= 0, - len(tks_up) == 1 and rag_tokenizer.tag(tks_up[0]).find("n") >= 0, - ] - return fea - - @staticmethod - def sort_X_by_page(arr, threshold): - # sort using y1 first and then x1 - arr = sorted(arr, key=lambda r: (r["page_number"], r["x0"], r["top"])) - for i in range(len(arr) - 1): - for j in range(i, -1, -1): - # restore the order using th - if abs(arr[j + 1]["x0"] - arr[j]["x0"]) < threshold and arr[j + 1]["top"] < arr[j]["top"] and arr[j + 1]["page_number"] == arr[j]["page_number"]: - tmp = arr[j] - arr[j] = arr[j + 1] - arr[j + 1] = tmp - return arr - - def _has_color(self, o): - if o.get("ncs", "") == "DeviceGray": - if o["stroking_color"] and o["stroking_color"][0] == 1 and o["non_stroking_color"] and o["non_stroking_color"][0] == 1: - if re.match(r"[a-zT_\[\]\(\)-]+", o.get("text", "")): - return False - return True - - def _table_transformer_job(self, ZM): - logging.debug("Table processing...") - imgs, pos = [], [] - tbcnt = [0] - MARGIN = 10 - self.tb_cpns = [] - assert len(self.page_layout) == len(self.page_images) - for p, tbls in enumerate(self.page_layout): # for page - tbls = [f for f in tbls if f["type"] == "table"] - tbcnt.append(len(tbls)) - if not tbls: - continue - for tb in tbls: # for table - left, top, right, bott = tb["x0"] - MARGIN, tb["top"] - MARGIN, tb["x1"] + MARGIN, tb["bottom"] + MARGIN - left *= ZM - top *= ZM - right *= ZM - bott *= ZM - pos.append((left, top)) - imgs.append(self.page_images[p].crop((left, top, right, bott))) - - assert len(self.page_images) == len(tbcnt) - 1 - if not imgs: - return - recos = self.tbl_det(imgs) - tbcnt = np.cumsum(tbcnt) - for i in range(len(tbcnt) - 1): # for page - pg = [] - for j, tb_items in enumerate(recos[tbcnt[i] : tbcnt[i + 1]]): # for table - poss = pos[tbcnt[i] : tbcnt[i + 1]] - for it in tb_items: # for table components - it["x0"] = it["x0"] + poss[j][0] - it["x1"] = it["x1"] + poss[j][0] - it["top"] = it["top"] + poss[j][1] - it["bottom"] = it["bottom"] + poss[j][1] - for n in ["x0", "x1", "top", "bottom"]: - it[n] /= ZM - it["top"] += self.page_cum_height[i] - it["bottom"] += self.page_cum_height[i] - it["pn"] = i - it["layoutno"] = j - pg.append(it) - self.tb_cpns.extend(pg) - - def gather(kwd, fzy=10, ption=0.6): - eles = Recognizer.sort_Y_firstly([r for r in self.tb_cpns if re.match(kwd, r["label"])], fzy) - eles = Recognizer.layouts_cleanup(self.boxes, eles, 5, ption) - return Recognizer.sort_Y_firstly(eles, 0) - - # add R,H,C,SP tag to boxes within table layout - headers = gather(r".*header$") - rows = gather(r".* (row|header)") - spans = gather(r".*spanning") - clmns = sorted([r for r in self.tb_cpns if re.match(r"table column$", r["label"])], key=lambda x: (x["pn"], x["layoutno"], x["x0"])) - clmns = Recognizer.layouts_cleanup(self.boxes, clmns, 5, 0.5) - for b in self.boxes: - if b.get("layout_type", "") != "table": - continue - ii = Recognizer.find_overlapped_with_threshold(b, rows, thr=0.3) - if ii is not None: - b["R"] = ii - b["R_top"] = rows[ii]["top"] - b["R_bott"] = rows[ii]["bottom"] - - ii = Recognizer.find_overlapped_with_threshold(b, headers, thr=0.3) - if ii is not None: - b["H_top"] = headers[ii]["top"] - b["H_bott"] = headers[ii]["bottom"] - b["H_left"] = headers[ii]["x0"] - b["H_right"] = headers[ii]["x1"] - b["H"] = ii - - ii = Recognizer.find_horizontally_tightest_fit(b, clmns) - if ii is not None: - b["C"] = ii - b["C_left"] = clmns[ii]["x0"] - b["C_right"] = clmns[ii]["x1"] - - ii = Recognizer.find_overlapped_with_threshold(b, spans, thr=0.3) - if ii is not None: - b["H_top"] = spans[ii]["top"] - b["H_bott"] = spans[ii]["bottom"] - b["H_left"] = spans[ii]["x0"] - b["H_right"] = spans[ii]["x1"] - b["SP"] = ii - - def __ocr(self, pagenum, img, chars, ZM=3, device_id: int | None = None): - start = timer() - bxs = self.ocr.detect(np.array(img), device_id) - logging.info(f"__ocr detecting boxes of a image cost ({timer() - start}s)") - - start = timer() - if not bxs: - self.boxes.append([]) - return - bxs = [(line[0], line[1][0]) for line in bxs] - bxs = Recognizer.sort_Y_firstly( - [ - {"x0": b[0][0] / ZM, "x1": b[1][0] / ZM, "top": b[0][1] / ZM, "text": "", "txt": t, "bottom": b[-1][1] / ZM, "chars": [], "page_number": pagenum} - for b, t in bxs - if b[0][0] <= b[1][0] and b[0][1] <= b[-1][1] - ], - self.mean_height[pagenum - 1] / 3, - ) - - # merge chars in the same rect - for c in chars: - ii = Recognizer.find_overlapped(c, bxs) - if ii is None: - self.lefted_chars.append(c) - continue - ch = c["bottom"] - c["top"] - bh = bxs[ii]["bottom"] - bxs[ii]["top"] - if abs(ch - bh) / max(ch, bh) >= 0.7 and c["text"] != " ": - self.lefted_chars.append(c) - continue - bxs[ii]["chars"].append(c) - - for b in bxs: - if not b["chars"]: - del b["chars"] - continue - m_ht = np.mean([c["height"] for c in b["chars"]]) - for c in Recognizer.sort_Y_firstly(b["chars"], m_ht): - if c["text"] == " " and b["text"]: - if re.match(r"[0-9a-zA-Zа-яА-Я,.?;:!%%]", b["text"][-1]): - b["text"] += " " - else: - b["text"] += c["text"] - del b["chars"] - - logging.info(f"__ocr sorting {len(chars)} chars cost {timer() - start}s") - start = timer() - boxes_to_reg = [] - img_np = np.array(img) - for b in bxs: - if not b["text"]: - left, right, top, bott = b["x0"] * ZM, b["x1"] * ZM, b["top"] * ZM, b["bottom"] * ZM - b["box_image"] = self.ocr.get_rotate_crop_image(img_np, np.array([[left, top], [right, top], [right, bott], [left, bott]], dtype=np.float32)) - boxes_to_reg.append(b) - del b["txt"] - texts = self.ocr.recognize_batch([b["box_image"] for b in boxes_to_reg], device_id) - for i in range(len(boxes_to_reg)): - boxes_to_reg[i]["text"] = texts[i] - del boxes_to_reg[i]["box_image"] - logging.info(f"__ocr recognize {len(bxs)} boxes cost {timer() - start}s") - bxs = [b for b in bxs if b["text"]] - if self.mean_height[pagenum - 1] == 0: - self.mean_height[pagenum - 1] = np.median([b["bottom"] - b["top"] for b in bxs]) - self.boxes.append(bxs) - - def _layouts_rec(self, ZM, drop=True): - assert len(self.page_images) == len(self.boxes) - self.boxes, self.page_layout = self.layouter(self.page_images, self.boxes, ZM, drop=drop) - # cumlative Y - for i in range(len(self.boxes)): - self.boxes[i]["top"] += self.page_cum_height[self.boxes[i]["page_number"] - 1] - self.boxes[i]["bottom"] += self.page_cum_height[self.boxes[i]["page_number"] - 1] - - def _text_merge(self): - # merge adjusted boxes - bxs = self.boxes - - def end_with(b, txt): - txt = txt.strip() - tt = b.get("text", "").strip() - return tt and tt.find(txt) == len(tt) - len(txt) - - def start_with(b, txts): - tt = b.get("text", "").strip() - return tt and any([tt.find(t.strip()) == 0 for t in txts]) - - # horizontally merge adjacent box with the same layout - i = 0 - while i < len(bxs) - 1: - b = bxs[i] - b_ = bxs[i + 1] - if b.get("layoutno", "0") != b_.get("layoutno", "1") or b.get("layout_type", "") in ["table", "figure", "equation"]: - i += 1 - continue - if abs(self._y_dis(b, b_)) < self.mean_height[bxs[i]["page_number"] - 1] / 3: - # merge - bxs[i]["x1"] = b_["x1"] - bxs[i]["top"] = (b["top"] + b_["top"]) / 2 - bxs[i]["bottom"] = (b["bottom"] + b_["bottom"]) / 2 - bxs[i]["text"] += b_["text"] - bxs.pop(i + 1) - continue - i += 1 - continue - - dis_thr = 1 - dis = b["x1"] - b_["x0"] - if b.get("layout_type", "") != "text" or b_.get("layout_type", "") != "text": - if end_with(b, ",") or start_with(b_, "(,"): - dis_thr = -8 - else: - i += 1 - continue - - if abs(self._y_dis(b, b_)) < self.mean_height[bxs[i]["page_number"] - 1] / 5 and dis >= dis_thr and b["x1"] < b_["x1"]: - # merge - bxs[i]["x1"] = b_["x1"] - bxs[i]["top"] = (b["top"] + b_["top"]) / 2 - bxs[i]["bottom"] = (b["bottom"] + b_["bottom"]) / 2 - bxs[i]["text"] += b_["text"] - bxs.pop(i + 1) - continue - i += 1 - self.boxes = bxs - - def _naive_vertical_merge(self, zoomin=3): - import math - bxs = Recognizer.sort_Y_firstly(self.boxes, np.median(self.mean_height) / 3) - - column_width = np.median([b["x1"] - b["x0"] for b in self.boxes]) - if not column_width or math.isnan(column_width): - column_width = self.mean_width[0] - self.column_num = int(self.page_images[0].size[0] / zoomin / column_width) - if column_width < self.page_images[0].size[0] / zoomin / self.column_num: - logging.info("Multi-column................... {} {}".format(column_width, self.page_images[0].size[0] / zoomin / self.column_num)) - self.boxes = self.sort_X_by_page(self.boxes, column_width / self.column_num) - - i = 0 - while i + 1 < len(bxs): - b = bxs[i] - b_ = bxs[i + 1] - if b["page_number"] < b_["page_number"] and re.match(r"[0-9 •一—-]+$", b["text"]): - bxs.pop(i) - continue - if not b["text"].strip(): - bxs.pop(i) - continue - concatting_feats = [ - b["text"].strip()[-1] in ",;:'\",、‘“;:-", - len(b["text"].strip()) > 1 and b["text"].strip()[-2] in ",;:'\",‘“、;:", - b_["text"].strip() and b_["text"].strip()[0] in "。;?!?”)),,、:", - ] - # features for not concating - feats = [ - b.get("layoutno", 0) != b_.get("layoutno", 0), - b["text"].strip()[-1] in "。?!?", - self.is_english and b["text"].strip()[-1] in ".!?", - b["page_number"] == b_["page_number"] and b_["top"] - b["bottom"] > self.mean_height[b["page_number"] - 1] * 1.5, - b["page_number"] < b_["page_number"] and abs(b["x0"] - b_["x0"]) > self.mean_width[b["page_number"] - 1] * 4, - ] - # split features - detach_feats = [b["x1"] < b_["x0"], b["x0"] > b_["x1"]] - if (any(feats) and not any(concatting_feats)) or any(detach_feats): - logging.debug( - "{} {} {} {}".format( - b["text"], - b_["text"], - any(feats), - any(concatting_feats), - ) - ) - i += 1 - continue - # merge up and down - b["bottom"] = b_["bottom"] - b["text"] += b_["text"] - b["x0"] = min(b["x0"], b_["x0"]) - b["x1"] = max(b["x1"], b_["x1"]) - bxs.pop(i + 1) - self.boxes = bxs - - def _concat_downward(self, concat_between_pages=True): - self.boxes = Recognizer.sort_Y_firstly(self.boxes, 0) - return - - # count boxes in the same row as a feature - for i in range(len(self.boxes)): - mh = self.mean_height[self.boxes[i]["page_number"] - 1] - self.boxes[i]["in_row"] = 0 - j = max(0, i - 12) - while j < min(i + 12, len(self.boxes)): - if j == i: - j += 1 - continue - ydis = self._y_dis(self.boxes[i], self.boxes[j]) / mh - if abs(ydis) < 1: - self.boxes[i]["in_row"] += 1 - elif ydis > 0: - break - j += 1 - - # concat between rows - boxes = deepcopy(self.boxes) - blocks = [] - while boxes: - chunks = [] - - def dfs(up, dp): - chunks.append(up) - i = dp - while i < min(dp + 12, len(boxes)): - ydis = self._y_dis(up, boxes[i]) - smpg = up["page_number"] == boxes[i]["page_number"] - mh = self.mean_height[up["page_number"] - 1] - mw = self.mean_width[up["page_number"] - 1] - if smpg and ydis > mh * 4: - break - if not smpg and ydis > mh * 16: - break - down = boxes[i] - if not concat_between_pages and down["page_number"] > up["page_number"]: - break - - if up.get("R", "") != down.get("R", "") and up["text"][-1] != ",": - i += 1 - continue - - if re.match(r"[0-9]{2,3}/[0-9]{3}$", up["text"]) or re.match(r"[0-9]{2,3}/[0-9]{3}$", down["text"]) or not down["text"].strip(): - i += 1 - continue - - if not down["text"].strip() or not up["text"].strip(): - i += 1 - continue - - if up["x1"] < down["x0"] - 10 * mw or up["x0"] > down["x1"] + 10 * mw: - i += 1 - continue - - if i - dp < 5 and up.get("layout_type") == "text": - if up.get("layoutno", "1") == down.get("layoutno", "2"): - dfs(down, i + 1) - boxes.pop(i) - return - i += 1 - continue - - fea = self._updown_concat_features(up, down) - if self.updown_cnt_mdl.predict(xgb.DMatrix([fea]))[0] <= 0.5: - i += 1 - continue - dfs(down, i + 1) - boxes.pop(i) - return - - dfs(boxes[0], 1) - boxes.pop(0) - if chunks: - blocks.append(chunks) - - # concat within each block - boxes = [] - for b in blocks: - if len(b) == 1: - boxes.append(b[0]) - continue - t = b[0] - for c in b[1:]: - t["text"] = t["text"].strip() - c["text"] = c["text"].strip() - if not c["text"]: - continue - if t["text"] and re.match(r"[0-9\.a-zA-Z]+$", t["text"][-1] + c["text"][-1]): - t["text"] += " " - t["text"] += c["text"] - t["x0"] = min(t["x0"], c["x0"]) - t["x1"] = max(t["x1"], c["x1"]) - t["page_number"] = min(t["page_number"], c["page_number"]) - t["bottom"] = c["bottom"] - if not t["layout_type"] and c["layout_type"]: - t["layout_type"] = c["layout_type"] - boxes.append(t) - - self.boxes = Recognizer.sort_Y_firstly(boxes, 0) - - def _filter_forpages(self): - if not self.boxes: - return - findit = False - i = 0 - while i < len(self.boxes): - if not re.match(r"(contents|目录|目次|table of contents|致谢|acknowledge)$", re.sub(r"( | |\u3000)+", "", self.boxes[i]["text"].lower())): - i += 1 - continue - findit = True - eng = re.match(r"[0-9a-zA-Z :'.-]{5,}", self.boxes[i]["text"].strip()) - self.boxes.pop(i) - if i >= len(self.boxes): - break - prefix = self.boxes[i]["text"].strip()[:3] if not eng else " ".join(self.boxes[i]["text"].strip().split()[:2]) - while not prefix: - self.boxes.pop(i) - if i >= len(self.boxes): - break - prefix = self.boxes[i]["text"].strip()[:3] if not eng else " ".join(self.boxes[i]["text"].strip().split()[:2]) - self.boxes.pop(i) - if i >= len(self.boxes) or not prefix: - break - for j in range(i, min(i + 128, len(self.boxes))): - if not re.match(prefix, self.boxes[j]["text"]): - continue - for k in range(i, j): - self.boxes.pop(i) - break - if findit: - return - - page_dirty = [0] * len(self.page_images) - for b in self.boxes: - if re.search(r"(··|··|··)", b["text"]): - page_dirty[b["page_number"] - 1] += 1 - page_dirty = set([i + 1 for i, t in enumerate(page_dirty) if t > 3]) - if not page_dirty: - return - i = 0 - while i < len(self.boxes): - if self.boxes[i]["page_number"] in page_dirty: - self.boxes.pop(i) - continue - i += 1 - - def _merge_with_same_bullet(self): - i = 0 - while i + 1 < len(self.boxes): - b = self.boxes[i] - b_ = self.boxes[i + 1] - if not b["text"].strip(): - self.boxes.pop(i) - continue - if not b_["text"].strip(): - self.boxes.pop(i + 1) - continue - - if ( - b["text"].strip()[0] != b_["text"].strip()[0] - or b["text"].strip()[0].lower() in set("qwertyuopasdfghjklzxcvbnm") - or rag_tokenizer.is_chinese(b["text"].strip()[0]) - or b["top"] > b_["bottom"] - ): - i += 1 - continue - b_["text"] = b["text"] + "\n" + b_["text"] - b_["x0"] = min(b["x0"], b_["x0"]) - b_["x1"] = max(b["x1"], b_["x1"]) - b_["top"] = b["top"] - self.boxes.pop(i) - - def _extract_table_figure(self, need_image, ZM, return_html, need_position, separate_tables_figures=False): - tables = {} - figures = {} - # extract figure and table boxes - i = 0 - lst_lout_no = "" - nomerge_lout_no = [] - while i < len(self.boxes): - if "layoutno" not in self.boxes[i]: - i += 1 - continue - lout_no = str(self.boxes[i]["page_number"]) + "-" + str(self.boxes[i]["layoutno"]) - if TableStructureRecognizer.is_caption(self.boxes[i]) or self.boxes[i]["layout_type"] in ["table caption", "title", "figure caption", "reference"]: - nomerge_lout_no.append(lst_lout_no) - if self.boxes[i]["layout_type"] == "table": - if re.match(r"(数据|资料|图表)*来源[:: ]", self.boxes[i]["text"]): - self.boxes.pop(i) - continue - if lout_no not in tables: - tables[lout_no] = [] - tables[lout_no].append(self.boxes[i]) - self.boxes.pop(i) - lst_lout_no = lout_no - continue - if need_image and self.boxes[i]["layout_type"] == "figure": - if re.match(r"(数据|资料|图表)*来源[:: ]", self.boxes[i]["text"]): - self.boxes.pop(i) - continue - if lout_no not in figures: - figures[lout_no] = [] - figures[lout_no].append(self.boxes[i]) - self.boxes.pop(i) - lst_lout_no = lout_no - continue - i += 1 - - # merge table on different pages - nomerge_lout_no = set(nomerge_lout_no) - tbls = sorted([(k, bxs) for k, bxs in tables.items()], key=lambda x: (x[1][0]["top"], x[1][0]["x0"])) - - i = len(tbls) - 1 - while i - 1 >= 0: - k0, bxs0 = tbls[i - 1] - k, bxs = tbls[i] - i -= 1 - if k0 in nomerge_lout_no: - continue - if bxs[0]["page_number"] == bxs0[0]["page_number"]: - continue - if bxs[0]["page_number"] - bxs0[0]["page_number"] > 1: - continue - mh = self.mean_height[bxs[0]["page_number"] - 1] - if self._y_dis(bxs0[-1], bxs[0]) > mh * 23: - continue - tables[k0].extend(tables[k]) - del tables[k] - - def x_overlapped(a, b): - return not any([a["x1"] < b["x0"], a["x0"] > b["x1"]]) - - # find captions and pop out - i = 0 - while i < len(self.boxes): - c = self.boxes[i] - # mh = self.mean_height[c["page_number"]-1] - if not TableStructureRecognizer.is_caption(c): - i += 1 - continue - - # find the nearest layouts - def nearest(tbls): - nonlocal c - mink = "" - minv = 1000000000 - for k, bxs in tbls.items(): - for b in bxs: - if b.get("layout_type", "").find("caption") >= 0: - continue - y_dis = self._y_dis(c, b) - x_dis = self._x_dis(c, b) if not x_overlapped(c, b) else 0 - dis = y_dis * y_dis + x_dis * x_dis - if dis < minv: - mink = k - minv = dis - return mink, minv - - tk, tv = nearest(tables) - fk, fv = nearest(figures) - # if min(tv, fv) > 2000: - # i += 1 - # continue - if tv < fv and tk: - tables[tk].insert(0, c) - logging.debug("TABLE:" + self.boxes[i]["text"] + "; Cap: " + tk) - elif fk: - figures[fk].insert(0, c) - logging.debug("FIGURE:" + self.boxes[i]["text"] + "; Cap: " + tk) - self.boxes.pop(i) - - def cropout(bxs, ltype, poss): - nonlocal ZM - pn = set([b["page_number"] - 1 for b in bxs]) - if len(pn) < 2: - pn = list(pn)[0] - ht = self.page_cum_height[pn] - b = {"x0": np.min([b["x0"] for b in bxs]), "top": np.min([b["top"] for b in bxs]) - ht, "x1": np.max([b["x1"] for b in bxs]), "bottom": np.max([b["bottom"] for b in bxs]) - ht} - louts = [layout for layout in self.page_layout[pn] if layout["type"] == ltype] - ii = Recognizer.find_overlapped(b, louts, naive=True) - if ii is not None: - b = louts[ii] - else: - logging.warning(f"Missing layout match: {pn + 1},%s" % (bxs[0].get("layoutno", ""))) - - left, top, right, bott = b["x0"], b["top"], b["x1"], b["bottom"] - if right < left: - right = left + 1 - poss.append((pn + self.page_from, left, right, top, bott)) - return self.page_images[pn].crop((left * ZM, top * ZM, right * ZM, bott * ZM)) - pn = {} - for b in bxs: - p = b["page_number"] - 1 - if p not in pn: - pn[p] = [] - pn[p].append(b) - pn = sorted(pn.items(), key=lambda x: x[0]) - imgs = [cropout(arr, ltype, poss) for p, arr in pn] - pic = Image.new("RGB", (int(np.max([i.size[0] for i in imgs])), int(np.sum([m.size[1] for m in imgs]))), (245, 245, 245)) - height = 0 - for img in imgs: - pic.paste(img, (0, int(height))) - height += img.size[1] - return pic - - res = [] - positions = [] - figure_results = [] - figure_positions = [] - # crop figure out and add caption - for k, bxs in figures.items(): - txt = "\n".join([b["text"] for b in bxs]) - if not txt: - continue - - poss = [] - - if separate_tables_figures: - figure_results.append((cropout(bxs, "figure", poss), [txt])) - figure_positions.append(poss) - else: - res.append((cropout(bxs, "figure", poss), [txt])) - positions.append(poss) - - for k, bxs in tables.items(): - if not bxs: - continue - bxs = Recognizer.sort_Y_firstly(bxs, np.mean([(b["bottom"] - b["top"]) / 2 for b in bxs])) - - poss = [] - - res.append((cropout(bxs, "table", poss), self.tbl_det.construct_table(bxs, html=return_html, is_english=self.is_english))) - positions.append(poss) - - if separate_tables_figures: - assert len(positions) + len(figure_positions) == len(res) + len(figure_results) - if need_position: - return list(zip(res, positions)), list(zip(figure_results, figure_positions)) - else: - return res, figure_results - else: - assert len(positions) == len(res) - if need_position: - return list(zip(res, positions)) - else: - return res - - def proj_match(self, line): - if len(line) <= 2: - return - if re.match(r"[0-9 ().,%%+/-]+$", line): - return False - for p, j in [ - (r"第[零一二三四五六七八九十百]+章", 1), - (r"第[零一二三四五六七八九十百]+[条节]", 2), - (r"[零一二三四五六七八九十百]+[、  ]", 3), - (r"[\((][零一二三四五六七八九十百]+[)\)]", 4), - (r"[0-9]+(、|\.[  ]|\.[^0-9])", 5), - (r"[0-9]+\.[0-9]+(、|[.  ]|[^0-9])", 6), - (r"[0-9]+\.[0-9]+\.[0-9]+(、|[  ]|[^0-9])", 7), - (r"[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+(、|[  ]|[^0-9])", 8), - (r".{,48}[::??]$", 9), - (r"[0-9]+)", 10), - (r"[\((][0-9]+[)\)]", 11), - (r"[零一二三四五六七八九十百]+是", 12), - (r"[⚫•➢✓]", 12), - ]: - if re.match(p, line): - return j - return - - def _line_tag(self, bx, ZM): - pn = [bx["page_number"]] - top = bx["top"] - self.page_cum_height[pn[0] - 1] - bott = bx["bottom"] - self.page_cum_height[pn[0] - 1] - page_images_cnt = len(self.page_images) - if pn[-1] - 1 >= page_images_cnt: - return "" - while bott * ZM > self.page_images[pn[-1] - 1].size[1]: - bott -= self.page_images[pn[-1] - 1].size[1] / ZM - pn.append(pn[-1] + 1) - if pn[-1] - 1 >= page_images_cnt: - return "" - - return "@@{}\t{:.1f}\t{:.1f}\t{:.1f}\t{:.1f}##".format("-".join([str(p) for p in pn]), bx["x0"], bx["x1"], top, bott) - - def __filterout_scraps(self, boxes, ZM): - def width(b): - return b["x1"] - b["x0"] - - def height(b): - return b["bottom"] - b["top"] - - def usefull(b): - if b.get("layout_type"): - return True - if width(b) > self.page_images[b["page_number"] - 1].size[0] / ZM / 3: - return True - if b["bottom"] - b["top"] > self.mean_height[b["page_number"] - 1]: - return True - return False - - res = [] - while boxes: - lines = [] - widths = [] - pw = self.page_images[boxes[0]["page_number"] - 1].size[0] / ZM - mh = self.mean_height[boxes[0]["page_number"] - 1] - mj = self.proj_match(boxes[0]["text"]) or boxes[0].get("layout_type", "") == "title" - - def dfs(line, st): - nonlocal mh, pw, lines, widths - lines.append(line) - widths.append(width(line)) - mmj = self.proj_match(line["text"]) or line.get("layout_type", "") == "title" - for i in range(st + 1, min(st + 20, len(boxes))): - if (boxes[i]["page_number"] - line["page_number"]) > 0: - break - if not mmj and self._y_dis(line, boxes[i]) >= 3 * mh and height(line) < 1.5 * mh: - break - - if not usefull(boxes[i]): - continue - if mmj or (self._x_dis(boxes[i], line) < pw / 10): - # and abs(width(boxes[i])-width_mean)/max(width(boxes[i]),width_mean)<0.5): - # concat following - dfs(boxes[i], i) - boxes.pop(i) - break - - try: - if usefull(boxes[0]): - dfs(boxes[0], 0) - else: - logging.debug("WASTE: " + boxes[0]["text"]) - except Exception: - pass - boxes.pop(0) - mw = np.mean(widths) - if mj or mw / pw >= 0.35 or mw > 200: - res.append("\n".join([c["text"] + self._line_tag(c, ZM) for c in lines])) - else: - logging.debug("REMOVED: " + "<<".join([c["text"] for c in lines])) - - return "\n\n".join(res) - - @staticmethod - def total_page_number(fnm, binary=None): - try: - with sys.modules[LOCK_KEY_pdfplumber]: - pdf = pdfplumber.open(fnm) if not binary else pdfplumber.open(BytesIO(binary)) - total_page = len(pdf.pages) - pdf.close() - return total_page - except Exception: - logging.exception("total_page_number") - - def __images__(self, fnm, zoomin=3, page_from=0, page_to=299, callback=None): - self.lefted_chars = [] - self.mean_height = [] - self.mean_width = [] - self.boxes = [] - self.garbages = {} - self.page_cum_height = [0] - self.page_layout = [] - self.page_from = page_from - start = timer() - try: - with sys.modules[LOCK_KEY_pdfplumber]: - with pdfplumber.open(fnm) if isinstance(fnm, str) else pdfplumber.open(BytesIO(fnm)) as pdf: - self.pdf = pdf - self.page_images = [p.to_image(resolution=72 * zoomin, antialias=True).annotated for i, p in enumerate(self.pdf.pages[page_from:page_to])] - - try: - self.page_chars = [[c for c in page.dedupe_chars().chars if self._has_color(c)] for page in self.pdf.pages[page_from:page_to]] - except Exception as e: - logging.warning(f"Failed to extract characters for pages {page_from}-{page_to}: {str(e)}") - self.page_chars = [[] for _ in range(page_to - page_from)] # If failed to extract, using empty list instead. - - self.total_page = len(self.pdf.pages) - - except Exception: - logging.exception("RAGFlowPdfParser __images__") - logging.info(f"__images__ dedupe_chars cost {timer() - start}s") - - self.outlines = [] - try: - with pdf2_read(fnm if isinstance(fnm, str) else BytesIO(fnm)) as pdf: - self.pdf = pdf - - outlines = self.pdf.outline - - def dfs(arr, depth): - for a in arr: - if isinstance(a, dict): - self.outlines.append((a["/Title"], depth)) - continue - dfs(a, depth + 1) - - dfs(outlines, 0) - - except Exception as e: - logging.warning(f"Outlines exception: {e}") - - if not self.outlines: - logging.warning("Miss outlines") - - logging.debug("Images converted.") - self.is_english = [ - re.search(r"[a-zA-Z0-9,/¸;:'\[\]\(\)!@#$%^&*\"?<>._-]{30,}", "".join(random.choices([c["text"] for c in self.page_chars[i]], k=min(100, len(self.page_chars[i]))))) - for i in range(len(self.page_chars)) - ] - if sum([1 if e else 0 for e in self.is_english]) > len(self.page_images) / 2: - self.is_english = True - else: - self.is_english = False - - async def __img_ocr(i, id, img, chars, limiter): - j = 0 - while j + 1 < len(chars): - if ( - chars[j]["text"] - and chars[j + 1]["text"] - and re.match(r"[0-9a-zA-Z,.:;!%]+", chars[j]["text"] + chars[j + 1]["text"]) - and chars[j + 1]["x0"] - chars[j]["x1"] >= min(chars[j + 1]["width"], chars[j]["width"]) / 2 - ): - chars[j]["text"] += " " - j += 1 - - if limiter: - async with limiter: - await trio.to_thread.run_sync(lambda: self.__ocr(i + 1, img, chars, zoomin, id)) - else: - self.__ocr(i + 1, img, chars, zoomin, id) - - if callback and i % 6 == 5: - callback(prog=(i + 1) * 0.6 / len(self.page_images), msg="") - - async def __img_ocr_launcher(): - def __ocr_preprocess(): - chars = self.page_chars[i] if not self.is_english else [] - self.mean_height.append(np.median(sorted([c["height"] for c in chars])) if chars else 0) - self.mean_width.append(np.median(sorted([c["width"] for c in chars])) if chars else 8) - self.page_cum_height.append(img.size[1] / zoomin) - return chars - - if self.parallel_limiter: - async with trio.open_nursery() as nursery: - for i, img in enumerate(self.page_images): - chars = __ocr_preprocess() - - nursery.start_soon(__img_ocr, i, i % PARALLEL_DEVICES, img, chars, self.parallel_limiter[i % PARALLEL_DEVICES]) - await trio.sleep(0.1) - else: - for i, img in enumerate(self.page_images): - chars = __ocr_preprocess() - await __img_ocr(i, 0, img, chars, None) - - start = timer() - - trio.run(__img_ocr_launcher) - - logging.info(f"__images__ {len(self.page_images)} pages cost {timer() - start}s") - - if not self.is_english and not any([c for c in self.page_chars]) and self.boxes: - bxes = [b for bxs in self.boxes for b in bxs] - self.is_english = re.search(r"[\na-zA-Z0-9,/¸;:'\[\]\(\)!@#$%^&*\"?<>._-]{30,}", "".join([b["text"] for b in random.choices(bxes, k=min(30, len(bxes)))])) - - logging.debug("Is it English:", self.is_english) - - self.page_cum_height = np.cumsum(self.page_cum_height) - assert len(self.page_cum_height) == len(self.page_images) + 1 - if len(self.boxes) == 0 and zoomin < 9: - self.__images__(fnm, zoomin * 3, page_from, page_to, callback) - - def __call__(self, fnm, need_image=True, zoomin=3, return_html=False): - self.__images__(fnm, zoomin) - self._layouts_rec(zoomin) - self._table_transformer_job(zoomin) - self._text_merge() - self._concat_downward() - self._filter_forpages() - tbls = self._extract_table_figure(need_image, zoomin, return_html, False) - return self.__filterout_scraps(deepcopy(self.boxes), zoomin), tbls - - def parse_into_bboxes(self, fnm, callback=None, zoomin=3): - start = timer() - self.__images__(fnm, zoomin) - if callback: - callback(0.40, "OCR finished ({:.2f}s)".format(timer() - start)) - - start = timer() - self._layouts_rec(zoomin) - if callback: - callback(0.63, "Layout analysis ({:.2f}s)".format(timer() - start)) - - start = timer() - self._table_transformer_job(zoomin) - if callback: - callback(0.83, "Table analysis ({:.2f}s)".format(timer() - start)) - - start = timer() - self._text_merge() - self._concat_downward() - self._naive_vertical_merge(zoomin) - if callback: - callback(0.92, "Text merged ({:.2f}s)".format(timer() - start)) - - start = timer() - tbls, figs = self._extract_table_figure(True, zoomin, True, True, True) - - def insert_table_figures(tbls_or_figs, layout_type): - def min_rectangle_distance(rect1, rect2): - import math - pn1, left1, right1, top1, bottom1 = rect1 - pn2, left2, right2, top2, bottom2 = rect2 - if right1 >= left2 and right2 >= left1 and bottom1 >= top2 and bottom2 >= top1: - return 0 - if right1 < left2: - dx = left2 - right1 - elif right2 < left1: - dx = left1 - right2 - else: - dx = 0 - if bottom1 < top2: - dy = top2 - bottom1 - elif bottom2 < top1: - dy = top1 - bottom2 - else: - dy = 0 - return math.sqrt(dx*dx + dy*dy)# + (pn2-pn1)*10000 - - for (img, txt), poss in tbls_or_figs: - bboxes = [(i, (b["page_number"], b["x0"], b["x1"], b["top"], b["bottom"])) for i, b in enumerate(self.boxes)] - dists = [(min_rectangle_distance((pn, left, right, top+self.page_cum_height[pn], bott+self.page_cum_height[pn]), rect),i) for i, rect in bboxes for pn, left, right, top, bott in poss] - min_i = np.argmin(dists, axis=0)[0] - min_i, rect = bboxes[dists[min_i][-1]] - if isinstance(txt, list): - txt = "\n".join(txt) - pn, left, right, top, bott = poss[0] - if self.boxes[min_i]["bottom"] < top+self.page_cum_height[pn]: - min_i += 1 - self.boxes.insert(min_i, { - "page_number": pn+1, "x0": left, "x1": right, "top": top+self.page_cum_height[pn], "bottom": bott+self.page_cum_height[pn], "layout_type": layout_type, "text": txt, "image": img, - "positions": [[pn+1, int(left), int(right), int(top), int(bott)]] - }) - - for b in self.boxes: - b["position_tag"] = self._line_tag(b, zoomin) - b["image"] = self.crop(b["position_tag"], zoomin) - b["positions"] = [[pos[0][-1]+1, *pos[1:]] for pos in SimplePdfParser.extract_positions(b["position_tag"])] - - insert_table_figures(tbls, "table") - insert_table_figures(figs, "figure") - if callback: - callback(1, "Structured ({:.2f}s)".format(timer() - start)) - return deepcopy(self.boxes) - - @staticmethod - def remove_tag(txt): - return re.sub(r"@@[\t0-9.-]+?##", "", txt) - - @staticmethod - def extract_positions(txt): - poss = [] - for tag in re.findall(r"@@[0-9-]+\t[0-9.\t]+##", txt): - pn, left, right, top, bottom = tag.strip("#").strip("@").split("\t") - left, right, top, bottom = float(left), float(right), float(top), float(bottom) - poss.append(([int(p) - 1 for p in pn.split("-")], left, right, top, bottom)) - return poss - - def crop(self, text, ZM=3, need_position=False): - imgs = [] - poss = self.extract_positions(text) - if not poss: - if need_position: - return None, None - return - - max_width = max(np.max([right - left for (_, left, right, _, _) in poss]), 6) - GAP = 6 - pos = poss[0] - poss.insert(0, ([pos[0][0]], pos[1], pos[2], max(0, pos[3] - 120), max(pos[3] - GAP, 0))) - pos = poss[-1] - poss.append(([pos[0][-1]], pos[1], pos[2], min(self.page_images[pos[0][-1]].size[1] / ZM, pos[4] + GAP), min(self.page_images[pos[0][-1]].size[1] / ZM, pos[4] + 120))) - - positions = [] - for ii, (pns, left, right, top, bottom) in enumerate(poss): - right = left + max_width - bottom *= ZM - for pn in pns[1:]: - bottom += self.page_images[pn - 1].size[1] - imgs.append(self.page_images[pns[0]].crop((left * ZM, top * ZM, right * ZM, min(bottom, self.page_images[pns[0]].size[1])))) - if 0 < ii < len(poss) - 1: - positions.append((pns[0] + self.page_from, left, right, top, min(bottom, self.page_images[pns[0]].size[1]) / ZM)) - bottom -= self.page_images[pns[0]].size[1] - for pn in pns[1:]: - imgs.append(self.page_images[pn].crop((left * ZM, 0, right * ZM, min(bottom, self.page_images[pn].size[1])))) - if 0 < ii < len(poss) - 1: - positions.append((pn + self.page_from, left, right, 0, min(bottom, self.page_images[pn].size[1]) / ZM)) - bottom -= self.page_images[pn].size[1] - - if not imgs: - if need_position: - return None, None - return - height = 0 - for img in imgs: - height += img.size[1] + GAP - height = int(height) - width = int(np.max([i.size[0] for i in imgs])) - pic = Image.new("RGB", (width, height), (245, 245, 245)) - height = 0 - for ii, img in enumerate(imgs): - if ii == 0 or ii + 1 == len(imgs): - img = img.convert("RGBA") - overlay = Image.new("RGBA", img.size, (0, 0, 0, 0)) - overlay.putalpha(128) - img = Image.alpha_composite(img, overlay).convert("RGB") - pic.paste(img, (0, int(height))) - height += img.size[1] + GAP - - if need_position: - return pic, positions - return pic - - def get_position(self, bx, ZM): - poss = [] - pn = bx["page_number"] - top = bx["top"] - self.page_cum_height[pn - 1] - bott = bx["bottom"] - self.page_cum_height[pn - 1] - poss.append((pn, bx["x0"], bx["x1"], top, min(bott, self.page_images[pn - 1].size[1] / ZM))) - while bott * ZM > self.page_images[pn - 1].size[1]: - bott -= self.page_images[pn - 1].size[1] / ZM - top = 0 - pn += 1 - poss.append((pn, bx["x0"], bx["x1"], top, min(bott, self.page_images[pn - 1].size[1] / ZM))) - return poss - - -class PlainParser: - def __call__(self, filename, from_page=0, to_page=100000, **kwargs): - self.outlines = [] - lines = [] - try: - self.pdf = pdf2_read(filename if isinstance(filename, str) else BytesIO(filename)) - for page in self.pdf.pages[from_page:to_page]: - lines.extend([t for t in page.extract_text().split("\n")]) - - outlines = self.pdf.outline - - def dfs(arr, depth): - for a in arr: - if isinstance(a, dict): - self.outlines.append((a["/Title"], depth)) - continue - dfs(a, depth + 1) - - dfs(outlines, 0) - except Exception: - logging.exception("Outlines exception") - if not self.outlines: - logging.warning("Miss outlines") - - return [(line, "") for line in lines], [] - - def crop(self, ck, need_position): - raise NotImplementedError - - @staticmethod - def remove_tag(txt): - raise NotImplementedError - - -class VisionParser(SimplePdfParser): - def __init__(self, vision_model, *args, **kwargs): - super().__init__(*args, **kwargs) - self.vision_model = vision_model - - def __images__(self, fnm, zoomin=3, page_from=0, page_to=299, callback=None): - try: - with sys.modules[LOCK_KEY_pdfplumber]: - self.pdf = pdfplumber.open(fnm) if isinstance(fnm, str) else pdfplumber.open(BytesIO(fnm)) - self.page_images = [p.to_image(resolution=72 * zoomin).annotated for i, p in enumerate(self.pdf.pages[page_from:page_to])] - self.total_page = len(self.pdf.pages) - except Exception: - self.page_images = None - self.total_page = 0 - logging.exception("VisionParser __images__") - - def __call__(self, filename, from_page=0, to_page=100000, **kwargs): - callback = kwargs.get("callback", lambda prog, msg: None) - zoomin = kwargs.get("zoomin", 3) - self.__images__(fnm=filename, zoomin=zoomin, page_from=from_page, page_to=to_page, callback=callback) - - total_pdf_pages = self.total_page - - start_page = max(0, from_page) - end_page = min(to_page, total_pdf_pages) - - all_docs = [] - - for idx, img_binary in enumerate(self.page_images or []): - pdf_page_num = idx # 0-based - if pdf_page_num < start_page or pdf_page_num >= end_page: - continue - - text = picture_vision_llm_chunk( - binary=img_binary, - vision_model=self.vision_model, - prompt=vision_llm_describe_prompt(page=pdf_page_num + 1), - callback=callback, - ) - if kwargs.get("callback"): - kwargs["callback"](idx * 1.0 / len(self.page_images), f"Processed: {idx + 1}/{len(self.page_images)}") - - if text: - width, height = self.page_images[idx].size - all_docs.append((text, f"{pdf_page_num + 1} 0 {width / zoomin} 0 {height / zoomin}")) - return all_docs, [] - - -# 向后兼容的别名 -PdfParser = SimplePdfParser diff --git a/ocr/postprocess.py b/ocr/postprocess.py deleted file mode 100644 index f4577f6..0000000 --- a/ocr/postprocess.py +++ /dev/null @@ -1,371 +0,0 @@ -# -# Copyright 2025 The InfiniFlow Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -import copy -import re -import numpy as np -import cv2 -from shapely.geometry import Polygon -import pyclipper - - -def build_post_process(config, global_config=None): - support_dict = {'DBPostProcess': DBPostProcess, 'CTCLabelDecode': CTCLabelDecode} - - config = copy.deepcopy(config) - module_name = config.pop('name') - if module_name == "None": - return - if global_config is not None: - config.update(global_config) - module_class = support_dict.get(module_name) - if module_class is None: - raise ValueError( - 'post process only support {}'.format(list(support_dict))) - return module_class(**config) - - -class DBPostProcess: - """ - The post process for Differentiable Binarization (DB). - """ - - def __init__(self, - thresh=0.3, - box_thresh=0.7, - max_candidates=1000, - unclip_ratio=2.0, - use_dilation=False, - score_mode="fast", - box_type='quad', - **kwargs): - self.thresh = thresh - self.box_thresh = box_thresh - self.max_candidates = max_candidates - self.unclip_ratio = unclip_ratio - self.min_size = 3 - self.score_mode = score_mode - self.box_type = box_type - assert score_mode in [ - "slow", "fast" - ], "Score mode must be in [slow, fast] but got: {}".format(score_mode) - - self.dilation_kernel = None if not use_dilation else np.array( - [[1, 1], [1, 1]]) - - def polygons_from_bitmap(self, pred, _bitmap, dest_width, dest_height): - ''' - _bitmap: single map with shape (1, H, W), - whose values are binarized as {0, 1} - ''' - - bitmap = _bitmap - height, width = bitmap.shape - - boxes = [] - scores = [] - - contours, _ = cv2.findContours((bitmap * 255).astype(np.uint8), - cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) - - for contour in contours[:self.max_candidates]: - epsilon = 0.002 * cv2.arcLength(contour, True) - approx = cv2.approxPolyDP(contour, epsilon, True) - points = approx.reshape((-1, 2)) - if points.shape[0] < 4: - continue - - score = self.box_score_fast(pred, points.reshape(-1, 2)) - if self.box_thresh > score: - continue - - if points.shape[0] > 2: - box = self.unclip(points, self.unclip_ratio) - if len(box) > 1: - continue - else: - continue - box = box.reshape(-1, 2) - - _, sside = self.get_mini_boxes(box.reshape((-1, 1, 2))) - if sside < self.min_size + 2: - continue - - box = np.array(box) - box[:, 0] = np.clip( - np.round(box[:, 0] / width * dest_width), 0, dest_width) - box[:, 1] = np.clip( - np.round(box[:, 1] / height * dest_height), 0, dest_height) - boxes.append(box.tolist()) - scores.append(score) - return boxes, scores - - def boxes_from_bitmap(self, pred, _bitmap, dest_width, dest_height): - ''' - _bitmap: single map with shape (1, H, W), - whose values are binarized as {0, 1} - ''' - - bitmap = _bitmap - height, width = bitmap.shape - - outs = cv2.findContours((bitmap * 255).astype(np.uint8), cv2.RETR_LIST, - cv2.CHAIN_APPROX_SIMPLE) - if len(outs) == 3: - _img, contours, _ = outs[0], outs[1], outs[2] - elif len(outs) == 2: - contours, _ = outs[0], outs[1] - - num_contours = min(len(contours), self.max_candidates) - - boxes = [] - scores = [] - for index in range(num_contours): - contour = contours[index] - points, sside = self.get_mini_boxes(contour) - if sside < self.min_size: - continue - points = np.array(points) - if self.score_mode == "fast": - score = self.box_score_fast(pred, points.reshape(-1, 2)) - else: - score = self.box_score_slow(pred, contour) - if self.box_thresh > score: - continue - - box = self.unclip(points, self.unclip_ratio).reshape(-1, 1, 2) - box, sside = self.get_mini_boxes(box) - if sside < self.min_size + 2: - continue - box = np.array(box) - - box[:, 0] = np.clip( - np.round(box[:, 0] / width * dest_width), 0, dest_width) - box[:, 1] = np.clip( - np.round(box[:, 1] / height * dest_height), 0, dest_height) - boxes.append(box.astype("int32")) - scores.append(score) - return np.array(boxes, dtype="int32"), scores - - def unclip(self, box, unclip_ratio): - poly = Polygon(box) - distance = poly.area * unclip_ratio / poly.length - offset = pyclipper.PyclipperOffset() - offset.AddPath(box, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON) - expanded = np.array(offset.Execute(distance)) - return expanded - - def get_mini_boxes(self, contour): - bounding_box = cv2.minAreaRect(contour) - points = sorted(list(cv2.boxPoints(bounding_box)), key=lambda x: x[0]) - - index_1, index_2, index_3, index_4 = 0, 1, 2, 3 - if points[1][1] > points[0][1]: - index_1 = 0 - index_4 = 1 - else: - index_1 = 1 - index_4 = 0 - if points[3][1] > points[2][1]: - index_2 = 2 - index_3 = 3 - else: - index_2 = 3 - index_3 = 2 - - box = [ - points[index_1], points[index_2], points[index_3], points[index_4] - ] - return box, min(bounding_box[1]) - - def box_score_fast(self, bitmap, _box): - ''' - box_score_fast: use bbox mean score as the mean score - ''' - h, w = bitmap.shape[:2] - box = _box.copy() - xmin = np.clip(np.floor(box[:, 0].min()).astype("int32"), 0, w - 1) - xmax = np.clip(np.ceil(box[:, 0].max()).astype("int32"), 0, w - 1) - ymin = np.clip(np.floor(box[:, 1].min()).astype("int32"), 0, h - 1) - ymax = np.clip(np.ceil(box[:, 1].max()).astype("int32"), 0, h - 1) - - mask = np.zeros((ymax - ymin + 1, xmax - xmin + 1), dtype=np.uint8) - box[:, 0] = box[:, 0] - xmin - box[:, 1] = box[:, 1] - ymin - cv2.fillPoly(mask, box.reshape(1, -1, 2).astype("int32"), 1) - return cv2.mean(bitmap[ymin:ymax + 1, xmin:xmax + 1], mask)[0] - - def box_score_slow(self, bitmap, contour): - ''' - box_score_slow: use polyon mean score as the mean score - ''' - h, w = bitmap.shape[:2] - contour = contour.copy() - contour = np.reshape(contour, (-1, 2)) - - xmin = np.clip(np.min(contour[:, 0]), 0, w - 1) - xmax = np.clip(np.max(contour[:, 0]), 0, w - 1) - ymin = np.clip(np.min(contour[:, 1]), 0, h - 1) - ymax = np.clip(np.max(contour[:, 1]), 0, h - 1) - - mask = np.zeros((ymax - ymin + 1, xmax - xmin + 1), dtype=np.uint8) - - contour[:, 0] = contour[:, 0] - xmin - contour[:, 1] = contour[:, 1] - ymin - - cv2.fillPoly(mask, contour.reshape(1, -1, 2).astype("int32"), 1) - return cv2.mean(bitmap[ymin:ymax + 1, xmin:xmax + 1], mask)[0] - - def __call__(self, outs_dict, shape_list): - pred = outs_dict['maps'] - if not isinstance(pred, np.ndarray): - pred = pred.numpy() - pred = pred[:, 0, :, :] - segmentation = pred > self.thresh - - boxes_batch = [] - for batch_index in range(pred.shape[0]): - src_h, src_w, ratio_h, ratio_w = shape_list[batch_index] - if self.dilation_kernel is not None: - mask = cv2.dilate( - np.array(segmentation[batch_index]).astype(np.uint8), - self.dilation_kernel) - else: - mask = segmentation[batch_index] - if self.box_type == 'poly': - boxes, scores = self.polygons_from_bitmap(pred[batch_index], - mask, src_w, src_h) - elif self.box_type == 'quad': - boxes, scores = self.boxes_from_bitmap(pred[batch_index], mask, - src_w, src_h) - else: - raise ValueError( - "box_type can only be one of ['quad', 'poly']") - - boxes_batch.append({'points': boxes}) - return boxes_batch - - -class BaseRecLabelDecode: - """ Convert between text-label and text-index """ - - def __init__(self, character_dict_path=None, use_space_char=False): - self.beg_str = "sos" - self.end_str = "eos" - self.reverse = False - self.character_str = [] - - if character_dict_path is None: - self.character_str = "0123456789abcdefghijklmnopqrstuvwxyz" - dict_character = list(self.character_str) - else: - with open(character_dict_path, "rb") as fin: - lines = fin.readlines() - for line in lines: - line = line.decode('utf-8').strip("\n").strip("\r\n") - self.character_str.append(line) - if use_space_char: - self.character_str.append(" ") - dict_character = list(self.character_str) - if 'arabic' in character_dict_path: - self.reverse = True - - dict_character = self.add_special_char(dict_character) - self.dict = {} - for i, char in enumerate(dict_character): - self.dict[char] = i - self.character = dict_character - - def pred_reverse(self, pred): - pred_re = [] - c_current = '' - for c in pred: - if not bool(re.search('[a-zA-Z0-9 :*./%+-]', c)): - if c_current != '': - pred_re.append(c_current) - pred_re.append(c) - c_current = '' - else: - c_current += c - if c_current != '': - pred_re.append(c_current) - - return ''.join(pred_re[::-1]) - - def add_special_char(self, dict_character): - return dict_character - - def decode(self, text_index, text_prob=None, is_remove_duplicate=False): - """ convert text-index into text-label. """ - result_list = [] - ignored_tokens = self.get_ignored_tokens() - batch_size = len(text_index) - for batch_idx in range(batch_size): - selection = np.ones(len(text_index[batch_idx]), dtype=bool) - if is_remove_duplicate: - selection[1:] = text_index[batch_idx][1:] != text_index[ - batch_idx][:-1] - for ignored_token in ignored_tokens: - selection &= text_index[batch_idx] != ignored_token - - char_list = [ - self.character[text_id] - for text_id in text_index[batch_idx][selection] - ] - if text_prob is not None: - conf_list = text_prob[batch_idx][selection] - else: - conf_list = [1] * len(selection) - if len(conf_list) == 0: - conf_list = [0] - - text = ''.join(char_list) - - if self.reverse: # for arabic rec - text = self.pred_reverse(text) - - result_list.append((text, np.mean(conf_list).tolist())) - return result_list - - def get_ignored_tokens(self): - return [0] # for ctc blank - - -class CTCLabelDecode(BaseRecLabelDecode): - """ Convert between text-label and text-index """ - - def __init__(self, character_dict_path=None, use_space_char=False, - **kwargs): - super(CTCLabelDecode, self).__init__(character_dict_path, - use_space_char) - - def __call__(self, preds, label=None, *args, **kwargs): - if isinstance(preds, tuple) or isinstance(preds, list): - preds = preds[-1] - if not isinstance(preds, np.ndarray): - preds = preds.numpy() - preds_idx = preds.argmax(axis=2) - preds_prob = preds.max(axis=2) - text = self.decode(preds_idx, preds_prob, is_remove_duplicate=True) - if label is None: - return text - label = self.decode(label) - return text, label - - def add_special_char(self, dict_character): - dict_character = ['blank'] + dict_character - return dict_character - diff --git a/ocr/requirements.txt b/ocr/requirements.txt deleted file mode 100644 index fedeaed..0000000 --- a/ocr/requirements.txt +++ /dev/null @@ -1,25 +0,0 @@ -# OCR PDF处理模块依赖 -# 核心依赖 -numpy>=1.21.0 -opencv-python>=4.5.0 -pillow>=8.0.0 -pdfplumber>=0.9.0 -onnxruntime>=1.12.0 -trio>=0.22.0 - -# 几何计算依赖 -shapely>=1.8.0 -pyclipper>=1.2.0 - -# Web框架依赖 -fastapi>=0.100.0 -uvicorn[standard]>=0.23.0 -pydantic>=2.0.0 - -# 模型下载依赖 -huggingface_hub>=0.16.0 - -# 可选依赖(用于GPU检测和加速) -# torch>=1.12.0 # 如果需要GPU支持,取消注释并安装 -# onnxruntime-gpu>=1.12.0 # 如果需要GPU支持,取消注释并安装 - diff --git a/ocr/service.py b/ocr/service.py deleted file mode 100644 index acfe810..0000000 --- a/ocr/service.py +++ /dev/null @@ -1,290 +0,0 @@ -# -# Copyright 2025 The InfiniFlow Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -""" -OCR 服务统一接口 -支持本地OCR模型和HTTP接口两种方式,可通过配置选择 -""" - -import logging -import os -from abc import ABC, abstractmethod -from typing import Optional, Callable, List, Tuple, Any - -logger = logging.getLogger(__name__) - - -class OCRService(ABC): - """OCR服务抽象接口""" - - @abstractmethod - async def remove_tag(self, text: str) -> str: - """ - 移除文本中的位置标签 - - Args: - text: 包含位置标签的文本 - - Returns: - 清理后的文本 - """ - pass - - @abstractmethod - def remove_tag_sync(self, text: str) -> str: - """ - 同步版本的 remove_tag(用于同步代码) - - Args: - text: 包含位置标签的文本 - - Returns: - 清理后的文本 - """ - pass - - @abstractmethod - async def extract_positions(self, text: str) -> List[Tuple[List[int], float, float, float, float]]: - """ - 从文本中提取位置信息 - - Args: - text: 包含位置标签的文本 - - Returns: - 位置信息列表,格式为 [(页码列表, left, right, top, bottom), ...] - """ - pass - - @abstractmethod - def extract_positions_sync(self, text: str) -> List[Tuple[List[int], float, float, float, float]]: - """ - 同步版本的 extract_positions(用于同步代码) - - Args: - text: 包含位置标签的文本 - - Returns: - 位置信息列表 - """ - pass - - @abstractmethod - async def parse_into_bboxes( - self, - pdf_bytes: bytes, - callback: Optional[Callable[[float, str], None]] = None, - zoomin: int = 3, - filename: str = "document.pdf" - ) -> List[dict]: - """ - 解析 PDF 并返回边界框 - - Args: - pdf_bytes: PDF 文件的二进制数据 - callback: 进度回调函数 (progress: float, message: str) -> None - zoomin: 图像放大倍数(1-5,默认为3) - filename: 文件名(仅用于日志) - - Returns: - 边界框列表 - """ - pass - - @abstractmethod - def parse_into_bboxes_sync( - self, - pdf_bytes: bytes, - callback: Optional[Callable[[float, str], None]] = None, - zoomin: int = 3, - filename: str = "document.pdf" - ) -> List[dict]: - """ - 同步版本的 parse_into_bboxes(用于同步代码) - - Args: - pdf_bytes: PDF 文件的二进制数据 - callback: 进度回调函数(注意:HTTP 调用中无法实时传递回调,此参数将被忽略) - zoomin: 图像放大倍数(1-5,默认为3) - filename: 文件名(仅用于日志) - - Returns: - 边界框列表 - """ - pass - - -class LocalOCRService(OCRService): - """本地OCR服务实现(直接调用本地OCR模型)""" - - def __init__(self, parser_instance=None): - """ - 初始化本地OCR服务 - - Args: - parser_instance: SimplePdfParser 实例,如果不提供则自动创建 - """ - if parser_instance is None: - from ocr import SimplePdfParser - from ocr.config import MODEL_DIR - logger.info(f"Initializing local OCR parser with model_dir={MODEL_DIR}") - self.parser = SimplePdfParser(model_dir=MODEL_DIR) - else: - self.parser = parser_instance - - async def remove_tag(self, text: str) -> str: - """使用本地解析器的静态方法移除标签""" - # SimplePdfParser.remove_tag 是静态方法,可以直接调用 - return self.parser.remove_tag(text) - - def remove_tag_sync(self, text: str) -> str: - """同步版本的 remove_tag""" - return self.parser.remove_tag(text) - - async def extract_positions(self, text: str) -> List[Tuple[List[int], float, float, float, float]]: - """使用本地解析器的静态方法提取位置""" - # SimplePdfParser.extract_positions 是静态方法,可以直接调用 - return self.parser.extract_positions(text) - - def extract_positions_sync(self, text: str) -> List[Tuple[List[int], float, float, float, float]]: - """同步版本的 extract_positions""" - return self.parser.extract_positions(text) - - async def parse_into_bboxes( - self, - pdf_bytes: bytes, - callback: Optional[Callable[[float, str], None]] = None, - zoomin: int = 3, - filename: str = "document.pdf" - ) -> List[dict]: - """使用本地解析器解析PDF""" - # 本地解析器可以直接接受BytesIO - import asyncio - from io import BytesIO - - # 在后台线程中运行同步方法 - loop = asyncio.get_event_loop() - bboxes = await loop.run_in_executor( - None, - lambda: self.parser.parse_into_bboxes(BytesIO(pdf_bytes), callback=callback, zoomin=zoomin) - ) - return bboxes - - def parse_into_bboxes_sync( - self, - pdf_bytes: bytes, - callback: Optional[Callable[[float, str], None]] = None, - zoomin: int = 3, - filename: str = "document.pdf" - ) -> List[dict]: - """同步版本的 parse_into_bboxes""" - from io import BytesIO - # 本地解析器可以直接接受BytesIO - return self.parser.parse_into_bboxes(BytesIO(pdf_bytes), callback=callback, zoomin=zoomin) - - -class HTTPOCRService(OCRService): - """HTTP OCR服务实现(通过HTTP接口调用OCR服务)""" - - def __init__(self, base_url: Optional[str] = None, timeout: float = 300.0): - """ - 初始化HTTP OCR服务 - - Args: - base_url: OCR 服务的基础 URL,如果不提供则从环境变量 OCR_SERVICE_URL 获取 - timeout: 请求超时时间(秒),默认 300 秒 - """ - from ocr.client import OCRClient - self.client = OCRClient(base_url=base_url, timeout=timeout) - - async def remove_tag(self, text: str) -> str: - """通过HTTP接口移除标签""" - return await self.client.remove_tag(text) - - def remove_tag_sync(self, text: str) -> str: - """同步版本的 remove_tag""" - return self.client.remove_tag_sync(text) - - async def extract_positions(self, text: str) -> List[Tuple[List[int], float, float, float, float]]: - """通过HTTP接口提取位置""" - return await self.client.extract_positions(text) - - def extract_positions_sync(self, text: str) -> List[Tuple[List[int], float, float, float, float]]: - """同步版本的 extract_positions""" - return self.client.extract_positions_sync(text) - - async def parse_into_bboxes( - self, - pdf_bytes: bytes, - callback: Optional[Callable[[float, str], None]] = None, - zoomin: int = 3, - filename: str = "document.pdf" - ) -> List[dict]: - """通过HTTP接口解析PDF""" - return await self.client.parse_into_bboxes(pdf_bytes, callback, zoomin, filename) - - def parse_into_bboxes_sync( - self, - pdf_bytes: bytes, - callback: Optional[Callable[[float, str], None]] = None, - zoomin: int = 3, - filename: str = "document.pdf" - ) -> List[dict]: - """同步版本的 parse_into_bboxes""" - return self.client.parse_into_bboxes_sync(pdf_bytes, callback, zoomin, filename) - - -# 全局服务实例(懒加载) -_global_service: Optional[OCRService] = None - - -def get_ocr_service() -> OCRService: - """ - 获取全局 OCR 服务实例(单例模式) - 根据环境变量 OCR_MODE 选择使用本地或HTTP方式: - - OCR_MODE=local 或未设置:使用本地OCR模型 - - OCR_MODE=http:使用HTTP接口 - - 也可以通过环境变量 OCR_SERVICE_URL 配置HTTP服务的地址(仅在OCR_MODE=http时生效) - - Returns: - OCRService 实例 - """ - global _global_service - if _global_service is None: - ocr_mode = os.getenv("OCR_MODE", "local").lower() - - if ocr_mode == "http": - base_url = os.getenv("OCR_SERVICE_URL", "http://localhost:8000/api/v1/ocr") - logger.info(f"Initializing HTTP OCR service with URL: {base_url}") - _global_service = HTTPOCRService(base_url=base_url) - else: - logger.info("Initializing local OCR service") - _global_service = LocalOCRService() - - return _global_service - - -# 为了向后兼容,保留 get_ocr_client 函数(但重定向到 get_ocr_service) -def get_ocr_client() -> OCRService: - """ - 获取OCR服务实例(向后兼容函数) - 建议使用 get_ocr_service() 替代 - - Returns: - OCRService 实例 - """ - return get_ocr_service() - diff --git a/ocr/utils.py b/ocr/utils.py deleted file mode 100644 index 90bd40b..0000000 --- a/ocr/utils.py +++ /dev/null @@ -1,40 +0,0 @@ -# -# Copyright 2025 The InfiniFlow Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -""" -OCR 模块工具函数 -""" -import os - - -def get_project_base_directory(*args): - """ - 获取项目根目录 - - Args: - *args: 可选的子路径 - - Returns: - str: 项目根目录路径 - """ - # 获取当前文件的目录 - current_dir = os.path.dirname(os.path.realpath(__file__)) - # 返回 ocr 模块的父目录(项目根目录) - base_dir = os.path.dirname(current_dir) - - if args: - return os.path.join(base_dir, *args) - return base_dir - diff --git a/pyproject.toml b/pyproject.toml index 2add3fe..e618534 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "ragflow" -version = "0.20.5" +version = "0.21.1" description = "[RAGFlow](https://ragflow.io/) is an open-source RAG (Retrieval-Augmented Generation) engine based on deep document understanding. It offers a streamlined RAG workflow for businesses of any scale, combining LLM (Large Language Models) to provide truthful question-answering capabilities, backed by well-founded citations from various complex formatted data." authors = [{ name = "Zhichang Yu", email = "yuzhichang@gmail.com" }] license-files = ["LICENSE"] @@ -30,7 +30,6 @@ dependencies = [ "demjson3==3.0.6", "discord-py==2.3.2", "duckduckgo-search>=7.2.0,<8.0.0", - "email-validator==2.3.0", "editdistance==0.8.1", "elastic-transport==8.12.0", "elasticsearch==8.12.1", @@ -41,14 +40,13 @@ dependencies = [ "flask-cors==5.0.0", "flask-login==0.6.3", "flask-session==0.8.0", - "fastapi==0.118.2", "google-search-results==2.4.2", "groq==0.9.0", "hanziconv==0.3.2", "html-text==0.6.2", - "httpx[socks]==0.27.2", + "httpx[socks]>=0.28.1,<0.29.0", "huggingface-hub>=0.25.0,<0.26.0", - "infinity-sdk==0.6.0.dev5", + "infinity-sdk==0.6.1", "infinity-emb>=0.0.66,<0.0.67", "itsdangerous==2.1.2", "json-repair==0.35.0", @@ -58,7 +56,7 @@ dependencies = [ "mistralai==0.4.2", "nltk==3.9.1", "numpy>=1.26.0,<2.0.0", - "ollama==0.2.1", + "ollama>=0.5.0", "onnxruntime==1.19.2; sys_platform == 'darwin' or platform_machine != 'x86_64'", "onnxruntime-gpu==1.19.2; sys_platform != 'darwin' and platform_machine == 'x86_64'", "openai>=1.45.0", @@ -104,7 +102,8 @@ dependencies = [ "tika==2.6.0", "tiktoken==0.7.0", "umap_learn==0.5.6", - "vertexai==1.64.0", + "vertexai==1.70.0", + "google-genai>=1.41.0,<2.0.0", "volcengine==1.0.194", "voyageai==0.2.3", "webdriver-manager==4.0.1", @@ -115,7 +114,7 @@ dependencies = [ "xpinyin==0.7.6", "yfinance==0.2.65", "zhipuai==2.0.1", - "google-generativeai>=0.8.1,<0.9.0", + "google-generativeai>=0.8.1,<0.9.0", # Needed for cv_model and embedding_model "python-docx>=1.1.2,<2.0.0", "pypdf2>=3.0.1,<4.0.0", "graspologic>=3.4.1,<4.0.0", @@ -135,6 +134,9 @@ dependencies = [ "litellm>=1.74.15.post1", "flask-mail>=0.10.0", "lark>=1.2.2", + "mammoth>=1.11.0", + "markdownify>=1.2.0", + "captcha>=0.7.1", ] [project.optional-dependencies] @@ -161,7 +163,7 @@ test = [ ] [[tool.uv.index]] -url = "https://mirrors.aliyun.com/pypi/simple" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" [tool.setuptools] packages = [ diff --git a/rag/app/book.py b/rag/app/book.py index e395434..319e7a2 100644 --- a/rag/app/book.py +++ b/rag/app/book.py @@ -20,11 +20,14 @@ import re from io import BytesIO from deepdoc.parser.utils import get_text +from rag.app import naive from rag.nlp import bullets_category, is_english,remove_contents_table, \ hierarchical_merge, make_colon_as_title, naive_merge, random_choices, tokenize_table, \ tokenize_chunks from rag.nlp import rag_tokenizer -from deepdoc.parser import PdfParser, DocxParser, PlainParser, HtmlParser +from deepdoc.parser import PdfParser, PlainParser, HtmlParser +from deepdoc.parser.figure_parser import vision_figure_parser_pdf_wrapper,vision_figure_parser_docx_wrapper +from PIL import Image class Pdf(PdfParser): @@ -81,13 +84,15 @@ def chunk(filename, binary=None, from_page=0, to_page=100000, sections, tbls = [], [] if re.search(r"\.docx$", filename, re.IGNORECASE): callback(0.1, "Start to parse.") - doc_parser = DocxParser() + doc_parser = naive.Docx() # TODO: table of contents need to be removed sections, tbls = doc_parser( - binary if binary else filename, from_page=from_page, to_page=to_page) + filename, binary=binary, from_page=from_page, to_page=to_page) remove_contents_table(sections, eng=is_english( random_choices([t for t, _ in sections], k=200))) - tbls = [((None, lns), None) for lns in tbls] + tbls=vision_figure_parser_docx_wrapper(sections=sections,tbls=tbls,callback=callback,**kwargs) + # tbls = [((None, lns), None) for lns in tbls] + sections=[(item[0],item[1] if item[1] is not None else "") for item in sections if not isinstance(item[1], Image.Image)] callback(0.8, "Finish parsing.") elif re.search(r"\.pdf$", filename, re.IGNORECASE): @@ -96,6 +101,7 @@ def chunk(filename, binary=None, from_page=0, to_page=100000, pdf_parser = PlainParser() sections, tbls = pdf_parser(filename if not binary else binary, from_page=from_page, to_page=to_page, callback=callback) + tbls=vision_figure_parser_pdf_wrapper(tbls=tbls,callback=callback,**kwargs) elif re.search(r"\.txt$", filename, re.IGNORECASE): callback(0.1, "Start to parse.") diff --git a/rag/app/manual.py b/rag/app/manual.py index 7fa395f..32a38ed 100644 --- a/rag/app/manual.py +++ b/rag/app/manual.py @@ -23,6 +23,7 @@ from io import BytesIO from rag.nlp import rag_tokenizer, tokenize, tokenize_table, bullets_category, title_frequency, tokenize_chunks, docx_question_level from rag.utils import num_tokens_from_string from deepdoc.parser import PdfParser, PlainParser, DocxParser +from deepdoc.parser.figure_parser import vision_figure_parser_pdf_wrapper,vision_figure_parser_docx_wrapper from docx import Document from PIL import Image @@ -252,7 +253,7 @@ def chunk(filename, binary=None, from_page=0, to_page=100000, tk_cnt = num_tokens_from_string(txt) if sec_id > -1: last_sid = sec_id - + tbls=vision_figure_parser_pdf_wrapper(tbls=tbls,callback=callback,**kwargs) res = tokenize_table(tbls, doc, eng) res.extend(tokenize_chunks(chunks, doc, eng, pdf_parser)) return res @@ -261,6 +262,7 @@ def chunk(filename, binary=None, from_page=0, to_page=100000, docx_parser = Docx() ti_list, tbls = docx_parser(filename, binary, from_page=0, to_page=10000, callback=callback) + tbls=vision_figure_parser_docx_wrapper(sections=ti_list,tbls=tbls,callback=callback,**kwargs) res = tokenize_table(tbls, doc, eng) for text, image in ti_list: d = copy.deepcopy(doc) diff --git a/rag/app/naive.py b/rag/app/naive.py index 9265ae7..a6c67b6 100644 --- a/rag/app/naive.py +++ b/rag/app/naive.py @@ -16,10 +16,10 @@ import logging import re +import os from functools import reduce from io import BytesIO from timeit import default_timer as timer - from docx import Document from docx.image.exceptions import InvalidImageStreamError, UnexpectedEndOfFileError, UnrecognizedImageError from docx.opc.pkgreader import _SerializedRelationships, _SerializedRelationship @@ -30,9 +30,11 @@ from tika import parser from api.db import LLMType from api.db.services.llm_service import LLMBundle +from api.utils.file_utils import extract_embed_file from deepdoc.parser import DocxParser, ExcelParser, HtmlParser, JsonParser, MarkdownElementExtractor, MarkdownParser, PdfParser, TxtParser -from deepdoc.parser.figure_parser import VisionFigureParser, vision_figure_parser_figure_data_wrapper +from deepdoc.parser.figure_parser import VisionFigureParser,vision_figure_parser_docx_wrapper,vision_figure_parser_pdf_wrapper from deepdoc.parser.pdf_parser import PlainParser, VisionParser +from deepdoc.parser.mineru_parser import MinerUParser from rag.nlp import concat_img, find_codec, naive_merge, naive_merge_with_images, naive_merge_docx, rag_tokenizer, tokenize_chunks, tokenize_chunks_with_images, tokenize_table @@ -256,6 +258,49 @@ class Docx(DocxParser): tbls.append(((None, html), "")) return new_line, tbls + def to_markdown(self, filename=None, binary=None, inline_images: bool = True): + """ + This function uses mammoth, licensed under the BSD 2-Clause License. + """ + + import base64 + import uuid + + import mammoth + from markdownify import markdownify + + docx_file = BytesIO(binary) if binary else open(filename, "rb") + + def _convert_image_to_base64(image): + try: + with image.open() as image_file: + image_bytes = image_file.read() + encoded = base64.b64encode(image_bytes).decode("utf-8") + base64_url = f"data:{image.content_type};base64,{encoded}" + + alt_name = "image" + alt_name = f"img_{uuid.uuid4().hex[:8]}" + + return {"src": base64_url, "alt": alt_name} + except Exception as e: + logging.warning(f"Failed to convert image to base64: {e}") + return {"src": "", "alt": "image"} + + try: + if inline_images: + result = mammoth.convert_to_html(docx_file, convert_image=mammoth.images.img_element(_convert_image_to_base64)) + else: + result = mammoth.convert_to_html(docx_file) + + html = result.value + + markdown_text = markdownify(html) + return markdown_text + + finally: + if not binary: + docx_file.close() + class Pdf(PdfParser): def __init__(self): @@ -285,7 +330,7 @@ class Pdf(PdfParser): callback(0.65, "Table analysis ({:.2f}s)".format(timer() - start)) start = timer() - self._text_merge() + self._text_merge(zoomin=zoomin) callback(0.67, "Text merged ({:.2f}s)".format(timer() - start)) if separate_tables_figures: @@ -297,6 +342,7 @@ class Pdf(PdfParser): tbls = self._extract_table_figure(True, zoomin, True, True) self._naive_vertical_merge() self._concat_downward() + self._final_reading_order_merge() # self._filter_forpages() logging.info("layouts cost: {}s".format(timer() - first_start)) return [(b["text"], self._line_tag(b, zoomin)) for b in self.boxes], tbls @@ -391,6 +437,7 @@ def chunk(filename, binary=None, from_page=0, to_page=100000, Successive text will be sliced into pieces using 'delimiter'. Next, these successive pieces are merge into chunks whose token number is no more than 'Max token number'. """ + is_english = lang.lower() == "english" # is_english(cks) parser_config = kwargs.get( @@ -404,27 +451,37 @@ def chunk(filename, binary=None, from_page=0, to_page=100000, res = [] pdf_parser = None section_images = None + + is_root = kwargs.get("is_root", True) + embed_res = [] + if is_root: + # Only extract embedded files at the root call + embeds = [] + if binary is not None: + embeds = extract_embed_file(binary) + else: + raise Exception("Embedding extraction from file path is not supported.") + + # Recursively chunk each embedded file and collect results + for embed_filename, embed_bytes in embeds: + try: + sub_res = chunk(embed_filename, binary=embed_bytes, lang=lang, callback=callback, is_root=False, **kwargs) or [] + embed_res.extend(sub_res) + except Exception as e: + if callback: + callback(0.05, f"Failed to chunk embed {embed_filename}: {e}") + continue + if re.search(r"\.docx$", filename, re.IGNORECASE): callback(0.1, "Start to parse.") - try: - vision_model = LLMBundle(kwargs["tenant_id"], LLMType.IMAGE2TEXT) - callback(0.15, "Visual model detected. Attempting to enhance figure extraction...") - except Exception: - vision_model = None + # fix "There is no item named 'word/NULL' in the archive", referring to https://github.com/python-openxml/python-docx/issues/1105#issuecomment-1298075246 _SerializedRelationships.load_from_xml = load_from_xml_v2 sections, tables = Docx()(filename, binary) - if vision_model: - figures_data = vision_figure_parser_figure_data_wrapper(sections) - try: - docx_vision_parser = VisionFigureParser(vision_model=vision_model, figures_data=figures_data, **kwargs) - boosted_figures = docx_vision_parser(callback=callback) - tables.extend(boosted_figures) - except Exception as e: - callback(0.6, f"Visual model error: {e}. Skipping figure parsing enhancement.") + tables=vision_figure_parser_docx_wrapper(sections=sections,tbls=tables,callback=callback,**kwargs) res = tokenize_table(tables, doc, is_english) callback(0.8, "Finish parsing.") @@ -437,10 +494,12 @@ def chunk(filename, binary=None, from_page=0, to_page=100000, "delimiter", "\n!?。;!?")) if kwargs.get("section_only", False): + chunks.extend(embed_res) return chunks res.extend(tokenize_chunks_with_images(chunks, doc, is_english, images)) logging.info("naive_merge({}): {}".format(filename, timer() - st)) + res.extend(embed_res) return res elif re.search(r"\.pdf$", filename, re.IGNORECASE): @@ -451,29 +510,28 @@ def chunk(filename, binary=None, from_page=0, to_page=100000, if layout_recognizer == "DeepDOC": pdf_parser = Pdf() - - try: - vision_model = LLMBundle(kwargs["tenant_id"], LLMType.IMAGE2TEXT) - callback(0.15, "Visual model detected. Attempting to enhance figure extraction...") - except Exception: - vision_model = None - - if vision_model: - sections, tables, figures = pdf_parser(filename if not binary else binary, from_page=from_page, to_page=to_page, callback=callback, separate_tables_figures=True) - callback(0.5, "Basic parsing complete. Proceeding with figure enhancement...") - try: - pdf_vision_parser = VisionFigureParser(vision_model=vision_model, figures_data=figures, **kwargs) - boosted_figures = pdf_vision_parser(callback=callback) - tables.extend(boosted_figures) - except Exception as e: - callback(0.6, f"Visual model error: {e}. Skipping figure parsing enhancement.") - tables.extend(figures) - else: - sections, tables = pdf_parser(filename if not binary else binary, from_page=from_page, to_page=to_page, callback=callback) + sections, tables = pdf_parser(filename if not binary else binary, from_page=from_page, to_page=to_page, callback=callback) + tables=vision_figure_parser_pdf_wrapper(tbls=tables,callback=callback,**kwargs) res = tokenize_table(tables, doc, is_english) callback(0.8, "Finish parsing.") + elif layout_recognizer == "MinerU": + mineru_executable = os.environ.get("MINERU_EXECUTABLE", "mineru") + pdf_parser = MinerUParser(mineru_path=mineru_executable) + if not pdf_parser.check_installation(): + callback(-1, "MinerU not found.") + return res + + sections, tables = pdf_parser.parse_pdf( + filepath=filename, + binary=binary, + callback=callback, + output_dir=os.environ.get("MINERU_OUTPUT_DIR", ""), + delete_output=bool(int(os.environ.get("MINERU_DELETE_OUTPUT", 1))), + ) + parser_config["chunk_token_num"] = 0 + callback(0.8, "Finish parsing.") else: if layout_recognizer == "Plain Text": pdf_parser = PlainParser() @@ -512,7 +570,7 @@ def chunk(filename, binary=None, from_page=0, to_page=100000, callback(0.2, "Visual model detected. Attempting to enhance figure extraction...") except Exception: vision_model = None - + if vision_model: # Process images for each section section_images = [] @@ -560,7 +618,6 @@ def chunk(filename, binary=None, from_page=0, to_page=100000, callback(0.8, f"tika.parser got empty content from {filename}.") logging.warning(f"tika.parser got empty content from {filename}.") return [] - else: raise NotImplementedError( "file type not supported yet(pdf, xlsx, doc, docx, txt supported)") @@ -577,6 +634,7 @@ def chunk(filename, binary=None, from_page=0, to_page=100000, "chunk_token_num", 128)), parser_config.get( "delimiter", "\n!?。;!?")) if kwargs.get("section_only", False): + chunks.extend(embed_res) return chunks res.extend(tokenize_chunks_with_images(chunks, doc, is_english, images)) @@ -586,11 +644,14 @@ def chunk(filename, binary=None, from_page=0, to_page=100000, "chunk_token_num", 128)), parser_config.get( "delimiter", "\n!?。;!?")) if kwargs.get("section_only", False): + chunks.extend(embed_res) return chunks res.extend(tokenize_chunks(chunks, doc, is_english, pdf_parser)) logging.info("naive_merge({}): {}".format(filename, timer() - st)) + if embed_res: + res.extend(embed_res) return res diff --git a/rag/app/one.py b/rag/app/one.py index 77c9645..bb86b80 100644 --- a/rag/app/one.py +++ b/rag/app/one.py @@ -23,6 +23,7 @@ from deepdoc.parser.utils import get_text from rag.app import naive from rag.nlp import rag_tokenizer, tokenize from deepdoc.parser import PdfParser, ExcelParser, PlainParser, HtmlParser +from deepdoc.parser.figure_parser import vision_figure_parser_pdf_wrapper,vision_figure_parser_docx_wrapper class Pdf(PdfParser): @@ -57,13 +58,8 @@ class Pdf(PdfParser): sections = [(b["text"], self.get_position(b, zoomin)) for i, b in enumerate(self.boxes)] - for (img, rows), poss in tbls: - if not rows: - continue - sections.append((rows if isinstance(rows, str) else rows[0], - [(p[0] + 1 - from_page, p[1], p[2], p[3], p[4]) for p in poss])) return [(txt, "") for txt, _ in sorted(sections, key=lambda x: ( - x[-1][0][0], x[-1][0][3], x[-1][0][1]))], None + x[-1][0][0], x[-1][0][3], x[-1][0][1]))], tbls def chunk(filename, binary=None, from_page=0, to_page=100000, @@ -80,6 +76,7 @@ def chunk(filename, binary=None, from_page=0, to_page=100000, if re.search(r"\.docx$", filename, re.IGNORECASE): callback(0.1, "Start to parse.") sections, tbls = naive.Docx()(filename, binary) + tbls=vision_figure_parser_docx_wrapper(sections=sections,tbls=tbls,callback=callback,**kwargs) sections = [s for s, _ in sections if s] for (_, html), _ in tbls: sections.append(html) @@ -89,8 +86,14 @@ def chunk(filename, binary=None, from_page=0, to_page=100000, pdf_parser = Pdf() if parser_config.get("layout_recognize", "DeepDOC") == "Plain Text": pdf_parser = PlainParser() - sections, _ = pdf_parser( + sections, tbls = pdf_parser( filename if not binary else binary, to_page=to_page, callback=callback) + tbls=vision_figure_parser_pdf_wrapper(tbls=tbls,callback=callback,**kwargs) + for (img, rows), poss in tbls: + if not rows: + continue + sections.append((rows if isinstance(rows, str) else rows[0], + [(p[0] + 1 - from_page, p[1], p[2], p[3], p[4]) for p in poss])) sections = [s for s, _ in sections if s] elif re.search(r"\.xlsx?$", filename, re.IGNORECASE): diff --git a/rag/app/paper.py b/rag/app/paper.py index c46f417..a8f29c8 100644 --- a/rag/app/paper.py +++ b/rag/app/paper.py @@ -18,12 +18,12 @@ import logging import copy import re +from deepdoc.parser.figure_parser import vision_figure_parser_pdf_wrapper from api.db import ParserType from rag.nlp import rag_tokenizer, tokenize, tokenize_table, add_positions, bullets_category, title_frequency, tokenize_chunks from deepdoc.parser import PdfParser, PlainParser import numpy as np - class Pdf(PdfParser): def __init__(self): self.model_speciess = ParserType.PAPER.value @@ -160,6 +160,9 @@ def chunk(filename, binary=None, from_page=0, to_page=100000, pdf_parser = Pdf() paper = pdf_parser(filename if not binary else binary, from_page=from_page, to_page=to_page, callback=callback) + tbls=paper["tables"] + tbls=vision_figure_parser_pdf_wrapper(tbls=tbls,callback=callback,**kwargs) + paper["tables"] = tbls else: raise NotImplementedError("file type not supported yet(pdf supported)") diff --git a/rag/app/picture.py b/rag/app/picture.py index a868cf4..bd2cb23 100644 --- a/rag/app/picture.py +++ b/rag/app/picture.py @@ -23,44 +23,62 @@ from PIL import Image from api.db import LLMType from api.db.services.llm_service import LLMBundle from deepdoc.vision import OCR -from rag.nlp import tokenize +from rag.nlp import rag_tokenizer, tokenize from rag.utils import clean_markdown_block -from rag.nlp import rag_tokenizer - ocr = OCR() +# Gemini supported MIME types +VIDEO_EXTS = [".mp4", ".mov", ".avi", ".flv", ".mpeg", ".mpg", ".webm", ".wmv", ".3gp", ".3gpp", ".mkv"] + def chunk(filename, binary, tenant_id, lang, callback=None, **kwargs): - img = Image.open(io.BytesIO(binary)).convert('RGB') doc = { "docnm_kwd": filename, "title_tks": rag_tokenizer.tokenize(re.sub(r"\.[a-zA-Z]+$", "", filename)), - "image": img, - "doc_type_kwd": "image" } - bxs = ocr(np.array(img)) - txt = "\n".join([t[0] for _, t in bxs if t[0]]) eng = lang.lower() == "english" - callback(0.4, "Finish OCR: (%s ...)" % txt[:12]) - if (eng and len(txt.split()) > 32) or len(txt) > 32: - tokenize(doc, txt, eng) - callback(0.8, "OCR results is too long to use CV LLM.") - return [doc] - try: - callback(0.4, "Use CV LLM to describe the picture.") - cv_mdl = LLMBundle(tenant_id, LLMType.IMAGE2TEXT, lang=lang) - img_binary = io.BytesIO() - img.save(img_binary, format='JPEG') - img_binary.seek(0) - ans = cv_mdl.describe(img_binary.read()) - callback(0.8, "CV LLM respond: %s ..." % ans[:32]) - txt += "\n" + ans - tokenize(doc, txt, eng) - return [doc] - except Exception as e: - callback(prog=-1, msg=str(e)) + if any(filename.lower().endswith(ext) for ext in VIDEO_EXTS): + try: + doc.update({"doc_type_kwd": "video"}) + cv_mdl = LLMBundle(tenant_id, llm_type=LLMType.IMAGE2TEXT, lang=lang) + ans = cv_mdl.chat(system="", history=[], gen_conf={}, video_bytes=binary, filename=filename) + callback(0.8, "CV LLM respond: %s ..." % ans[:32]) + ans += "\n" + ans + tokenize(doc, ans, eng) + return [doc] + except Exception as e: + callback(prog=-1, msg=str(e)) + else: + img = Image.open(io.BytesIO(binary)).convert("RGB") + doc.update( + { + "image": img, + "doc_type_kwd": "image", + } + ) + bxs = ocr(np.array(img)) + txt = "\n".join([t[0] for _, t in bxs if t[0]]) + callback(0.4, "Finish OCR: (%s ...)" % txt[:12]) + if (eng and len(txt.split()) > 32) or len(txt) > 32: + tokenize(doc, txt, eng) + callback(0.8, "OCR results is too long to use CV LLM.") + return [doc] + + try: + callback(0.4, "Use CV LLM to describe the picture.") + cv_mdl = LLMBundle(tenant_id, LLMType.IMAGE2TEXT, lang=lang) + img_binary = io.BytesIO() + img.save(img_binary, format="JPEG") + img_binary.seek(0) + ans = cv_mdl.describe(img_binary.read()) + callback(0.8, "CV LLM respond: %s ..." % ans[:32]) + txt += "\n" + ans + tokenize(doc, txt, eng) + return [doc] + except Exception as e: + callback(prog=-1, msg=str(e)) return [] @@ -79,7 +97,7 @@ def vision_llm_chunk(binary, vision_model, prompt=None, callback=None): try: with io.BytesIO() as img_binary: - img.save(img_binary, format='JPEG') + img.save(img_binary, format="JPEG") img_binary.seek(0) ans = clean_markdown_block(vision_model.describe_with_prompt(img_binary.read(), prompt)) txt += "\n" + ans diff --git a/rag/app/tag.py b/rag/app/tag.py index de2ce0f..e1a6756 100644 --- a/rag/app/tag.py +++ b/rag/app/tag.py @@ -133,14 +133,14 @@ def label_question(question, kbs): if tag_kb_ids: all_tags = get_tags_from_cache(tag_kb_ids) if not all_tags: - all_tags = settings.retrievaler.all_tags_in_portion(kb.tenant_id, tag_kb_ids) + all_tags = settings.retriever.all_tags_in_portion(kb.tenant_id, tag_kb_ids) set_tags_to_cache(tags=all_tags, kb_ids=tag_kb_ids) else: all_tags = json.loads(all_tags) tag_kbs = KnowledgebaseService.get_by_ids(tag_kb_ids) if not tag_kbs: return tags - tags = settings.retrievaler.tag_query(question, + tags = settings.retriever.tag_query(question, list(set([kb.tenant_id for kb in tag_kbs])), tag_kb_ids, all_tags, diff --git a/rag/benchmark.py b/rag/benchmark.py index 31a6c92..b738300 100644 --- a/rag/benchmark.py +++ b/rag/benchmark.py @@ -52,7 +52,7 @@ class Benchmark: run = defaultdict(dict) query_list = list(qrels.keys()) for query in query_list: - ranks = settings.retrievaler.retrieval(query, self.embd_mdl, self.tenant_id, [self.kb.id], 1, 30, + ranks = settings.retriever.retrieval(query, self.embd_mdl, self.tenant_id, [self.kb.id], 1, 30, 0.0, self.vector_similarity_weight) if len(ranks["chunks"]) == 0: print(f"deleted query: {query}") diff --git a/rag/flow/hierarchical_merger/hierarchical_merger.py b/rag/flow/hierarchical_merger/hierarchical_merger.py index 5967089..e7b8b9d 100644 --- a/rag/flow/hierarchical_merger/hierarchical_merger.py +++ b/rag/flow/hierarchical_merger/hierarchical_merger.py @@ -22,7 +22,7 @@ import trio from api.utils import get_uuid from api.utils.base64_image import id2image, image2id -from ocr.service import get_ocr_service +from deepdoc.parser.pdf_parser import RAGFlowPdfParser from rag.flow.base import ProcessBase, ProcessParamBase from rag.flow.hierarchical_merger.schema import HierarchicalMergerFromUpstream from rag.nlp import concat_img @@ -166,24 +166,21 @@ class HierarchicalMerger(ProcessBase): img = None for i in path: txt += lines[i] + "\n" - concat_img(img, id2image(section_images[i], partial(STORAGE_IMPL.get))) + concat_img(img, id2image(section_images[i], partial(STORAGE_IMPL.get, tenant_id=self._canvas._tenant_id))) cks.append(txt) images.append(img) - ocr_service = get_ocr_service() - processed_cks = [] - for c, img in zip(cks, images): - cleaned_text = await ocr_service.remove_tag(c) - positions = await ocr_service.extract_positions(c) - processed_cks.append({ - "text": cleaned_text, + cks = [ + { + "text": RAGFlowPdfParser.remove_tag(c), "image": img, - "positions": positions, - }) - cks = processed_cks + "positions": RAGFlowPdfParser.extract_positions(c), + } + for c, img in zip(cks, images) + ] async with trio.open_nursery() as nursery: for d in cks: - nursery.start_soon(image2id, d, partial(STORAGE_IMPL.put), get_uuid()) + nursery.start_soon(image2id, d, partial(STORAGE_IMPL.put, tenant_id=self._canvas._tenant_id), get_uuid()) self.set_output("chunks", cks) self.callback(1, "Done.") diff --git a/rag/flow/parser/parser.py b/rag/flow/parser/parser.py index efb00f2..bff05d0 100644 --- a/rag/flow/parser/parser.py +++ b/rag/flow/parser/parser.py @@ -29,8 +29,8 @@ from api.db.services.llm_service import LLMBundle from api.utils import get_uuid from api.utils.base64_image import image2id from deepdoc.parser import ExcelParser -from deepdoc.parser.pdf_parser import PlainParser, VisionParser -from ocr.service import get_ocr_service +from deepdoc.parser.mineru_parser import MinerUParser +from deepdoc.parser.pdf_parser import PlainParser, RAGFlowPdfParser, VisionParser from rag.app.naive import Docx from rag.flow.base import ProcessBase, ProcessParamBase from rag.flow.parser.schema import ParserFromUpstream @@ -53,6 +53,7 @@ class ParserParam(ProcessParamBase): ], "word": [ "json", + "markdown", ], "slides": [ "json", @@ -138,9 +139,16 @@ class ParserParam(ProcessParamBase): "oggvorbis", "ape" ], - "output_format": "json", + "output_format": "text", + }, + "video": { + "suffix":[ + "mp4", + "avi", + "mkv" + ], + "output_format": "text", }, - "video": {}, } def check(self): @@ -149,7 +157,7 @@ class ParserParam(ProcessParamBase): pdf_parse_method = pdf_config.get("parse_method", "") self.check_empty(pdf_parse_method, "Parse method abnormal.") - if pdf_parse_method.lower() not in ["deepdoc", "plain_text"]: + if pdf_parse_method.lower() not in ["deepdoc", "plain_text", "mineru"]: self.check_empty(pdf_config.get("lang", ""), "PDF VLM language") pdf_output_format = pdf_config.get("output_format", "") @@ -184,8 +192,10 @@ class ParserParam(ProcessParamBase): audio_config = self.setups.get("audio", "") if audio_config: self.check_empty(audio_config.get("llm_id"), "Audio VLM") - audio_language = audio_config.get("lang", "") - self.check_empty(audio_language, "Language") + + video_config = self.setups.get("video", "") + if video_config: + self.check_empty(video_config.get("llm_id"), "Video VLM") email_config = self.setups.get("email", "") if email_config: @@ -205,19 +215,38 @@ class Parser(ProcessBase): self.set_output("output_format", conf["output_format"]) if conf.get("parse_method").lower() == "deepdoc": - # 注意:HTTP 调用中无法传递 callback,callback 将被忽略 - ocr_service = get_ocr_service() - bboxes = ocr_service.parse_into_bboxes_sync(blob, callback=self.callback, filename=name) + bboxes = RAGFlowPdfParser().parse_into_bboxes(blob, callback=self.callback) elif conf.get("parse_method").lower() == "plain_text": lines, _ = PlainParser()(blob) bboxes = [{"text": t} for t, _ in lines] + elif conf.get("parse_method").lower() == "mineru": + mineru_executable = os.environ.get("MINERU_EXECUTABLE", "mineru") + pdf_parser = MinerUParser(mineru_path=mineru_executable) + if not pdf_parser.check_installation(): + raise RuntimeError("MinerU not found. Please install it via: pip install -U 'mineru[core]'.") + + lines, _ = pdf_parser.parse_pdf( + filepath=name, + binary=blob, + callback=self.callback, + output_dir=os.environ.get("MINERU_OUTPUT_DIR", ""), + delete_output=bool(int(os.environ.get("MINERU_DELETE_OUTPUT", 1))), + ) + bboxes = [] + for t, poss in lines: + box = { + "image": pdf_parser.crop(poss, 1), + "positions": [[pos[0][-1], *pos[1:]] for pos in pdf_parser.extract_positions(poss)], + "text": t, + } + bboxes.append(box) else: vision_model = LLMBundle(self._canvas._tenant_id, LLMType.IMAGE2TEXT, llm_name=conf.get("parse_method"), lang=self._param.setups["pdf"].get("lang")) lines, _ = VisionParser(vision_model=vision_model)(blob, callback=self.callback) bboxes = [] for t, poss in lines: - pn, x0, x1, top, bott = poss.split(" ") - bboxes.append({"page_number": int(pn), "x0": float(x0), "x1": float(x1), "top": float(top), "bottom": float(bott), "text": t}) + for pn, x0, x1, top, bott in RAGFlowPdfParser.extract_positions(poss): + bboxes.append({"page_number": int(pn[0]), "x0": float(x0), "x1": float(x1), "top": float(top), "bottom": float(bott), "text": t}) if conf.get("output_format") == "json": self.set_output("json", bboxes) @@ -250,13 +279,15 @@ class Parser(ProcessBase): conf = self._param.setups["word"] self.set_output("output_format", conf["output_format"]) docx_parser = Docx() - sections, tbls = docx_parser(name, binary=blob) - sections = [{"text": section[0], "image": section[1]} for section in sections if section] - sections.extend([{"text": tb, "image": None} for ((_,tb), _) in tbls]) - # json - assert conf.get("output_format") == "json", "have to be json for doc" + if conf.get("output_format") == "json": + sections, tbls = docx_parser(name, binary=blob) + sections = [{"text": section[0], "image": section[1]} for section in sections if section] + sections.extend([{"text": tb, "image": None} for ((_,tb), _) in tbls]) self.set_output("json", sections) + elif conf.get("output_format") == "markdown": + markdown_text = docx_parser.to_markdown(name, binary=blob) + self.set_output("markdown", markdown_text) def _slides(self, name, blob): from deepdoc.parser.ppt_parser import RAGFlowPptParser as ppt_parser @@ -348,24 +379,34 @@ class Parser(ProcessBase): conf = self._param.setups["audio"] self.set_output("output_format", conf["output_format"]) - - lang = conf["lang"] _, ext = os.path.splitext(name) with tempfile.NamedTemporaryFile(suffix=ext) as tmpf: tmpf.write(blob) tmpf.flush() tmp_path = os.path.abspath(tmpf.name) - seq2txt_mdl = LLMBundle(self._canvas.get_tenant_id(), LLMType.SPEECH2TEXT, lang=lang) + seq2txt_mdl = LLMBundle(self._canvas.get_tenant_id(), LLMType.SPEECH2TEXT) txt = seq2txt_mdl.transcription(tmp_path) self.set_output("text", txt) + def _video(self, name, blob): + self.callback(random.randint(1, 5) / 100.0, "Start to work on an video.") + + conf = self._param.setups["video"] + self.set_output("output_format", conf["output_format"]) + + cv_mdl = LLMBundle(self._canvas.get_tenant_id(), LLMType.IMAGE2TEXT) + txt = cv_mdl.chat(system="", history=[], gen_conf={}, video_bytes=blob, filename=name) + + self.set_output("text", txt) + def _email(self, name, blob): self.callback(random.randint(1, 5) / 100.0, "Start to work on an email.") email_content = {} conf = self._param.setups["email"] + self.set_output("output_format", conf["output_format"]) target_fields = conf["fields"] _, ext = os.path.splitext(name) @@ -403,8 +444,8 @@ class Parser(ProcessBase): _add_content(msg, msg.get_content_type()) - email_content["text"] = body_text - email_content["text_html"] = body_html + email_content["text"] = "\n".join(body_text) + email_content["text_html"] = "\n".join(body_html) # get attachment if "attachments" in target_fields: attachments = [] @@ -414,7 +455,7 @@ class Parser(ProcessBase): dispositions = content_disposition.strip().split(";") if dispositions[0].lower() == "attachment": filename = part.get_filename() - payload = part.get_payload(decode=True) + payload = part.get_payload(decode=True).decode(part.get_content_charset()) attachments.append({ "filename": filename, "payload": payload, @@ -442,15 +483,16 @@ class Parser(ProcessBase): } # get body if "body" in target_fields: - email_content["text"] = msg.body # usually empty. try text_html instead - email_content["text_html"] = msg.htmlBody + email_content["text"] = msg.body[0] if isinstance(msg.body, list) and msg.body else msg.body + if not email_content["text"] and msg.htmlBody: + email_content["text"] = msg.htmlBody[0] if isinstance(msg.htmlBody, list) and msg.htmlBody else msg.htmlBody # get attachments if "attachments" in target_fields: attachments = [] for t in msg.attachments: attachments.append({ "filename": t.name, - "payload": t.data # binary + "payload": t.data.decode("utf-8") }) email_content["attachments"] = attachments @@ -485,6 +527,7 @@ class Parser(ProcessBase): "word": self._word, "image": self._image, "audio": self._audio, + "video": self._video, "email": self._email, } try: @@ -514,4 +557,4 @@ class Parser(ProcessBase): outs = self.output() async with trio.open_nursery() as nursery: for d in outs.get("json", []): - nursery.start_soon(image2id, d, partial(STORAGE_IMPL.put), get_uuid()) + nursery.start_soon(image2id, d, partial(STORAGE_IMPL.put, tenant_id=self._canvas._tenant_id), get_uuid()) diff --git a/rag/flow/splitter/schema.py b/rag/flow/splitter/schema.py index 9144a4d..9875d65 100644 --- a/rag/flow/splitter/schema.py +++ b/rag/flow/splitter/schema.py @@ -25,7 +25,7 @@ class SplitterFromUpstream(BaseModel): file: dict | None = Field(default=None) chunks: list[dict[str, Any]] | None = Field(default=None) - output_format: Literal["json", "markdown", "text", "html"] | None = Field(default=None) + output_format: Literal["json", "markdown", "text", "html", "chunks"] | None = Field(default=None) json_result: list[dict[str, Any]] | None = Field(default=None, alias="json") markdown_result: str | None = Field(default=None, alias="markdown") diff --git a/rag/flow/splitter/splitter.py b/rag/flow/splitter/splitter.py index e9d5878..24f62b6 100644 --- a/rag/flow/splitter/splitter.py +++ b/rag/flow/splitter/splitter.py @@ -19,7 +19,7 @@ import trio from api.utils import get_uuid from api.utils.base64_image import id2image, image2id -from ocr.service import get_ocr_service +from deepdoc.parser.pdf_parser import RAGFlowPdfParser from rag.flow.base import ProcessBase, ProcessParamBase from rag.flow.splitter.schema import SplitterFromUpstream from rag.nlp import naive_merge, naive_merge_with_images @@ -87,7 +87,7 @@ class Splitter(ProcessBase): sections, section_images = [], [] for o in from_upstream.json_result or []: sections.append((o.get("text", ""), o.get("position_tag", ""))) - section_images.append(id2image(o.get("img_id"), partial(STORAGE_IMPL.get))) + section_images.append(id2image(o.get("img_id"), partial(STORAGE_IMPL.get, tenant_id=self._canvas._tenant_id))) chunks, images = naive_merge_with_images( sections, @@ -96,20 +96,16 @@ class Splitter(ProcessBase): deli, self._param.overlapped_percent, ) - ocr_service = get_ocr_service() - cks = [] - for c, img in zip(chunks, images): - if not c.strip(): - continue - cleaned_text = await ocr_service.remove_tag(c) - positions = await ocr_service.extract_positions(c) - cks.append({ - "text": cleaned_text, + cks = [ + { + "text": RAGFlowPdfParser.remove_tag(c), "image": img, - "positions": [[pos[0][-1]+1, *pos[1:]] for pos in positions], - }) + "positions": [[pos[0][-1]+1, *pos[1:]] for pos in RAGFlowPdfParser.extract_positions(c)], + } + for c, img in zip(chunks, images) if c.strip() + ] async with trio.open_nursery() as nursery: for d in cks: - nursery.start_soon(image2id, d, partial(STORAGE_IMPL.put), get_uuid()) + nursery.start_soon(image2id, d, partial(STORAGE_IMPL.put, tenant_id=self._canvas._tenant_id), get_uuid()) self.set_output("chunks", cks) self.callback(1, "Done.") diff --git a/rag/flow/tokenizer/tokenizer.py b/rag/flow/tokenizer/tokenizer.py index 425e203..23e1794 100644 --- a/rag/flow/tokenizer/tokenizer.py +++ b/rag/flow/tokenizer/tokenizer.py @@ -126,7 +126,7 @@ class Tokenizer(ProcessBase): if ck.get("summary"): ck["content_ltks"] = rag_tokenizer.tokenize(str(ck["summary"])) ck["content_sm_ltks"] = rag_tokenizer.fine_grained_tokenize(ck["content_ltks"]) - else: + elif ck.get("text"): ck["content_ltks"] = rag_tokenizer.tokenize(ck["text"]) ck["content_sm_ltks"] = rag_tokenizer.fine_grained_tokenize(ck["content_ltks"]) if i % 100 == 99: @@ -155,6 +155,8 @@ class Tokenizer(ProcessBase): for i, ck in enumerate(chunks): ck["title_tks"] = rag_tokenizer.tokenize(re.sub(r"\.[a-zA-Z]+$", "", from_upstream.name)) ck["title_sm_tks"] = rag_tokenizer.fine_grained_tokenize(ck["title_tks"]) + if not ck.get("text"): + continue ck["content_ltks"] = rag_tokenizer.tokenize(ck["text"]) ck["content_sm_ltks"] = rag_tokenizer.fine_grained_tokenize(ck["content_ltks"]) if i % 100 == 99: diff --git a/rag/llm/chat_model.py b/rag/llm/chat_model.py index 81e1a34..8ca38c8 100644 --- a/rag/llm/chat_model.py +++ b/rag/llm/chat_model.py @@ -132,8 +132,7 @@ class Base(ABC): "tool_choice", "logprobs", "top_logprobs", - "extra_headers", - "enable_thinking" + "extra_headers" } gen_conf = {k: v for k, v in gen_conf.items() if k in allowed_conf} @@ -142,6 +141,22 @@ class Base(ABC): def _chat(self, history, gen_conf, **kwargs): logging.info("[HISTORY]" + json.dumps(history, ensure_ascii=False, indent=2)) + if self.model_name.lower().find("qwq") >= 0: + logging.info(f"[INFO] {self.model_name} detected as reasoning model, using _chat_streamly") + + final_ans = "" + tol_token = 0 + for delta, tol in self._chat_streamly(history, gen_conf, with_reasoning=False, **kwargs): + if delta.startswith("") or delta.endswith(""): + continue + final_ans += delta + tol_token = tol + + if len(final_ans.strip()) == 0: + final_ans = "**ERROR**: Empty response from reasoning model" + + return final_ans.strip(), tol_token + if self.model_name.lower().find("qwen3") >= 0: kwargs["extra_body"] = {"enable_thinking": False} @@ -152,7 +167,7 @@ class Base(ABC): ans = response.choices[0].message.content.strip() if response.choices[0].finish_reason == "length": ans = self._length_stop(ans) - return ans, self.total_token_count(response) + return ans, total_token_count_from_response(response) def _chat_streamly(self, history, gen_conf, **kwargs): logging.info("[HISTORY STREAMLY]" + json.dumps(history, ensure_ascii=False, indent=4)) @@ -178,7 +193,7 @@ class Base(ABC): reasoning_start = False ans = resp.choices[0].delta.content - tol = self.total_token_count(resp) + tol = total_token_count_from_response(resp) if not tol: tol = num_tokens_from_string(resp.choices[0].delta.content) @@ -268,7 +283,7 @@ class Base(ABC): for _ in range(self.max_rounds + 1): logging.info(f"{self.tools=}") response = self.client.chat.completions.create(model=self.model_name, messages=history, tools=self.tools, tool_choice="auto", **gen_conf) - tk_count += self.total_token_count(response) + tk_count += total_token_count_from_response(response) if any([not response.choices, not response.choices[0].message]): raise Exception(f"500 response structure error. Response: {response}") @@ -386,7 +401,7 @@ class Base(ABC): answer += resp.choices[0].delta.content yield resp.choices[0].delta.content - tol = self.total_token_count(resp) + tol = total_token_count_from_response(resp) if not tol: total_tokens += num_tokens_from_string(resp.choices[0].delta.content) else: @@ -422,7 +437,7 @@ class Base(ABC): if not resp.choices[0].delta.content: resp.choices[0].delta.content = "" continue - tol = self.total_token_count(resp) + tol = total_token_count_from_response(resp) if not tol: total_tokens += num_tokens_from_string(resp.choices[0].delta.content) else: @@ -457,9 +472,6 @@ class Base(ABC): yield total_tokens - def total_token_count(self, resp): - return total_token_count_from_response(resp) - def _calculate_dynamic_ctx(self, history): """Calculate dynamic context window size""" @@ -589,7 +601,7 @@ class BaiChuanChat(Base): ans += LENGTH_NOTIFICATION_CN else: ans += LENGTH_NOTIFICATION_EN - return ans, self.total_token_count(response) + return ans, total_token_count_from_response(response) def chat_streamly(self, system, history, gen_conf={}, **kwargs): if system and history and history[0].get("role") != "system": @@ -612,7 +624,7 @@ class BaiChuanChat(Base): if not resp.choices[0].delta.content: resp.choices[0].delta.content = "" ans = resp.choices[0].delta.content - tol = self.total_token_count(resp) + tol = total_token_count_from_response(resp) if not tol: total_tokens += num_tokens_from_string(resp.choices[0].delta.content) else: @@ -676,9 +688,9 @@ class ZhipuChat(Base): ans += LENGTH_NOTIFICATION_CN else: ans += LENGTH_NOTIFICATION_EN - tk_count = self.total_token_count(resp) + tk_count = total_token_count_from_response(resp) if resp.choices[0].finish_reason == "stop": - tk_count = self.total_token_count(resp) + tk_count = total_token_count_from_response(resp) yield ans except Exception as e: yield ans + "\n**ERROR**: " + str(e) @@ -797,7 +809,7 @@ class MiniMaxChat(Base): ans += LENGTH_NOTIFICATION_CN else: ans += LENGTH_NOTIFICATION_EN - return ans, self.total_token_count(response) + return ans, total_token_count_from_response(response) def chat_streamly(self, system, history, gen_conf): if system and history and history[0].get("role") != "system": @@ -832,7 +844,7 @@ class MiniMaxChat(Base): if "choices" in resp and "delta" in resp["choices"][0]: text = resp["choices"][0]["delta"]["content"] ans = text - tol = self.total_token_count(resp) + tol = total_token_count_from_response(resp) if not tol: total_tokens += num_tokens_from_string(text) else: @@ -871,7 +883,7 @@ class MistralChat(Base): ans += LENGTH_NOTIFICATION_CN else: ans += LENGTH_NOTIFICATION_EN - return ans, self.total_token_count(response) + return ans, total_token_count_from_response(response) def chat_streamly(self, system, history, gen_conf={}, **kwargs): if system and history and history[0].get("role") != "system": @@ -1095,7 +1107,7 @@ class BaiduYiyanChat(Base): system = history[0]["content"] if history and history[0]["role"] == "system" else "" response = self.client.do(model=self.model_name, messages=[h for h in history if h["role"] != "system"], system=system, **gen_conf).body ans = response["result"] - return ans, self.total_token_count(response) + return ans, total_token_count_from_response(response) def chat_streamly(self, system, history, gen_conf={}, **kwargs): gen_conf["penalty_score"] = ((gen_conf.get("presence_penalty", 0) + gen_conf.get("frequency_penalty", 0)) / 2) + 1 @@ -1109,7 +1121,7 @@ class BaiduYiyanChat(Base): for resp in response: resp = resp.body ans = resp["result"] - total_tokens = self.total_token_count(resp) + total_tokens = total_token_count_from_response(resp) yield ans @@ -1150,15 +1162,13 @@ class GoogleChat(Base): else: self.client = AnthropicVertex(region=region, project_id=project_id) else: - import vertexai.generative_models as glm - from google.cloud import aiplatform + from google import genai if access_token: - credits = service_account.Credentials.from_service_account_info(access_token) - aiplatform.init(credentials=credits, project=project_id, location=region) + credits = service_account.Credentials.from_service_account_info(access_token, scopes=scopes) + self.client = genai.Client(vertexai=True, project=project_id, location=region, credentials=credits) else: - aiplatform.init(project=project_id, location=region) - self.client = glm.GenerativeModel(model_name=self.model_name) + self.client = genai.Client(vertexai=True, project=project_id, location=region) def _clean_conf(self, gen_conf): if "claude" in self.model_name: @@ -1167,6 +1177,7 @@ class GoogleChat(Base): else: if "max_tokens" in gen_conf: gen_conf["max_output_tokens"] = gen_conf["max_tokens"] + del gen_conf["max_tokens"] for k in list(gen_conf.keys()): if k not in ["temperature", "top_p", "max_output_tokens"]: del gen_conf[k] @@ -1174,7 +1185,9 @@ class GoogleChat(Base): def _chat(self, history, gen_conf={}, **kwargs): system = history[0]["content"] if history and history[0]["role"] == "system" else "" + if "claude" in self.model_name: + gen_conf = self._clean_conf(gen_conf) response = self.client.messages.create( model=self.model_name, messages=[h for h in history if h["role"] != "system"], @@ -1190,25 +1203,63 @@ class GoogleChat(Base): response["usage"]["input_tokens"] + response["usage"]["output_tokens"], ) - self.client._system_instruction = system - hist = [] + # Gemini models with google-genai SDK + # Set default thinking_budget=0 if not specified + if "thinking_budget" not in gen_conf: + gen_conf["thinking_budget"] = 0 + + thinking_budget = gen_conf.pop("thinking_budget", 0) + gen_conf = self._clean_conf(gen_conf) + + # Build GenerateContentConfig + try: + from google.genai.types import GenerateContentConfig, ThinkingConfig, Content, Part + except ImportError as e: + logging.error(f"[GoogleChat] Failed to import google-genai: {e}. Please install: pip install google-genai>=1.41.0") + raise + + config_dict = {} + if system: + config_dict["system_instruction"] = system + if "temperature" in gen_conf: + config_dict["temperature"] = gen_conf["temperature"] + if "top_p" in gen_conf: + config_dict["top_p"] = gen_conf["top_p"] + if "max_output_tokens" in gen_conf: + config_dict["max_output_tokens"] = gen_conf["max_output_tokens"] + + # Add ThinkingConfig + config_dict["thinking_config"] = ThinkingConfig(thinking_budget=thinking_budget) + + config = GenerateContentConfig(**config_dict) + + # Convert history to google-genai Content format + contents = [] for item in history: if item["role"] == "system": continue - hist.append(deepcopy(item)) - item = hist[-1] - if "role" in item and item["role"] == "assistant": - item["role"] = "model" - if "content" in item: - item["parts"] = [ - { - "text": item.pop("content"), - } - ] + # google-genai uses 'model' instead of 'assistant' + role = "model" if item["role"] == "assistant" else item["role"] + content = Content( + role=role, + parts=[Part(text=item["content"])] + ) + contents.append(content) + + response = self.client.models.generate_content( + model=self.model_name, + contents=contents, + config=config + ) - response = self.client.generate_content(hist, generation_config=gen_conf) ans = response.text - return ans, response.usage_metadata.total_token_count + # Get token count from response + try: + total_tokens = response.usage_metadata.total_token_count + except Exception: + total_tokens = 0 + + return ans, total_tokens def chat_streamly(self, system, history, gen_conf={}, **kwargs): if "claude" in self.model_name: @@ -1235,28 +1286,65 @@ class GoogleChat(Base): yield total_tokens else: - self.client._system_instruction = system - if "max_tokens" in gen_conf: - gen_conf["max_output_tokens"] = gen_conf["max_tokens"] - for k in list(gen_conf.keys()): - if k not in ["temperature", "top_p", "max_output_tokens"]: - del gen_conf[k] - for item in history: - if "role" in item and item["role"] == "assistant": - item["role"] = "model" - if "content" in item: - item["parts"] = item.pop("content") + # Gemini models with google-genai SDK ans = "" + total_tokens = 0 + + # Set default thinking_budget=0 if not specified + if "thinking_budget" not in gen_conf: + gen_conf["thinking_budget"] = 0 + + thinking_budget = gen_conf.pop("thinking_budget", 0) + gen_conf = self._clean_conf(gen_conf) + + # Build GenerateContentConfig try: - response = self.model.generate_content(history, generation_config=gen_conf, stream=True) - for resp in response: - ans = resp.text + from google.genai.types import GenerateContentConfig, ThinkingConfig, Content, Part + except ImportError as e: + logging.error(f"[GoogleChat] Failed to import google-genai: {e}. Please install: pip install google-genai>=1.41.0") + raise + + config_dict = {} + if system: + config_dict["system_instruction"] = system + if "temperature" in gen_conf: + config_dict["temperature"] = gen_conf["temperature"] + if "top_p" in gen_conf: + config_dict["top_p"] = gen_conf["top_p"] + if "max_output_tokens" in gen_conf: + config_dict["max_output_tokens"] = gen_conf["max_output_tokens"] + + # Add ThinkingConfig + config_dict["thinking_config"] = ThinkingConfig(thinking_budget=thinking_budget) + + config = GenerateContentConfig(**config_dict) + + # Convert history to google-genai Content format + contents = [] + for item in history: + # google-genai uses 'model' instead of 'assistant' + role = "model" if item["role"] == "assistant" else item["role"] + content = Content( + role=role, + parts=[Part(text=item["content"])] + ) + contents.append(content) + + try: + for chunk in self.client.models.generate_content_stream( + model=self.model_name, + contents=contents, + config=config + ): + text = chunk.text + ans = text + total_tokens += num_tokens_from_string(text) yield ans except Exception as e: yield ans + "\n**ERROR**: " + str(e) - yield response._chunks[-1].usage_metadata.total_token_count + yield total_tokens class GPUStackChat(Base): @@ -1334,6 +1422,9 @@ class LiteLLMBase(ABC): self.bedrock_ak = json.loads(key).get("bedrock_ak", "") self.bedrock_sk = json.loads(key).get("bedrock_sk", "") self.bedrock_region = json.loads(key).get("bedrock_region", "") + elif self.provider == SupportedLiteLLMProvider.OpenRouter: + self.api_key = json.loads(key).get("api_key", "") + self.provider_order = json.loads(key).get("provider_order", "") def _get_delay(self): """Calculate retry delay time""" @@ -1378,14 +1469,13 @@ class LiteLLMBase(ABC): timeout=self.timeout, ) # response = self.client.chat.completions.create(model=self.model_name, messages=history, **gen_conf, **kwargs) - if any([not response.choices, not response.choices[0].message, not response.choices[0].message.content]): return "", 0 ans = response.choices[0].message.content.strip() if response.choices[0].finish_reason == "length": ans = self._length_stop(ans) - return ans, self.total_token_count(response) + return ans, total_token_count_from_response(response) def _chat_streamly(self, history, gen_conf, **kwargs): logging.info("[HISTORY STREAMLY]" + json.dumps(history, ensure_ascii=False, indent=4)) @@ -1419,7 +1509,7 @@ class LiteLLMBase(ABC): reasoning_start = False ans = delta.content - tol = self.total_token_count(resp) + tol = total_token_count_from_response(resp) if not tol: tol = num_tokens_from_string(delta.content) @@ -1529,6 +1619,24 @@ class LiteLLMBase(ABC): "aws_region_name": self.bedrock_region, } ) + + if self.provider == SupportedLiteLLMProvider.OpenRouter: + if self.provider_order: + def _to_order_list(x): + if x is None: + return [] + if isinstance(x, str): + return [s.strip() for s in x.split(",") if s.strip()] + if isinstance(x, (list, tuple)): + return [str(s).strip() for s in x if str(s).strip()] + return [] + extra_body = {} + provider_cfg = {} + provider_order = _to_order_list(self.provider_order) + provider_cfg["order"] = provider_order + provider_cfg["allow_fallbacks"] = False + extra_body["provider"] = provider_cfg + completion_args.update({"extra_body": extra_body}) return completion_args def chat_with_tools(self, system: str, history: list, gen_conf: dict = {}): @@ -1554,7 +1662,7 @@ class LiteLLMBase(ABC): timeout=self.timeout, ) - tk_count += self.total_token_count(response) + tk_count += total_token_count_from_response(response) if not hasattr(response, "choices") or not response.choices or not response.choices[0].message: raise Exception(f"500 response structure error. Response: {response}") @@ -1686,7 +1794,7 @@ class LiteLLMBase(ABC): answer += delta.content yield delta.content - tol = self.total_token_count(resp) + tol = total_token_count_from_response(resp) if not tol: total_tokens += num_tokens_from_string(delta.content) else: @@ -1735,7 +1843,7 @@ class LiteLLMBase(ABC): delta = resp.choices[0].delta if not hasattr(delta, "content") or delta.content is None: continue - tol = self.total_token_count(resp) + tol = total_token_count_from_response(resp) if not tol: total_tokens += num_tokens_from_string(delta.content) else: @@ -1769,17 +1877,6 @@ class LiteLLMBase(ABC): yield total_tokens - def total_token_count(self, resp): - try: - return resp.usage.total_tokens - except Exception: - pass - try: - return resp["usage"]["total_tokens"] - except Exception: - pass - return 0 - def _calculate_dynamic_ctx(self, history): """Calculate dynamic context window size""" diff --git a/rag/llm/cv_model.py b/rag/llm/cv_model.py index 7e76364..67d1bd1 100644 --- a/rag/llm/cv_model.py +++ b/rag/llm/cv_model.py @@ -13,12 +13,16 @@ # See the License for the specific language governing permissions and # limitations under the License. # + import base64 import json import os +import tempfile +import logging from abc import ABC from copy import deepcopy from io import BytesIO +from pathlib import Path from urllib.parse import urljoin import requests from openai import OpenAI @@ -38,6 +42,7 @@ class Base(ABC): self.is_tools = False self.tools = [] self.toolcall_sessions = {} + self.extra_body = None def describe(self, image): raise NotImplementedError("Please implement encode method!") @@ -45,7 +50,7 @@ class Base(ABC): def describe_with_prompt(self, image, prompt=None): raise NotImplementedError("Please implement encode method!") - def _form_history(self, system, history, images=[]): + def _form_history(self, system, history, images=None): hist = [] if system: hist.append({"role": "system", "content": system}) @@ -73,24 +78,26 @@ class Base(ABC): }) return pmpt - def chat(self, system, history, gen_conf, images=[], **kwargs): + def chat(self, system, history, gen_conf, images=None, **kwargs): try: response = self.client.chat.completions.create( model=self.model_name, - messages=self._form_history(system, history, images) + messages=self._form_history(system, history, images), + extra_body=self.extra_body, ) return response.choices[0].message.content.strip(), response.usage.total_tokens except Exception as e: return "**ERROR**: " + str(e), 0 - def chat_streamly(self, system, history, gen_conf, images=[], **kwargs): + def chat_streamly(self, system, history, gen_conf, images=None, **kwargs): ans = "" tk_count = 0 try: response = self.client.chat.completions.create( model=self.model_name, messages=self._form_history(system, history, images), - stream=True + stream=True, + extra_body=self.extra_body, ) for resp in response: if not resp.choices[0].delta.content: @@ -167,6 +174,7 @@ class GptV4(Base): def __init__(self, key, model_name="gpt-4-vision-preview", lang="Chinese", base_url="https://api.openai.com/v1", **kwargs): if not base_url: base_url = "https://api.openai.com/v1" + self.api_key = key self.client = OpenAI(api_key=key, base_url=base_url) self.model_name = model_name self.lang = lang @@ -177,6 +185,7 @@ class GptV4(Base): res = self.client.chat.completions.create( model=self.model_name, messages=self.prompt(b64), + extra_body=self.extra_body, ) return res.choices[0].message.content.strip(), total_token_count_from_response(res) @@ -185,6 +194,7 @@ class GptV4(Base): res = self.client.chat.completions.create( model=self.model_name, messages=self.vision_llm_prompt(b64, prompt), + extra_body=self.extra_body, ) return res.choices[0].message.content.strip(),total_token_count_from_response(res) @@ -218,6 +228,61 @@ class QWenCV(GptV4): base_url = "https://dashscope.aliyuncs.com/compatible-mode/v1" super().__init__(key, model_name, lang=lang, base_url=base_url, **kwargs) + def chat(self, system, history, gen_conf, images=None, video_bytes=None, filename=""): + if video_bytes: + try: + summary, summary_num_tokens = self._process_video(video_bytes, filename) + return summary, summary_num_tokens + except Exception as e: + return "**ERROR**: " + str(e), 0 + + return "**ERROR**: Method chat not supported yet.", 0 + + def _process_video(self, video_bytes, filename): + from dashscope import MultiModalConversation + + video_suffix = Path(filename).suffix or ".mp4" + with tempfile.NamedTemporaryFile(delete=False, suffix=video_suffix) as tmp: + tmp.write(video_bytes) + tmp_path = tmp.name + + video_path = f"file://{tmp_path}" + messages = [ + { + "role": "user", + "content": [ + { + "video": video_path, + "fps": 2, + }, + { + "text": "Please summarize this video in proper sentences.", + }, + ], + } + ] + + def call_api(): + response = MultiModalConversation.call( + api_key=self.api_key, + model=self.model_name, + messages=messages, + ) + summary = response["output"]["choices"][0]["message"].content[0]["text"] + return summary, num_tokens_from_string(summary) + + try: + return call_api() + except Exception as e1: + import dashscope + + dashscope.base_http_api_url = "https://dashscope-intl.aliyuncs.com/api/v1" + try: + return call_api() + except Exception as e2: + raise RuntimeError(f"Both default and intl endpoint failed.\nFirst error: {e1}\nSecond error: {e2}") + + class HunyuanCV(GptV4): _FACTORY_NAME = "Tencent Hunyuan" @@ -249,6 +314,17 @@ class StepFunCV(GptV4): self.lang = lang Base.__init__(self, **kwargs) +class VolcEngineCV(GptV4): + _FACTORY_NAME = "VolcEngine" + + def __init__(self, key, model_name, lang="Chinese", base_url="https://ark.cn-beijing.volces.com/api/v3", **kwargs): + if not base_url: + base_url = "https://ark.cn-beijing.volces.com/api/v3" + ark_api_key = json.loads(key).get("ark_api_key", "") + self.client = OpenAI(api_key=ark_api_key, base_url=base_url) + self.model_name = json.loads(key).get("ep_id", "") + json.loads(key).get("endpoint_id", "") + self.lang = lang + Base.__init__(self, **kwargs) class LmStudioCV(GptV4): _FACTORY_NAME = "LM-Studio" @@ -327,10 +403,27 @@ class OpenRouterCV(GptV4): ): if not base_url: base_url = "https://openrouter.ai/api/v1" - self.client = OpenAI(api_key=key, base_url=base_url) + api_key = json.loads(key).get("api_key", "") + self.client = OpenAI(api_key=api_key, base_url=base_url) self.model_name = model_name self.lang = lang Base.__init__(self, **kwargs) + provider_order = json.loads(key).get("provider_order", "") + self.extra_body = {} + if provider_order: + def _to_order_list(x): + if x is None: + return [] + if isinstance(x, str): + return [s.strip() for s in x.split(",") if s.strip()] + if isinstance(x, (list, tuple)): + return [str(s).strip() for s in x if str(s).strip()] + return [] + provider_cfg = {} + provider_order = _to_order_list(provider_order) + provider_cfg["order"] = provider_order + provider_cfg["allow_fallbacks"] = False + self.extra_body["provider"] = provider_cfg class LocalAICV(GptV4): @@ -413,7 +506,7 @@ class OllamaCV(Base): options["frequency_penalty"] = gen_conf["frequency_penalty"] return options - def _form_history(self, system, history, images=[]): + def _form_history(self, system, history, images=None): hist = deepcopy(history) if system and hist[0]["role"] == "user": hist.insert(0, {"role": "system", "content": system}) @@ -454,7 +547,7 @@ class OllamaCV(Base): except Exception as e: return "**ERROR**: " + str(e), 0 - def chat(self, system, history, gen_conf, images=[]): + def chat(self, system, history, gen_conf, images=None): try: response = self.client.chat( model=self.model_name, @@ -468,7 +561,7 @@ class OllamaCV(Base): except Exception as e: return "**ERROR**: " + str(e), 0 - def chat_streamly(self, system, history, gen_conf, images=[]): + def chat_streamly(self, system, history, gen_conf, images=None): ans = "" try: response = self.client.chat( @@ -496,13 +589,14 @@ class GeminiCV(Base): client.configure(api_key=key) _client = client.get_default_generative_client() + self.api_key=key self.model_name = model_name self.model = GenerativeModel(model_name=self.model_name) self.model._client = _client self.lang = lang Base.__init__(self, **kwargs) - def _form_history(self, system, history, images=[]): + def _form_history(self, system, history, images=None): hist = [] if system: hist.append({"role": "user", "parts": [system, history[0]["content"]]}) @@ -538,7 +632,15 @@ class GeminiCV(Base): res = self.model.generate_content(input) return res.text, total_token_count_from_response(res) - def chat(self, system, history, gen_conf, images=[]): + + def chat(self, system, history, gen_conf, images=None, video_bytes=None, filename=""): + if video_bytes: + try: + summary, summary_num_tokens = self._process_video(video_bytes, filename) + return summary, summary_num_tokens + except Exception as e: + return "**ERROR**: " + str(e), 0 + generation_config = dict(temperature=gen_conf.get("temperature", 0.3), top_p=gen_conf.get("top_p", 0.7)) try: response = self.model.generate_content( @@ -549,7 +651,7 @@ class GeminiCV(Base): except Exception as e: return "**ERROR**: " + str(e), 0 - def chat_streamly(self, system, history, gen_conf, images=[]): + def chat_streamly(self, system, history, gen_conf, images=None): ans = "" response = None try: @@ -570,6 +672,46 @@ class GeminiCV(Base): yield total_token_count_from_response(response) + def _process_video(self, video_bytes, filename): + from google import genai + from google.genai import types + + video_size_mb = len(video_bytes) / (1024 * 1024) + client = genai.Client(api_key=self.api_key) + + tmp_path = None + try: + if video_size_mb <= 20: + response = client.models.generate_content( + model="models/gemini-2.5-flash", + contents=types.Content(parts=[ + types.Part(inline_data=types.Blob(data=video_bytes, mime_type="video/mp4")), + types.Part(text="Please summarize the video in proper sentences.") + ]) + ) + else: + logging.info(f"Video size {video_size_mb:.2f}MB exceeds 20MB. Using Files API...") + video_suffix = Path(filename).suffix or ".mp4" + with tempfile.NamedTemporaryFile(delete=False, suffix=video_suffix) as tmp: + tmp.write(video_bytes) + tmp_path = Path(tmp.name) + uploaded_file = client.files.upload(file=tmp_path) + + response = client.models.generate_content( + model="gemini-2.5-flash", + contents=[uploaded_file, "Please summarize this video in proper sentences."] + ) + + summary = response.text or "" + logging.info(f"Video summarized: {summary[:32]}...") + return summary, num_tokens_from_string(summary) + except Exception as e: + logging.error(f"Video processing failed: {e}") + raise + finally: + if tmp_path and tmp_path.exists(): + tmp_path.unlink() + class NvidiaCV(Base): _FACTORY_NAME = "NVIDIA" @@ -614,7 +756,7 @@ class NvidiaCV(Base): response = response.json() return ( response["choices"][0]["message"]["content"].strip(), - response["usage"]["total_tokens"], + total_token_count_from_response(response), ) def _request(self, msg, gen_conf={}): @@ -637,26 +779,26 @@ class NvidiaCV(Base): response = self._request(vision_prompt) return ( response["choices"][0]["message"]["content"].strip(), - response["usage"]["total_tokens"], + total_token_count_from_response(response) ) - def chat(self, system, history, gen_conf, images=[], **kwargs): + def chat(self, system, history, gen_conf, images=None, **kwargs): try: response = self._request(self._form_history(system, history, images), gen_conf) return ( response["choices"][0]["message"]["content"].strip(), - response["usage"]["total_tokens"], + total_token_count_from_response(response) ) except Exception as e: return "**ERROR**: " + str(e), 0 - def chat_streamly(self, system, history, gen_conf, images=[], **kwargs): + def chat_streamly(self, system, history, gen_conf, images=None, **kwargs): total_tokens = 0 try: response = self._request(self._form_history(system, history, images), gen_conf) cnt = response["choices"][0]["message"]["content"] if "usage" in response and "total_tokens" in response["usage"]: - total_tokens += response["usage"]["total_tokens"] + total_tokens += total_token_count_from_response(response) for resp in cnt: yield resp except Exception as e: @@ -716,7 +858,7 @@ class AnthropicCV(Base): gen_conf["max_tokens"] = self.max_tokens return gen_conf - def chat(self, system, history, gen_conf, images=[]): + def chat(self, system, history, gen_conf, images=None): gen_conf = self._clean_conf(gen_conf) ans = "" try: @@ -737,7 +879,7 @@ class AnthropicCV(Base): except Exception as e: return ans + "\n**ERROR**: " + str(e), 0 - def chat_streamly(self, system, history, gen_conf, images=[]): + def chat_streamly(self, system, history, gen_conf, images=None): gen_conf = self._clean_conf(gen_conf) total_tokens = 0 try: @@ -821,13 +963,13 @@ class GoogleCV(AnthropicCV, GeminiCV): else: return GeminiCV.describe_with_prompt(self, image, prompt) - def chat(self, system, history, gen_conf, images=[]): + def chat(self, system, history, gen_conf, images=None): if "claude" in self.model_name: return AnthropicCV.chat(self, system, history, gen_conf, images) else: return GeminiCV.chat(self, system, history, gen_conf, images) - def chat_streamly(self, system, history, gen_conf, images=[]): + def chat_streamly(self, system, history, gen_conf, images=None): if "claude" in self.model_name: for ans in AnthropicCV.chat_streamly(self, system, history, gen_conf, images): yield ans diff --git a/rag/llm/sequence2txt_model.py b/rag/llm/sequence2txt_model.py index c43a014..c66adad 100644 --- a/rag/llm/sequence2txt_model.py +++ b/rag/llm/sequence2txt_model.py @@ -234,8 +234,8 @@ class DeepInfraSeq2txt(Base): self.client = OpenAI(api_key=key, base_url=base_url) self.model_name = model_name - - + + class CometAPISeq2txt(Base): _FACTORY_NAME = "CometAPI" @@ -244,7 +244,8 @@ class CometAPISeq2txt(Base): base_url = "https://api.cometapi.com/v1" self.client = OpenAI(api_key=key, base_url=base_url) self.model_name = model_name - + + class DeerAPISeq2txt(Base): _FACTORY_NAME = "DeerAPI" @@ -253,3 +254,44 @@ class DeerAPISeq2txt(Base): base_url = "https://api.deerapi.com/v1" self.client = OpenAI(api_key=key, base_url=base_url) self.model_name = model_name + + +class ZhipuSeq2txt(Base): + _FACTORY_NAME = "ZHIPU-AI" + + def __init__(self, key, model_name="glm-asr", base_url="https://open.bigmodel.cn/api/paas/v4", **kwargs): + if not base_url: + base_url = "https://open.bigmodel.cn/api/paas/v4" + self.base_url = base_url + self.api_key = key + self.model_name = model_name + self.gen_conf = kwargs.get("gen_conf", {}) + self.stream = kwargs.get("stream", False) + + def transcription(self, audio_path): + payload = { + "model": self.model_name, + "temperature": str(self.gen_conf.get("temperature", 0.75)) or "0.75", + "stream": self.stream, + } + + headers = {"Authorization": f"Bearer {self.api_key}"} + with open(audio_path, "rb") as audio_file: + files = {"file": audio_file} + + try: + response = requests.post( + url=f"{self.base_url}/audio/transcriptions", + data=payload, + files=files, + headers=headers, + ) + body = response.json() + if response.status_code == 200: + full_content = body["text"] + return full_content, num_tokens_from_string(full_content) + else: + error = body["error"] + return f"**ERROR**: code: {error['code']}, message: {error['message']}", 0 + except Exception as e: + return "**ERROR**: " + str(e), 0 diff --git a/rag/nlp/__init__.py b/rag/nlp/__init__.py index db17998..4fd6eb5 100644 --- a/rag/nlp/__init__.py +++ b/rag/nlp/__init__.py @@ -459,12 +459,10 @@ def tree_merge(bull, sections, depth): return len(BULLET_PATTERN[bull])+1, text else: return len(BULLET_PATTERN[bull])+2, text - level_set = set() lines = [] for section in sections: level, text = get_level(bull, section) - if not text.strip("\n"): continue @@ -578,8 +576,7 @@ def hierarchical_merge(bull, sections, depth): def naive_merge(sections: str | list, chunk_token_num=128, delimiter="\n。;!?", overlapped_percent=0): - from ocr.service import get_ocr_service - ocr_service = get_ocr_service() + from deepdoc.parser.pdf_parser import RAGFlowPdfParser if not sections: return [] if isinstance(sections, str): @@ -599,7 +596,7 @@ def naive_merge(sections: str | list, chunk_token_num=128, delimiter="\n。; # Ensure that the length of the merged chunk does not exceed chunk_token_num if cks[-1] == "" or tk_nums[-1] > chunk_token_num * (100 - overlapped_percent)/100.: if cks: - overlapped = ocr_service.remove_tag_sync(cks[-1]) + overlapped = RAGFlowPdfParser.remove_tag(cks[-1]) t = overlapped[int(len(overlapped)*(100-overlapped_percent)/100.):] + t if t.find(pos) < 0: t += pos @@ -614,20 +611,19 @@ def naive_merge(sections: str | list, chunk_token_num=128, delimiter="\n。; dels = get_delimiters(delimiter) for sec, pos in sections: if num_tokens_from_string(sec) < chunk_token_num: - add_chunk(sec, pos) + add_chunk("\n"+sec, pos) continue split_sec = re.split(r"(%s)" % dels, sec, flags=re.DOTALL) for sub_sec in split_sec: if re.match(f"^{dels}$", sub_sec): continue - add_chunk(sub_sec, pos) + add_chunk("\n"+sub_sec, pos) return cks def naive_merge_with_images(texts, images, chunk_token_num=128, delimiter="\n。;!?", overlapped_percent=0): - from ocr.service import get_ocr_service - ocr_service = get_ocr_service() + from deepdoc.parser.pdf_parser import RAGFlowPdfParser if not texts or len(texts) != len(images): return [], [] cks = [""] @@ -644,7 +640,7 @@ def naive_merge_with_images(texts, images, chunk_token_num=128, delimiter="\n。 # Ensure that the length of the merged chunk does not exceed chunk_token_num if cks[-1] == "" or tk_nums[-1] > chunk_token_num * (100 - overlapped_percent)/100.: if cks: - overlapped = ocr_service.remove_tag_sync(cks[-1]) + overlapped = RAGFlowPdfParser.remove_tag(cks[-1]) t = overlapped[int(len(overlapped)*(100-overlapped_percent)/100.):] + t if t.find(pos) < 0: t += pos @@ -671,13 +667,13 @@ def naive_merge_with_images(texts, images, chunk_token_num=128, delimiter="\n。 for sub_sec in split_sec: if re.match(f"^{dels}$", sub_sec): continue - add_chunk(sub_sec, image, text_pos) + add_chunk("\n"+sub_sec, image, text_pos) else: split_sec = re.split(r"(%s)" % dels, text) for sub_sec in split_sec: if re.match(f"^{dels}$", sub_sec): continue - add_chunk(sub_sec, image) + add_chunk("\n"+sub_sec, image) return cks, result_images @@ -759,7 +755,7 @@ def naive_merge_docx(sections, chunk_token_num=128, delimiter="\n。;!?"): for sub_sec in split_sec: if re.match(f"^{dels}$", sub_sec): continue - add_chunk(sub_sec, image,"") + add_chunk("\n"+sub_sec, image,"") line = "" if line: @@ -767,7 +763,7 @@ def naive_merge_docx(sections, chunk_token_num=128, delimiter="\n。;!?"): for sub_sec in split_sec: if re.match(f"^{dels}$", sub_sec): continue - add_chunk(sub_sec, image,"") + add_chunk("\n"+sub_sec, image,"") return cks, images @@ -799,8 +795,8 @@ class Node: def __init__(self, level, depth=-1, texts=None): self.level = level self.depth = depth - self.texts = texts if texts is not None else [] # 存放内容 - self.children = [] # 子节点 + self.texts = texts or [] + self.children = [] def add_child(self, child_node): self.children.append(child_node) @@ -827,35 +823,51 @@ class Node: return f"Node(level={self.level}, texts={self.texts}, children={len(self.children)})" def build_tree(self, lines): - stack = [self] - for line in lines: - level, text = line - node = Node(level=level, texts=[text]) - - if level <= self.depth or self.depth == -1: - while stack and level <= stack[-1].get_level(): - stack.pop() - - stack[-1].add_child(node) - stack.append(node) - else: + stack = [self] + for level, text in lines: + if self.depth != -1 and level > self.depth: + # Beyond target depth: merge content into the current leaf instead of creating deeper nodes stack[-1].add_text(text) - return self + continue + + # Move up until we find the proper parent whose level is strictly smaller than current + while len(stack) > 1 and level <= stack[-1].get_level(): + stack.pop() + + node = Node(level=level, texts=[text]) + # Attach as child of current parent and descend + stack[-1].add_child(node) + stack.append(node) + + return self def get_tree(self): tree_list = [] - self._dfs(self, tree_list, 0, []) + self._dfs(self, tree_list, []) return tree_list - def _dfs(self, node, tree_list, current_depth, titles): + def _dfs(self, node, tree_list, titles): + level = node.get_level() + texts = node.get_texts() + child = node.get_children() - if node.get_texts(): - if 0 < node.get_level() < self.depth: - titles.extend(node.get_texts()) - else: - combined_text = ["\n".join(titles + node.get_texts())] - tree_list.append(combined_text) + if level == 0 and texts: + tree_list.append("\n".join(titles+texts)) + # Titles within configured depth are accumulated into the current path + if 1 <= level <= self.depth: + path_titles = titles + texts + else: + path_titles = titles - for child in node.get_children(): - self._dfs(child, tree_list, current_depth + 1, titles.copy()) + # Body outside the depth limit becomes its own chunk under the current title path + if level > self.depth and texts: + tree_list.append("\n".join(path_titles + texts)) + + # A leaf title within depth emits its title path as a chunk (header-only section) + elif not child and (1 <= level <= self.depth): + tree_list.append("\n".join(path_titles)) + + # Recurse into children with the updated title path + for c in child: + self._dfs(c, tree_list, path_titles) \ No newline at end of file diff --git a/rag/nlp/search.py b/rag/nlp/search.py index db14230..c10b803 100644 --- a/rag/nlp/search.py +++ b/rag/nlp/search.py @@ -13,12 +13,15 @@ # See the License for the specific language governing permissions and # limitations under the License. # +import json import logging import re import math +import os from collections import OrderedDict from dataclasses import dataclass +from rag.prompts.generator import relevant_chunks_with_toc from rag.settings import TAG_FLD, PAGERANK_FLD from rag.utils import rmSpace, get_float from rag.nlp import rag_tokenizer, query @@ -69,7 +72,7 @@ class Dealer: def search(self, req, idx_names: str | list[str], kb_ids: list[str], emb_mdl=None, - highlight=False, + highlight: bool | list = False, rank_feature: dict | None = None ): filters = self.get_filters(req) @@ -98,7 +101,11 @@ class Dealer: total = self.dataStore.getTotal(res) logging.debug("Dealer.search TOTAL: {}".format(total)) else: - highlightFields = ["content_ltks", "title_tks"] if highlight else [] + highlightFields = ["content_ltks", "title_tks"] + if not highlight: + highlightFields = [] + elif isinstance(highlight, list): + highlightFields = highlight matchText, keywords = self.qryr.question(qst, min_match=0.3) if emb_mdl is None: matchExprs = [matchText] @@ -152,7 +159,7 @@ class Dealer: query_vector=q_vec, aggregation=aggs, highlight=highlight, - field=self.dataStore.getFields(res, src), + field=self.dataStore.getFields(res, src + ["_score"]), keywords=keywords ) @@ -352,10 +359,8 @@ class Dealer: if not question: return ranks - RERANK_LIMIT = 64 - RERANK_LIMIT = int(RERANK_LIMIT//page_size + ((RERANK_LIMIT%page_size)/(page_size*1.) + 0.5)) * page_size if page_size>1 else 1 - if RERANK_LIMIT < 1: ## when page_size is very large the RERANK_LIMIT will be 0. - RERANK_LIMIT = 1 + # Ensure RERANK_LIMIT is multiple of page_size + RERANK_LIMIT = math.ceil(64/page_size) * page_size if page_size>1 else 1 req = {"kb_ids": kb_ids, "doc_ids": doc_ids, "page": math.ceil(page_size*page/RERANK_LIMIT), "size": RERANK_LIMIT, "question": question, "vector": True, "topk": top, "similarity": similarity_threshold, @@ -374,15 +379,26 @@ class Dealer: vector_similarity_weight, rank_feature=rank_feature) else: - sim, tsim, vsim = self.rerank( - sres, question, 1 - vector_similarity_weight, vector_similarity_weight, - rank_feature=rank_feature) + lower_case_doc_engine = os.getenv('DOC_ENGINE', 'elasticsearch') + if lower_case_doc_engine == "elasticsearch": + # ElasticSearch doesn't normalize each way score before fusion. + sim, tsim, vsim = self.rerank( + sres, question, 1 - vector_similarity_weight, vector_similarity_weight, + rank_feature=rank_feature) + else: + # Don't need rerank here since Infinity normalizes each way score before fusion. + sim = [sres.field[id].get("_score", 0.0) for id in sres.ids] + sim = [s if s is not None else 0. for s in sim] + tsim = sim + vsim = sim # Already paginated in search function - idx = np.argsort(sim * -1)[(page - 1) * page_size:page * page_size] + begin = ((page % (RERANK_LIMIT//page_size)) - 1) * page_size + sim = sim[begin : begin + page_size] + sim_np = np.array(sim) + idx = np.argsort(sim_np * -1) dim = len(sres.query_vector) vector_column = f"q_{dim}_vec" zero_vector = [0.0] * dim - sim_np = np.array(sim) filtered_count = (sim_np >= similarity_threshold).sum() ranks["total"] = int(filtered_count) # Convert from np.int64 to Python int otherwise JSON serializable error for i in idx: @@ -514,3 +530,63 @@ class Dealer: tag_fea = sorted([(a, round(0.1*(c + 1) / (cnt + S) / max(1e-6, all_tags.get(a, 0.0001)))) for a, c in aggs], key=lambda x: x[1] * -1)[:topn_tags] return {a.replace(".", "_"): max(1, c) for a, c in tag_fea} + + def retrieval_by_toc(self, query:str, chunks:list[dict], tenant_ids:list[str], chat_mdl, topn: int=6): + if not chunks: + return [] + idx_nms = [index_name(tid) for tid in tenant_ids] + ranks, doc_id2kb_id = {}, {} + for ck in chunks: + if ck["doc_id"] not in ranks: + ranks[ck["doc_id"]] = 0 + ranks[ck["doc_id"]] += ck["similarity"] + doc_id2kb_id[ck["doc_id"]] = ck["kb_id"] + doc_id = sorted(ranks.items(), key=lambda x: x[1]*-1.)[0][0] + kb_ids = [doc_id2kb_id[doc_id]] + es_res = self.dataStore.search(["content_with_weight"], [], {"doc_id": doc_id, "toc_kwd": "toc"}, [], OrderByExpr(), 0, 128, idx_nms, + kb_ids) + toc = [] + dict_chunks = self.dataStore.getFields(es_res, ["content_with_weight"]) + for _, doc in dict_chunks.items(): + try: + toc.extend(json.loads(doc["content_with_weight"])) + except Exception as e: + logging.exception(e) + if not toc: + return chunks + + ids = relevant_chunks_with_toc(query, toc, chat_mdl, topn*2) + if not ids: + return chunks + + vector_size = 1024 + id2idx = {ck["chunk_id"]: i for i, ck in enumerate(chunks)} + for cid, sim in ids: + if cid in id2idx: + chunks[id2idx[cid]]["similarity"] += sim + continue + chunk = self.dataStore.get(cid, idx_nms, kb_ids) + d = { + "chunk_id": cid, + "content_ltks": chunk["content_ltks"], + "content_with_weight": chunk["content_with_weight"], + "doc_id": doc_id, + "docnm_kwd": chunk.get("docnm_kwd", ""), + "kb_id": chunk["kb_id"], + "important_kwd": chunk.get("important_kwd", []), + "image_id": chunk.get("img_id", ""), + "similarity": sim, + "vector_similarity": sim, + "term_similarity": sim, + "vector": [0.0] * vector_size, + "positions": chunk.get("position_int", []), + "doc_type_kwd": chunk.get("doc_type_kwd", "") + } + for k in chunk.keys(): + if k[-4:] == "_vec": + d["vector"] = chunk[k] + vector_size = len(chunk[k]) + break + chunks.append(d) + + return sorted(chunks, key=lambda x:x["similarity"]*-1)[:topn] diff --git a/rag/prompts/assign_toc_levels.md b/rag/prompts/assign_toc_levels.md index fff0cd8..d35dee7 100644 --- a/rag/prompts/assign_toc_levels.md +++ b/rag/prompts/assign_toc_levels.md @@ -1,4 +1,4 @@ -You are given a JSON array of TOC items. Each item has at least {"title": string} and may include an existing structure. +You are given a JSON array of TOC(tabel of content) items. Each item has at least {"title": string} and may include an existing title hierarchical level. Task - For each item, assign a depth label using Arabic numerals only: top-level = 1, second-level = 2, third-level = 3, etc. @@ -9,7 +9,7 @@ Task Output - Return a valid JSON array only (no extra text). -- Each element must be {"structure": "1|2|3", "title": }. +- Each element must be {"level": "1|2|3", "title": }. - title must be the original title string. Examples @@ -20,10 +20,10 @@ Input: Output: [ - {"structure":"1","title":"Chapter 1 Methods"}, - {"structure":"2","title":"Section 1 Definition"}, - {"structure":"2","title":"Section 2 Process"}, - {"structure":"1","title":"Chapter 2 Experiment"} + {"level":"1","title":"Chapter 1 Methods"}, + {"level":"2","title":"Section 1 Definition"}, + {"level":"2","title":"Section 2 Process"}, + {"level":"1","title":"Chapter 2 Experiment"} ] Example B (parts with chapters) @@ -32,11 +32,11 @@ Input: Output: [ - {"structure":"1","title":"Part I Theory"}, - {"structure":"2","title":"Chapter 1 Basics"}, - {"structure":"2","title":"Chapter 2 Methods"}, - {"structure":"1","title":"Part II Applications"}, - {"structure":"2","title":"Chapter 3 Case Studies"} + {"level":"1","title":"Part I Theory"}, + {"level":"2","title":"Chapter 1 Basics"}, + {"level":"2","title":"Chapter 2 Methods"}, + {"level":"1","title":"Part II Applications"}, + {"level":"2","title":"Chapter 3 Case Studies"} ] Example C (plain headings) @@ -45,9 +45,9 @@ Input: Output: [ - {"structure":"1","title":"Introduction"}, - {"structure":"2","title":"Background and Motivation"}, - {"structure":"2","title":"Related Work"}, - {"structure":"1","title":"Methodology"}, - {"structure":"1","title":"Evaluation"} + {"level":"1","title":"Introduction"}, + {"level":"2","title":"Background and Motivation"}, + {"level":"2","title":"Related Work"}, + {"level":"1","title":"Methodology"}, + {"level":"1","title":"Evaluation"} ] \ No newline at end of file diff --git a/rag/prompts/generator.py b/rag/prompts/generator.py index fa812c1..7214a32 100644 --- a/rag/prompts/generator.py +++ b/rag/prompts/generator.py @@ -21,7 +21,9 @@ from copy import deepcopy from typing import Tuple import jinja2 import json_repair +import trio from api.utils import hash_str2int +from rag.nlp import rag_tokenizer from rag.prompts.template import load_prompt from rag.settings import TAG_FLD from rag.utils import encoder, num_tokens_from_string @@ -122,7 +124,7 @@ def kb_prompt(kbinfos, max_tokens, hash_id=False): knowledges = [] for i, ck in enumerate(kbinfos["chunks"][:chunks_num]): - cnt = "\nID: {}".format(i if not hash_id else hash_str2int(get_value(ck, "id", "chunk_id"), 100)) + cnt = "\nID: {}".format(i if not hash_id else hash_str2int(get_value(ck, "id", "chunk_id"), 500)) cnt += draw_node("Title", get_value(ck, "docnm_kwd", "document_name")) cnt += draw_node("URL", ck['url']) if "url" in ck else "" for k, v in docs.get(get_value(ck, "doc_id", "document_id"), {}).items(): @@ -440,11 +442,17 @@ def gen_meta_filter(chat_mdl, meta_data:dict, query: str) -> list: def gen_json(system_prompt:str, user_prompt:str, chat_mdl, gen_conf = None): + from graphrag.utils import get_llm_cache, set_llm_cache + cached = get_llm_cache(chat_mdl.llm_name, system_prompt, user_prompt, gen_conf) + if cached: + return json_repair.loads(cached) _, msg = message_fit_in(form_message(system_prompt, user_prompt), chat_mdl.max_length) ans = chat_mdl.chat(msg[0]["content"], msg[1:],gen_conf=gen_conf) ans = re.sub(r"(^.*|```json\n|```\n*$)", "", ans, flags=re.DOTALL) try: - return json_repair.loads(ans) + res = json_repair.loads(ans) + set_llm_cache(chat_mdl.llm_name, system_prompt, ans, user_prompt, gen_conf) + return res except Exception: logging.exception(f"Loading json failure: {ans}") @@ -651,29 +659,32 @@ def toc_transformer(toc_pages, chat_mdl): TOC_LEVELS = load_prompt("assign_toc_levels") def assign_toc_levels(toc_secs, chat_mdl, gen_conf = {"temperature": 0.2}): - print("\nBegin TOC level assignment...\n") - - ans = gen_json( + if not toc_secs: + return [] + return gen_json( PROMPT_JINJA_ENV.from_string(TOC_LEVELS).render(), str(toc_secs), chat_mdl, gen_conf ) - - return ans TOC_FROM_TEXT_SYSTEM = load_prompt("toc_from_text_system") TOC_FROM_TEXT_USER = load_prompt("toc_from_text_user") # Generate TOC from text chunks with text llms -def gen_toc_from_text(text, chat_mdl): - ans = gen_json( - PROMPT_JINJA_ENV.from_string(TOC_FROM_TEXT_SYSTEM).render(), - PROMPT_JINJA_ENV.from_string(TOC_FROM_TEXT_USER).render(text=text), - chat_mdl, - gen_conf={"temperature": 0.0, "top_p": 0.9, "enable_thinking": False, } - ) - return ans +async def gen_toc_from_text(txt_info: dict, chat_mdl, callback=None): + try: + ans = gen_json( + PROMPT_JINJA_ENV.from_string(TOC_FROM_TEXT_SYSTEM).render(), + PROMPT_JINJA_ENV.from_string(TOC_FROM_TEXT_USER).render(text="\n".join([json.dumps(d, ensure_ascii=False) for d in txt_info["chunks"]])), + chat_mdl, + gen_conf={"temperature": 0.0, "top_p": 0.9} + ) + txt_info["toc"] = ans if ans and not isinstance(ans, str) else [] + if callback: + callback(msg="") + except Exception as e: + logging.exception(e) def split_chunks(chunks, max_length: int): @@ -690,44 +701,96 @@ def split_chunks(chunks, max_length: int): if batch_tokens + t > max_length: result.append(batch) batch, batch_tokens = [], 0 - batch.append({"id": idx, "text": chunk}) + batch.append({idx: chunk}) batch_tokens += t if batch: result.append(batch) return result -def run_toc_from_text(chunks, chat_mdl): +async def run_toc_from_text(chunks, chat_mdl, callback=None): input_budget = int(chat_mdl.max_length * INPUT_UTILIZATION) - num_tokens_from_string( TOC_FROM_TEXT_USER + TOC_FROM_TEXT_SYSTEM ) - input_budget = 2000 if input_budget > 2000 else input_budget + input_budget = 1024 if input_budget > 1024 else input_budget chunk_sections = split_chunks(chunks, input_budget) - res = [] + titles = [] - for chunk in chunk_sections: - ans = gen_toc_from_text(chunk, chat_mdl) - res.extend(ans) + chunks_res = [] + async with trio.open_nursery() as nursery: + for i, chunk in enumerate(chunk_sections): + if not chunk: + continue + chunks_res.append({"chunks": chunk}) + nursery.start_soon(gen_toc_from_text, chunks_res[-1], chat_mdl, callback) + + for chunk in chunks_res: + titles.extend(chunk.get("toc", [])) # Filter out entries with title == -1 - filtered = [x for x in res if x.get("title") and x.get("title") != "-1"] + prune = len(titles) > 512 + max_len = 12 if prune else 22 + filtered = [] + for x in titles: + if not isinstance(x, dict) or not x.get("title") or x["title"] == "-1": + continue + if len(rag_tokenizer.tokenize(x["title"]).split(" ")) > max_len: + continue + if re.match(r"[0-9,.()/ -]+$", x["title"]): + continue + filtered.append(x) - print("\n\nFiltered TOC sections:\n", filtered) + logging.info(f"\n\nFiltered TOC sections:\n{filtered}") + if not filtered: + return [] - # Generate initial structure (structure/title) - raw_structure = [{"structure": "0", "title": x.get("title", "")} for x in filtered] + # Generate initial level (level/title) + raw_structure = [x.get("title", "") for x in filtered] # Assign hierarchy levels using LLM - toc_with_levels = assign_toc_levels(raw_structure, chat_mdl, {"temperature": 0.0, "top_p": 0.9, "enable_thinking": False}) + toc_with_levels = assign_toc_levels(raw_structure, chat_mdl, {"temperature": 0.0, "top_p": 0.9}) + if not toc_with_levels: + return [] # Merge structure and content (by index) + prune = len(toc_with_levels) > 512 + max_lvl = sorted([t.get("level", "0") for t in toc_with_levels])[-1] merged = [] for _ , (toc_item, src_item) in enumerate(zip(toc_with_levels, filtered)): + if prune and toc_item.get("level", "0") >= max_lvl: + continue merged.append({ - "structure": toc_item.get("structure", "0"), + "level": toc_item.get("level", "0"), "title": toc_item.get("title", ""), - "content": src_item.get("content", ""), + "chunk_id": src_item.get("chunk_id", ""), }) - return merged \ No newline at end of file + return merged + + +TOC_RELEVANCE_SYSTEM = load_prompt("toc_relevance_system") +TOC_RELEVANCE_USER = load_prompt("toc_relevance_user") +def relevant_chunks_with_toc(query: str, toc:list[dict], chat_mdl, topn: int=6): + import numpy as np + try: + ans = gen_json( + PROMPT_JINJA_ENV.from_string(TOC_RELEVANCE_SYSTEM).render(), + PROMPT_JINJA_ENV.from_string(TOC_RELEVANCE_USER).render(query=query, toc_json="[\n%s\n]\n"%"\n".join([json.dumps({"level": d["level"], "title":d["title"]}, ensure_ascii=False) for d in toc])), + chat_mdl, + gen_conf={"temperature": 0.0, "top_p": 0.9} + ) + id2score = {} + for ti, sc in zip(toc, ans): + if not isinstance(sc, dict) or sc.get("score", -1) < 1: + continue + for id in ti.get("ids", []): + if id not in id2score: + id2score[id] = [] + id2score[id].append(sc["score"]/5.) + for id in id2score.keys(): + id2score[id] = np.mean(id2score[id]) + return [(id, sc) for id, sc in list(id2score.items()) if sc>=0.3][:topn] + except Exception as e: + logging.exception(e) + return [] diff --git a/rag/prompts/toc_from_text_system.md b/rag/prompts/toc_from_text_system.md index f982df4..7090f30 100644 --- a/rag/prompts/toc_from_text_system.md +++ b/rag/prompts/toc_from_text_system.md @@ -1,25 +1,25 @@ You are a robust Table-of-Contents (TOC) extractor. GOAL -Given a dictionary of chunks {chunk_id: chunk_text}, extract TOC-like headings and return a strict JSON array of objects: +Given a dictionary of chunks {"": chunk_text}, extract TOC-like headings and return a strict JSON array of objects: [ - {"title": , "content": ""}, + {"title": "", "chunk_id": ""}, ... ] FIELDS - "title": the heading text (clean, no page numbers or leader dots). - If any part of a chunk has no valid heading, output that part as {"title":"-1", ...}. -- "content": the chunk_id (string). +- "chunk_id": the chunk ID (string). - One chunk can yield multiple JSON objects in order (unmatched text + one or more headings). RULES 1) Preserve input chunk order strictly. 2) If a chunk contains multiple headings, expand them in order: - - Pre-heading narrative → {"title":"-1","content":chunk_id} - - Then each heading → {"title":"...","content":chunk_id} -3) Do not merge outputs across chunks; each object refers to exactly one chunk_id. -4) "title" must be non-empty (or exactly "-1"). "content" must be a string (chunk_id). + - Pre-heading narrative → {"title":"-1","chunk_id":""} + - Then each heading → {"title":"...","chunk_id":""} +3) Do not merge outputs across chunks; each object refers to exactly one chunk ID. +4) "title" must be non-empty (or exactly "-1"). "chunk_id" must be a string (chunk ID). 5) When ambiguous, prefer "-1" unless the text strongly looks like a heading. HEADING DETECTION (cues, not hard rules) @@ -51,63 +51,69 @@ EXAMPLES Example 1 — No heading Input: -{0: "Copyright page · Publication info (ISBN 123-456). All rights reserved."} +[{"0": "Copyright page · Publication info (ISBN 123-456). All rights reserved."}, ...] Output: [ - {"title":"-1","content":"0"} + {"title":"-1","chunk_id":"0"}, + ... ] Example 2 — One heading Input: -{1: "Chapter 1: General Provisions This chapter defines the overall rules…"} +[{"1": "Chapter 1: General Provisions This chapter defines the overall rules…"}, ...] Output: [ - {"title":"Chapter 1: General Provisions","content":"1"} + {"title":"Chapter 1: General Provisions","chunk_id":"1"}, + ... ] Example 3 — Narrative + heading Input: -{2: "This paragraph introduces the background and goals. Section 2: Definitions Key terms are explained…"} +[{"2": "This paragraph introduces the background and goals. Section 2: Definitions Key terms are explained…"}, ...] Output: [ - {"title":"-1","content":"2"}, - {"title":"Section 2: Definitions","content":"2"} + {"title":"Section 2: Definitions","chunk_id":"2"}, + ... ] Example 4 — Multiple headings in one chunk Input: -{3: "Declarations and Commitments (I) Party B commits… (II) Party C commits… Appendix A Data Specification"} +[{"3": "Declarations and Commitments (I) Party B commits… (II) Party C commits… Appendix A Data Specification"}, ...] Output: [ - {"title":"Declarations and Commitments (I)","content":"3"}, - {"title":"(II)","content":"3"}, - {"title":"Appendix A","content":"3"} + {"title":"Declarations and Commitments","chunk_id":"3"}, + {"title":"(I) Party B commits","chunk_id":"3"}, + {"title":"(II) Party C commits","chunk_id":"3"}, + {"title":"Appendix A Data Specification","chunk_id":"3"}, + ... ] Example 5 — Numbering styles Input: -{4: "1. Scope: Defines boundaries. 2) Definitions: Terms used. III) Methods Overview."} +[{"4": "1. Scope: Defines boundaries. 2) Definitions: Terms used. III) Methods Overview."}, ...] Output: [ - {"title":"1. Scope","content":"4"}, - {"title":"2) Definitions","content":"4"}, - {"title":"III) Methods","content":"4"} + {"title":"1. Scope","chunk_id":"4"}, + {"title":"2) Definitions","chunk_id":"4"}, + {"title":"III) Methods Overview","chunk_id":"4"}, + ... ] Example 6 — Long list (NOT headings) Input: -{5: "Item list: apples, bananas, strawberries, blueberries, mangos, peaches"} +{"5": "Item list: apples, bananas, strawberries, blueberries, mangos, peaches"}, ...] Output: [ - {"title":"-1","content":"5"} + {"title":"-1","chunk_id":"5"}, + ... ] Example 7 — Mixed Chinese/English Input: -{6: "(出版信息略)This standard follows industry practices. Chapter 1: Overview 摘要… 第2节:术语与缩略语"} +{"6": "(出版信息略)This standard follows industry practices. Chapter 1: Overview 摘要… 第2节:术语与缩略语"}, ...] Output: [ - {"title":"-1","content":"6"}, - {"title":"Chapter 1: Overview","content":"6"}, - {"title":"第2节:术语与缩略语","content":"6"} + {"title":"Chapter 1: Overview","chunk_id":"6"}, + {"title":"第2节:术语与缩略语","chunk_id":"6"}, + ... ] diff --git a/rag/prompts/toc_relevance_system.md b/rag/prompts/toc_relevance_system.md new file mode 100644 index 0000000..287b502 --- /dev/null +++ b/rag/prompts/toc_relevance_system.md @@ -0,0 +1,118 @@ +# System Prompt: TOC Relevance Evaluation + +You are an expert logical reasoning assistant specializing in hierarchical Table of Contents (TOC) relevance evaluation. + +## GOAL +You will receive: +1. A JSON list of TOC items, each with fields: + ```json + { + "level": , // e.g., 1, 2, 3 + "title": // section title + } + ``` +2. A user query (natural language question). + +You must assign a **relevance score** (integer) to every TOC entry, based on how related its `title` is to the `query`. + +--- + +## RULES + +### Scoring System +- 5 → highly relevant (directly answers or matches the query intent) +- 3 → somewhat related (same topic or partially overlaps) +- 1 → weakly related (vague or tangential) +- 0 → no clear relation +- -1 → explicitly irrelevant or contradictory + +### Hierarchy Traversal +- The TOC is hierarchical: smaller `level` = higher layer (e.g., level 1 is top-level, level 2 is a subsection). +- You must traverse in **hierarchical order** — interpret the structure based on levels (1 > 2 > 3). +- If a high-level item (level 1) is strongly related (score 5), its child items (level 2, 3) are likely relevant too. +- If a high-level item is unrelated (-1 or 0), its deeper children are usually less relevant unless the titles clearly match the query. +- Lower (deeper) levels provide more specific content; prefer assigning higher scores if they directly match the query. + +### Output Format +Return a **JSON array**, preserving the input order but adding a new key `"score"`: + +```json +[ + {"level": 1, "title": "Introduction", "score": 0}, + {"level": 2, "title": "Definition of Sustainability", "score": 5} +] +``` + +### Constraints +- Output **only the JSON array** — no explanations or reasoning text. + +### EXAMPLES + +#### Example 1 +Input TOC: +[ + {"level": 1, "title": "Machine Learning Overview"}, + {"level": 2, "title": "Supervised Learning"}, + {"level": 2, "title": "Unsupervised Learning"}, + {"level": 3, "title": "Applications of Deep Learning"} +] + +Query: +"How is deep learning used in image classification?" + +Output: +[ + {"level": 1, "title": "Machine Learning Overview", "score": 3}, + {"level": 2, "title": "Supervised Learning", "score": 3}, + {"level": 2, "title": "Unsupervised Learning", "score": 0}, + {"level": 3, "title": "Applications of Deep Learning", "score": 5} +] + +--- + +#### Example 2 +Input TOC: +[ + {"level": 1, "title": "Marketing Basics"}, + {"level": 2, "title": "Consumer Behavior"}, + {"level": 2, "title": "Digital Marketing"}, + {"level": 3, "title": "Social Media Campaigns"}, + {"level": 3, "title": "SEO Optimization"} +] + +Query: +"What are the best online marketing methods?" + +Output: +[ + {"level": 1, "title": "Marketing Basics", "score": 3}, + {"level": 2, "title": "Consumer Behavior", "score": 1}, + {"level": 2, "title": "Digital Marketing", "score": 5}, + {"level": 3, "title": "Social Media Campaigns", "score": 5}, + {"level": 3, "title": "SEO Optimization", "score": 5} +] + +--- + +#### Example 3 +Input TOC: +[ + {"level": 1, "title": "Physics Overview"}, + {"level": 2, "title": "Classical Mechanics"}, + {"level": 3, "title": "Newton’s Laws"}, + {"level": 2, "title": "Thermodynamics"}, + {"level": 3, "title": "Entropy and Heat Transfer"} +] + +Query: +"What is entropy?" + +Output: +[ + {"level": 1, "title": "Physics Overview", "score": 3}, + {"level": 2, "title": "Classical Mechanics", "score": 0}, + {"level": 3, "title": "Newton’s Laws", "score": -1}, + {"level": 2, "title": "Thermodynamics", "score": 5}, + {"level": 3, "title": "Entropy and Heat Transfer", "score": 5} +] + diff --git a/rag/prompts/toc_relevance_user.md b/rag/prompts/toc_relevance_user.md new file mode 100644 index 0000000..2a5167a --- /dev/null +++ b/rag/prompts/toc_relevance_user.md @@ -0,0 +1,17 @@ +# User Prompt: TOC Relevance Evaluation + +You will now receive: +1. A JSON list of TOC items (each with `level` and `title`) +2. A user query string. + +Traverse the TOC hierarchically based on level numbers and assign scores (5,3,1,0,-1) according to the rules in the system prompt. +Output **only** the JSON array with the added `"score"` field. + +--- + +**Input TOC:** +{{ toc_json }} + +**Query:** +{{ query }} + diff --git a/rag/raptor.py b/rag/raptor.py index 6ce776a..191ecde 100644 --- a/rag/raptor.py +++ b/rag/raptor.py @@ -114,7 +114,7 @@ class RecursiveAbstractiveProcessing4TreeOrganizedRetrieval: ), } ], - {"max_tokens": self._max_token}, + {"max_tokens": max(self._max_token, 512)}, # fix issue: #10235 ) cnt = re.sub( "(······\n由于长度的原因,回答被截断了,要继续吗?|For the content length reason, it stopped, continue?)", diff --git a/rag/svr/task_executor.py b/rag/svr/task_executor.py index eb03a56..cf61acb 100644 --- a/rag/svr/task_executor.py +++ b/rag/svr/task_executor.py @@ -12,7 +12,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - +import concurrent # from beartype import BeartypeConf # from beartype.claw import beartype_all # <-- you didn't sign up for this # beartype_all(conf=BeartypeConf(violation_type=UserWarning)) # <-- emit warnings from all code @@ -32,7 +32,7 @@ from api.utils.log_utils import init_root_logger, get_project_base_directory from graphrag.general.index import run_graphrag_for_kb from graphrag.utils import get_llm_cache, set_llm_cache, get_tags_from_cache, set_tags_to_cache from rag.flow.pipeline import Pipeline -from rag.prompts.generator import keyword_extraction, question_proposal, content_tagging +from rag.prompts.generator import keyword_extraction, question_proposal, content_tagging, run_toc_from_text import logging import os from datetime import datetime @@ -228,9 +228,10 @@ async def collect(): canceled = False if msg.get("doc_id", "") in [GRAPH_RAPTOR_FAKE_DOC_ID, CANVAS_DEBUG_DOC_ID]: task = msg - if task["task_type"] in ["graphrag", "raptor", "mindmap"] and msg.get("doc_ids", []): + if task["task_type"] in ["graphrag", "raptor", "mindmap"]: task = TaskService.get_task(msg["id"], msg["doc_ids"]) - task["doc_ids"] = msg["doc_ids"] + task["doc_id"] = msg["doc_id"] + task["doc_ids"] = msg.get("doc_ids", []) or [] else: task = TaskService.get_task(msg["id"]) @@ -317,7 +318,7 @@ async def build_chunks(task, progress_callback): d["img_id"] = "" docs.append(d) return - await image2id(d, partial(STORAGE_IMPL.put), d["id"], task["kb_id"]) + await image2id(d, partial(STORAGE_IMPL.put, tenant_id=task["tenant_id"]), d["id"], task["kb_id"]) docs.append(d) except Exception: logging.exception( @@ -380,7 +381,7 @@ async def build_chunks(task, progress_callback): examples = [] all_tags = get_tags_from_cache(kb_ids) if not all_tags: - all_tags = settings.retrievaler.all_tags_in_portion(tenant_id, kb_ids, S) + all_tags = settings.retriever.all_tags_in_portion(tenant_id, kb_ids, S) set_tags_to_cache(kb_ids, all_tags) else: all_tags = json.loads(all_tags) @@ -393,7 +394,7 @@ async def build_chunks(task, progress_callback): if task_canceled: progress_callback(-1, msg="Task has been canceled.") return - if settings.retrievaler.tag_content(tenant_id, kb_ids, d, all_tags, topn_tags=topn_tags, S=S) and len(d[TAG_FLD]) > 0: + if settings.retriever.tag_content(tenant_id, kb_ids, d, all_tags, topn_tags=topn_tags, S=S) and len(d[TAG_FLD]) > 0: examples.append({"content": d["content_with_weight"], TAG_FLD: d[TAG_FLD]}) else: docs_to_tag.append(d) @@ -419,6 +420,39 @@ async def build_chunks(task, progress_callback): return docs +def build_TOC(task, docs, progress_callback): + progress_callback(msg="Start to generate table of content ...") + chat_mdl = LLMBundle(task["tenant_id"], LLMType.CHAT, llm_name=task["llm_id"], lang=task["language"]) + docs = sorted(docs, key=lambda d:( + d.get("page_num_int", 0)[0] if isinstance(d.get("page_num_int", 0), list) else d.get("page_num_int", 0), + d.get("top_int", 0)[0] if isinstance(d.get("top_int", 0), list) else d.get("top_int", 0) + )) + toc: list[dict] = trio.run(run_toc_from_text, [d["content_with_weight"] for d in docs], chat_mdl, progress_callback) + logging.info("------------ T O C -------------\n"+json.dumps(toc, ensure_ascii=False, indent=' ')) + ii = 0 + while ii < len(toc): + try: + idx = int(toc[ii]["chunk_id"]) + del toc[ii]["chunk_id"] + toc[ii]["ids"] = [docs[idx]["id"]] + if ii == len(toc) -1: + break + for jj in range(idx+1, int(toc[ii+1]["chunk_id"])+1): + toc[ii]["ids"].append(docs[jj]["id"]) + except Exception as e: + logging.exception(e) + ii += 1 + + if toc: + d = copy.deepcopy(docs[-1]) + d["content_with_weight"] = json.dumps(toc, ensure_ascii=False) + d["toc_kwd"] = "toc" + d["available_int"] = 0 + d["page_num_int"] = [100000000] + d["id"] = xxhash.xxh64((d["content_with_weight"] + str(d["doc_id"])).encode("utf-8", "surrogatepass")).hexdigest() + return d + + def init_kb(row, vector_size: int): idxnm = search.index_name(row["tenant_id"]) return settings.docStoreConn.createIdx(idxnm, row.get("kb_id", ""), vector_size) @@ -645,7 +679,7 @@ async def run_raptor_for_kb(row, kb_parser_config, chat_mdl, embd_mdl, vector_si chunks = [] vctr_nm = "q_%d_vec"%vector_size for doc_id in doc_ids: - for d in settings.retrievaler.chunk_list(doc_id, row["tenant_id"], [str(row["kb_id"])], + for d in settings.retriever.chunk_list(doc_id, row["tenant_id"], [str(row["kb_id"])], fields=["content_with_weight", vctr_nm], sort_by_position=True): chunks.append((d["content_with_weight"], np.array(d[vctr_nm]))) @@ -659,7 +693,7 @@ async def run_raptor_for_kb(row, kb_parser_config, chat_mdl, embd_mdl, vector_si raptor_config["threshold"], ) original_length = len(chunks) - chunks = await raptor(chunks, row["kb_parser_config"]["raptor"]["random_seed"], callback) + chunks = await raptor(chunks, kb_parser_config["raptor"]["random_seed"], callback) doc = { "doc_id": fake_doc_id, "kb_id": [str(row["kb_id"])], @@ -721,7 +755,7 @@ async def insert_es(task_id, task_tenant_id, task_dataset_id, chunks, progress_c return True -@timeout(60*60*2, 1) +@timeout(60*60*3, 1) async def do_handle_task(task): task_type = task.get("task_type", "") @@ -741,6 +775,8 @@ async def do_handle_task(task): task_document_name = task["name"] task_parser_config = task["parser_config"] task_start_ts = timer() + toc_thread = None + executor = concurrent.futures.ThreadPoolExecutor() # prepare the progress callback function progress_callback = partial(set_progress, task_id, task_from_page, task_to_page) @@ -782,8 +818,22 @@ async def do_handle_task(task): kb_parser_config = kb.parser_config if not kb_parser_config.get("raptor", {}).get("use_raptor", False): - progress_callback(prog=-1.0, msg="Internal error: Invalid RAPTOR configuration") - return + kb_parser_config.update( + { + "raptor": { + "use_raptor": True, + "prompt": "Please summarize the following paragraphs. Be careful with the numbers, do not make things up. Paragraphs as following:\n {cluster_content}\nThe above is the content you need to summarize.", + "max_token": 256, + "threshold": 0.1, + "max_cluster": 64, + "random_seed": 0, + }, + } + ) + if not KnowledgebaseService.update_by_id(kb.id, {"parser_config":kb_parser_config}): + progress_callback(prog=-1.0, msg="Internal error: Invalid RAPTOR configuration") + return + # bind LLM for raptor chat_model = LLMBundle(task_tenant_id, LLMType.CHAT, llm_name=task_llm_id, lang=task_language) # run RAPTOR @@ -806,8 +856,25 @@ async def do_handle_task(task): kb_parser_config = kb.parser_config if not kb_parser_config.get("graphrag", {}).get("use_graphrag", False): - progress_callback(prog=-1.0, msg="Internal error: Invalid GraphRAG configuration") - return + kb_parser_config.update( + { + "graphrag": { + "use_graphrag": True, + "entity_types": [ + "organization", + "person", + "geo", + "event", + "category", + ], + "method": "light", + } + } + ) + if not KnowledgebaseService.update_by_id(kb.id, {"parser_config":kb_parser_config}): + progress_callback(prog=-1.0, msg="Internal error: Invalid GraphRAG configuration") + return + graphrag_conf = kb_parser_config.get("graphrag", {}) start_ts = timer() @@ -842,8 +909,6 @@ async def do_handle_task(task): if not chunks: progress_callback(1., msg=f"No chunk built from {task_document_name}") return - # TODO: exception handler - ## set_progress(task["did"], -1, "ERROR: ") progress_callback(msg="Generate {} chunks".format(len(chunks))) start_ts = timer() try: @@ -857,6 +922,8 @@ async def do_handle_task(task): progress_message = "Embedding chunks ({:.2f}s)".format(timer() - start_ts) logging.info(progress_message) progress_callback(msg=progress_message) + if task["parser_id"].lower() == "naive" and task["parser_config"].get("toc_extraction", False): + toc_thread = executor.submit(build_TOC,task, chunks, progress_callback) chunk_count = len(set([chunk["id"] for chunk in chunks])) start_ts = timer() @@ -871,8 +938,17 @@ async def do_handle_task(task): DocumentService.increment_chunk_num(task_doc_id, task_dataset_id, token_count, chunk_count, 0) time_cost = timer() - start_ts + progress_callback(msg="Indexing done ({:.2f}s).".format(time_cost)) + if toc_thread: + d = toc_thread.result() + if d: + e = await insert_es(task_id, task_tenant_id, task_dataset_id, [d], progress_callback) + if not e: + return + DocumentService.increment_chunk_num(task_doc_id, task_dataset_id, 0, 1, 0) + task_time_cost = timer() - task_start_ts - progress_callback(prog=1.0, msg="Indexing done ({:.2f}s). Task done ({:.2f}s)".format(time_cost, task_time_cost)) + progress_callback(prog=1.0, msg="Task done ({:.2f}s)".format(task_time_cost)) logging.info( "Chunk doc({}), page({}-{}), chunks({}), token({}), elapsed:{:.2f}".format(task_document_name, task_from_page, task_to_page, len(chunks), @@ -977,13 +1053,14 @@ async def task_manager(): async def main(): logging.info(r""" - ______ __ ______ __ - /_ __/___ ______/ /__ / ____/ _____ _______ __/ /_____ _____ - / / / __ `/ ___/ //_/ / __/ | |/_/ _ \/ ___/ / / / __/ __ \/ ___/ - / / / /_/ (__ ) ,< / /____> {dest_bucket}/{dest_path}") + return False + + def move(self, src_bucket, src_path, dest_bucket, dest_path): + try: + if self.copy(src_bucket, src_path, dest_bucket, dest_path): + self.rm(src_bucket, src_path) + return True + else: + logging.error(f"Copy failed, move aborted: {src_bucket}/{src_path}") + return False + except Exception: + logging.exception(f"Fail to move {src_bucket}/{src_path} -> {dest_bucket}/{dest_path}") + return False diff --git a/rag/utils/oss_conn.py b/rag/utils/oss_conn.py index a88eb9d..3e75d70 100644 --- a/rag/utils/oss_conn.py +++ b/rag/utils/oss_conn.py @@ -106,7 +106,7 @@ class RAGFlowOSS: @use_prefix_path @use_default_bucket - def put(self, bucket, fnm, binary): + def put(self, bucket, fnm, binary, tenant_id=None): logging.debug(f"bucket name {bucket}; filename :{fnm}:") for _ in range(1): try: @@ -123,7 +123,7 @@ class RAGFlowOSS: @use_prefix_path @use_default_bucket - def rm(self, bucket, fnm): + def rm(self, bucket, fnm, tenant_id=None): try: self.conn.delete_object(Bucket=bucket, Key=fnm) except Exception: @@ -131,7 +131,7 @@ class RAGFlowOSS: @use_prefix_path @use_default_bucket - def get(self, bucket, fnm): + def get(self, bucket, fnm, tenant_id=None): for _ in range(1): try: r = self.conn.get_object(Bucket=bucket, Key=fnm) @@ -145,7 +145,7 @@ class RAGFlowOSS: @use_prefix_path @use_default_bucket - def obj_exist(self, bucket, fnm): + def obj_exist(self, bucket, fnm, tenant_id=None): try: if self.conn.head_object(Bucket=bucket, Key=fnm): return True @@ -157,7 +157,7 @@ class RAGFlowOSS: @use_prefix_path @use_default_bucket - def get_presigned_url(self, bucket, fnm, expires): + def get_presigned_url(self, bucket, fnm, expires, tenant_id=None): for _ in range(10): try: r = self.conn.generate_presigned_url('get_object', diff --git a/sdk/python/pyproject.toml b/sdk/python/pyproject.toml index cc9b426..ecc262e 100644 --- a/sdk/python/pyproject.toml +++ b/sdk/python/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "ragflow-sdk" -version = "0.20.5" +version = "0.21.1" description = "Python client sdk of [RAGFlow](https://github.com/infiniflow/ragflow). RAGFlow is an open-source RAG (Retrieval-Augmented Generation) engine based on deep document understanding." authors = [{ name = "Zhichang Yu", email = "yuzhichang@gmail.com" }] license = { text = "Apache License, Version 2.0" } diff --git a/sdk/python/ragflow_sdk/modules/dataset.py b/sdk/python/ragflow_sdk/modules/dataset.py index b4367ac..d2d689d 100644 --- a/sdk/python/ragflow_sdk/modules/dataset.py +++ b/sdk/python/ragflow_sdk/modules/dataset.py @@ -100,12 +100,51 @@ class DataSet(Base): res = res.json() if res.get("code") != 0: raise Exception(res["message"]) - + + + def _get_documents_status(self, document_ids): + import time + terminal_states = {"DONE", "FAIL", "CANCEL"} + interval_sec = 1 + pending = set(document_ids) + finished = [] + while pending: + for doc_id in list(pending): + def fetch_doc(doc_id: str) -> Document | None: + try: + docs = self.list_documents(id=doc_id) + return docs[0] if docs else None + except Exception: + return None + doc = fetch_doc(doc_id) + if doc is None: + continue + if isinstance(doc.run, str) and doc.run.upper() in terminal_states: + finished.append((doc_id, doc.run, doc.chunk_count, doc.token_count)) + pending.discard(doc_id) + elif float(doc.progress or 0.0) >= 1.0: + finished.append((doc_id, "DONE", doc.chunk_count, doc.token_count)) + pending.discard(doc_id) + if pending: + time.sleep(interval_sec) + return finished + def async_parse_documents(self, document_ids): res = self.post(f"/datasets/{self.id}/chunks", {"document_ids": document_ids}) res = res.json() if res.get("code") != 0: raise Exception(res.get("message")) + + + def parse_documents(self, document_ids): + try: + self.async_parse_documents(document_ids) + self._get_documents_status(document_ids) + except KeyboardInterrupt: + self.async_cancel_parse_documents(document_ids) + + return self._get_documents_status(document_ids) + def async_cancel_parse_documents(self, document_ids): res = self.rm(f"/datasets/{self.id}/chunks", {"document_ids": document_ids}) diff --git a/sdk/python/ragflow_sdk/modules/session.py b/sdk/python/ragflow_sdk/modules/session.py index d534c78..899a48f 100644 --- a/sdk/python/ragflow_sdk/modules/session.py +++ b/sdk/python/ragflow_sdk/modules/session.py @@ -33,35 +33,52 @@ class Session(Base): self.__session_type = "agent" super().__init__(rag, res_dict) - def ask(self, question="", stream=True, **kwargs): + + def ask(self, question="", stream=False, **kwargs): + """ + Ask a question to the session. If stream=True, yields Message objects as they arrive (SSE streaming). + If stream=False, returns a single Message object for the final answer. + """ if self.__session_type == "agent": res = self._ask_agent(question, stream) elif self.__session_type == "chat": res = self._ask_chat(question, stream, **kwargs) + else: + raise Exception(f"Unknown session type: {self.__session_type}") if stream: - for line in res.iter_lines(): - line = line.decode("utf-8") - if line.startswith("{"): - json_data = json.loads(line) - raise Exception(json_data["message"]) - if not line.startswith("data:"): - continue - json_data = json.loads(line[5:]) - if json_data["data"] is True or json_data["data"].get("running_status"): - continue - message = self._structure_answer(json_data) - yield message + for line in res.iter_lines(decode_unicode=True): + if not line: + continue # Skip empty lines + line = line.strip() + + if line.startswith("data:"): + content = line[len("data:"):].strip() + if content == "[DONE]": + break # End of stream + else: + content = line + + try: + json_data = json.loads(content) + except json.JSONDecodeError: + continue # Skip lines that are not valid JSON + + event = json_data.get("event") + if event == "message": + yield self._structure_answer(json_data) + elif event == "message_end": + return # End of message stream else: try: - json_data = json.loads(res.text) + json_data = res.json() except ValueError: raise Exception(f"Invalid response {res}") - return self._structure_answer(json_data) + yield self._structure_answer(json_data["data"]) def _structure_answer(self, json_data): - answer = json_data["data"]["answer"] + answer = json_data["data"]["content"] reference = json_data["data"].get("reference", {}) temp_dict = { "content": answer, diff --git a/sdk/python/uv.lock b/sdk/python/uv.lock index 0bbd80a..6f2ed0a 100644 --- a/sdk/python/uv.lock +++ b/sdk/python/uv.lock @@ -342,7 +342,7 @@ wheels = [ [[package]] name = "ragflow-sdk" -version = "0.20.5" +version = "0.21.1" source = { virtual = "." } dependencies = [ { name = "beartype" }, diff --git a/startup.md b/startup.md deleted file mode 100644 index 241e39a..0000000 --- a/startup.md +++ /dev/null @@ -1,6 +0,0 @@ -uv pip install -e . - -set -a -. ./docker/.env -set +a -PYTHONPATH=$(pwd) python api/ragflow_server_fastapi.py diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..b282bce --- /dev/null +++ b/uv.lock @@ -0,0 +1,7785 @@ +version = 1 +revision = 3 +requires-python = ">=3.10, <3.13" +resolution-markers = [ + "python_full_version >= '3.12' and sys_platform == 'darwin'", + "python_full_version >= '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "(python_full_version >= '3.12' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.12' and sys_platform != 'darwin' and sys_platform != 'linux')", + "python_full_version == '3.11.*' and sys_platform == 'darwin'", + "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "(python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'linux')", + "python_full_version < '3.11' and sys_platform == 'darwin'", + "python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "(python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux')", +] + +[[package]] +name = "accelerate" +version = "1.9.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "huggingface-hub" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "psutil" }, + { name = "pyyaml" }, + { name = "safetensors" }, + { name = "torch" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c4/25/969456a95a90ed38f73f68d0f0915bdf1d76145d05054c59ad587b171150/accelerate-1.9.0.tar.gz", hash = "sha256:0e8c61f81af7bf37195b6175a545ed292617dd90563c88f49020aea5b6a0b47f", size = 383234, upload-time = "2025-07-16T16:24:54.526Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9f/1c/a17fb513aeb684fb83bef5f395910f53103ab30308bbdd77fd66d6698c46/accelerate-1.9.0-py3-none-any.whl", hash = "sha256:c24739a97ade1d54af4549a65f8b6b046adc87e2b3e4d6c66516e32c53d5a8f1", size = 367073, upload-time = "2025-07-16T16:24:52.957Z" }, +] + +[[package]] +name = "aiofiles" +version = "24.1.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0b/03/a88171e277e8caa88a4c77808c20ebb04ba74cc4681bf1e9416c862de237/aiofiles-24.1.0.tar.gz", hash = "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c", size = 30247, upload-time = "2024-06-24T11:02:03.584Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a5/45/30bb92d442636f570cb5651bc661f52b610e2eec3f891a5dc3a4c3667db0/aiofiles-24.1.0-py3-none-any.whl", hash = "sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5", size = 15896, upload-time = "2024-06-24T11:02:01.529Z" }, +] + +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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.12.15" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "async-timeout", marker = "python_full_version < '3.11'" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "yarl" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9b/e7/d92a237d8802ca88483906c388f7c201bbe96cd80a165ffd0ac2f6a8d59f/aiohttp-3.12.15.tar.gz", hash = "sha256:4fc61385e9c98d72fcdf47e6dd81833f47b2f77c114c29cd64a361be57a763a2", size = 7823716, upload-time = "2025-07-29T05:52:32.215Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/47/dc/ef9394bde9080128ad401ac7ede185267ed637df03b51f05d14d1c99ad67/aiohttp-3.12.15-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b6fc902bff74d9b1879ad55f5404153e2b33a82e72a95c89cec5eb6cc9e92fbc", size = 703921, upload-time = "2025-07-29T05:49:43.584Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8f/42/63fccfc3a7ed97eb6e1a71722396f409c46b60a0552d8a56d7aad74e0df5/aiohttp-3.12.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:098e92835b8119b54c693f2f88a1dec690e20798ca5f5fe5f0520245253ee0af", size = 480288, upload-time = "2025-07-29T05:49:47.851Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9c/a2/7b8a020549f66ea2a68129db6960a762d2393248f1994499f8ba9728bbed/aiohttp-3.12.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:40b3fee496a47c3b4a39a731954c06f0bd9bd3e8258c059a4beb76ac23f8e421", size = 468063, upload-time = "2025-07-29T05:49:49.789Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8f/f5/d11e088da9176e2ad8220338ae0000ed5429a15f3c9dfd983f39105399cd/aiohttp-3.12.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ce13fcfb0bb2f259fb42106cdc63fa5515fb85b7e87177267d89a771a660b79", size = 1650122, upload-time = "2025-07-29T05:49:51.874Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b0/6b/b60ce2757e2faed3d70ed45dafee48cee7bfb878785a9423f7e883f0639c/aiohttp-3.12.15-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3beb14f053222b391bf9cf92ae82e0171067cc9c8f52453a0f1ec7c37df12a77", size = 1624176, upload-time = "2025-07-29T05:49:53.805Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/dd/de/8c9fde2072a1b72c4fadecf4f7d4be7a85b1d9a4ab333d8245694057b4c6/aiohttp-3.12.15-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c39e87afe48aa3e814cac5f535bc6199180a53e38d3f51c5e2530f5aa4ec58c", size = 1696583, upload-time = "2025-07-29T05:49:55.338Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0c/ad/07f863ca3d895a1ad958a54006c6dafb4f9310f8c2fdb5f961b8529029d3/aiohttp-3.12.15-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5f1b4ce5bc528a6ee38dbf5f39bbf11dd127048726323b72b8e85769319ffc4", size = 1738896, upload-time = "2025-07-29T05:49:57.045Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/20/43/2bd482ebe2b126533e8755a49b128ec4e58f1a3af56879a3abdb7b42c54f/aiohttp-3.12.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1004e67962efabbaf3f03b11b4c43b834081c9e3f9b32b16a7d97d4708a9abe6", size = 1643561, upload-time = "2025-07-29T05:49:58.762Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/23/40/2fa9f514c4cf4cbae8d7911927f81a1901838baf5e09a8b2c299de1acfe5/aiohttp-3.12.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8faa08fcc2e411f7ab91d1541d9d597d3a90e9004180edb2072238c085eac8c2", size = 1583685, upload-time = "2025-07-29T05:50:00.375Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b8/c3/94dc7357bc421f4fb978ca72a201a6c604ee90148f1181790c129396ceeb/aiohttp-3.12.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fe086edf38b2222328cdf89af0dde2439ee173b8ad7cb659b4e4c6f385b2be3d", size = 1627533, upload-time = "2025-07-29T05:50:02.306Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bf/3f/1f8911fe1844a07001e26593b5c255a685318943864b27b4e0267e840f95/aiohttp-3.12.15-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:79b26fe467219add81d5e47b4a4ba0f2394e8b7c7c3198ed36609f9ba161aecb", size = 1638319, upload-time = "2025-07-29T05:50:04.282Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4e/46/27bf57a99168c4e145ffee6b63d0458b9c66e58bb70687c23ad3d2f0bd17/aiohttp-3.12.15-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b761bac1192ef24e16706d761aefcb581438b34b13a2f069a6d343ec8fb693a5", size = 1613776, upload-time = "2025-07-29T05:50:05.863Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0f/7e/1d2d9061a574584bb4ad3dbdba0da90a27fdc795bc227def3a46186a8bc1/aiohttp-3.12.15-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e153e8adacfe2af562861b72f8bc47f8a5c08e010ac94eebbe33dc21d677cd5b", size = 1693359, upload-time = "2025-07-29T05:50:07.563Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/08/98/bee429b52233c4a391980a5b3b196b060872a13eadd41c3a34be9b1469ed/aiohttp-3.12.15-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:fc49c4de44977aa8601a00edbf157e9a421f227aa7eb477d9e3df48343311065", size = 1716598, upload-time = "2025-07-29T05:50:09.33Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/57/39/b0314c1ea774df3392751b686104a3938c63ece2b7ce0ba1ed7c0b4a934f/aiohttp-3.12.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2776c7ec89c54a47029940177e75c8c07c29c66f73464784971d6a81904ce9d1", size = 1644940, upload-time = "2025-07-29T05:50:11.334Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1b/83/3dacb8d3f8f512c8ca43e3fa8a68b20583bd25636ffa4e56ee841ffd79ae/aiohttp-3.12.15-cp310-cp310-win32.whl", hash = "sha256:2c7d81a277fa78b2203ab626ced1487420e8c11a8e373707ab72d189fcdad20a", size = 429239, upload-time = "2025-07-29T05:50:12.803Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/eb/f9/470b5daba04d558c9673ca2034f28d067f3202a40e17804425f0c331c89f/aiohttp-3.12.15-cp310-cp310-win_amd64.whl", hash = "sha256:83603f881e11f0f710f8e2327817c82e79431ec976448839f3cd05d7afe8f830", size = 452297, upload-time = "2025-07-29T05:50:14.266Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/20/19/9e86722ec8e835959bd97ce8c1efa78cf361fa4531fca372551abcc9cdd6/aiohttp-3.12.15-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d3ce17ce0220383a0f9ea07175eeaa6aa13ae5a41f30bc61d84df17f0e9b1117", size = 711246, upload-time = "2025-07-29T05:50:15.937Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/71/f9/0a31fcb1a7d4629ac9d8f01f1cb9242e2f9943f47f5d03215af91c3c1a26/aiohttp-3.12.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:010cc9bbd06db80fe234d9003f67e97a10fe003bfbedb40da7d71c1008eda0fe", size = 483515, upload-time = "2025-07-29T05:50:17.442Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/62/6c/94846f576f1d11df0c2e41d3001000527c0fdf63fce7e69b3927a731325d/aiohttp-3.12.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3f9d7c55b41ed687b9d7165b17672340187f87a773c98236c987f08c858145a9", size = 471776, upload-time = "2025-07-29T05:50:19.568Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f8/6c/f766d0aaafcee0447fad0328da780d344489c042e25cd58fde566bf40aed/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc4fbc61bb3548d3b482f9ac7ddd0f18c67e4225aaa4e8552b9f1ac7e6bda9e5", size = 1741977, upload-time = "2025-07-29T05:50:21.665Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/17/e5/fb779a05ba6ff44d7bc1e9d24c644e876bfff5abe5454f7b854cace1b9cc/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7fbc8a7c410bb3ad5d595bb7118147dfbb6449d862cc1125cf8867cb337e8728", size = 1690645, upload-time = "2025-07-29T05:50:23.333Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/37/4e/a22e799c2035f5d6a4ad2cf8e7c1d1bd0923192871dd6e367dafb158b14c/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:74dad41b3458dbb0511e760fb355bb0b6689e0630de8a22b1b62a98777136e16", size = 1789437, upload-time = "2025-07-29T05:50:25.007Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/28/e5/55a33b991f6433569babb56018b2fb8fb9146424f8b3a0c8ecca80556762/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b6f0af863cf17e6222b1735a756d664159e58855da99cfe965134a3ff63b0b0", size = 1828482, upload-time = "2025-07-29T05:50:26.693Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c6/82/1ddf0ea4f2f3afe79dffed5e8a246737cff6cbe781887a6a170299e33204/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5b7fe4972d48a4da367043b8e023fb70a04d1490aa7d68800e465d1b97e493b", size = 1730944, upload-time = "2025-07-29T05:50:28.382Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1b/96/784c785674117b4cb3877522a177ba1b5e4db9ce0fd519430b5de76eec90/aiohttp-3.12.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6443cca89553b7a5485331bc9bedb2342b08d073fa10b8c7d1c60579c4a7b9bd", size = 1668020, upload-time = "2025-07-29T05:50:30.032Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/12/8a/8b75f203ea7e5c21c0920d84dd24a5c0e971fe1e9b9ebbf29ae7e8e39790/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6c5f40ec615e5264f44b4282ee27628cea221fcad52f27405b80abb346d9f3f8", size = 1716292, upload-time = "2025-07-29T05:50:31.983Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/47/0b/a1451543475bb6b86a5cfc27861e52b14085ae232896a2654ff1231c0992/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:2abbb216a1d3a2fe86dbd2edce20cdc5e9ad0be6378455b05ec7f77361b3ab50", size = 1711451, upload-time = "2025-07-29T05:50:33.989Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/55/fd/793a23a197cc2f0d29188805cfc93aa613407f07e5f9da5cd1366afd9d7c/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:db71ce547012a5420a39c1b744d485cfb823564d01d5d20805977f5ea1345676", size = 1691634, upload-time = "2025-07-29T05:50:35.846Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ca/bf/23a335a6670b5f5dfc6d268328e55a22651b440fca341a64fccf1eada0c6/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ced339d7c9b5030abad5854aa5413a77565e5b6e6248ff927d3e174baf3badf7", size = 1785238, upload-time = "2025-07-29T05:50:37.597Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/57/4f/ed60a591839a9d85d40694aba5cef86dde9ee51ce6cca0bb30d6eb1581e7/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:7c7dd29c7b5bda137464dc9bfc738d7ceea46ff70309859ffde8c022e9b08ba7", size = 1805701, upload-time = "2025-07-29T05:50:39.591Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/85/e0/444747a9455c5de188c0f4a0173ee701e2e325d4b2550e9af84abb20cdba/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:421da6fd326460517873274875c6c5a18ff225b40da2616083c5a34a7570b685", size = 1718758, upload-time = "2025-07-29T05:50:41.292Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/36/ab/1006278d1ffd13a698e5dd4bfa01e5878f6bddefc296c8b62649753ff249/aiohttp-3.12.15-cp311-cp311-win32.whl", hash = "sha256:4420cf9d179ec8dfe4be10e7d0fe47d6d606485512ea2265b0d8c5113372771b", size = 428868, upload-time = "2025-07-29T05:50:43.063Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/10/97/ad2b18700708452400278039272032170246a1bf8ec5d832772372c71f1a/aiohttp-3.12.15-cp311-cp311-win_amd64.whl", hash = "sha256:edd533a07da85baa4b423ee8839e3e91681c7bfa19b04260a469ee94b778bf6d", size = 453273, upload-time = "2025-07-29T05:50:44.613Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/63/97/77cb2450d9b35f517d6cf506256bf4f5bda3f93a66b4ad64ba7fc917899c/aiohttp-3.12.15-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:802d3868f5776e28f7bf69d349c26fc0efadb81676d0afa88ed00d98a26340b7", size = 702333, upload-time = "2025-07-29T05:50:46.507Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/83/6d/0544e6b08b748682c30b9f65640d006e51f90763b41d7c546693bc22900d/aiohttp-3.12.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2800614cd560287be05e33a679638e586a2d7401f4ddf99e304d98878c29444", size = 476948, upload-time = "2025-07-29T05:50:48.067Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3a/1d/c8c40e611e5094330284b1aea8a4b02ca0858f8458614fa35754cab42b9c/aiohttp-3.12.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8466151554b593909d30a0a125d638b4e5f3836e5aecde85b66b80ded1cb5b0d", size = 469787, upload-time = "2025-07-29T05:50:49.669Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/38/7d/b76438e70319796bfff717f325d97ce2e9310f752a267bfdf5192ac6082b/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e5a495cb1be69dae4b08f35a6c4579c539e9b5706f606632102c0f855bcba7c", size = 1716590, upload-time = "2025-07-29T05:50:51.368Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/79/b1/60370d70cdf8b269ee1444b390cbd72ce514f0d1cd1a715821c784d272c9/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6404dfc8cdde35c69aaa489bb3542fb86ef215fc70277c892be8af540e5e21c0", size = 1699241, upload-time = "2025-07-29T05:50:53.628Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a3/2b/4968a7b8792437ebc12186db31523f541943e99bda8f30335c482bea6879/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3ead1c00f8521a5c9070fcb88f02967b1d8a0544e6d85c253f6968b785e1a2ab", size = 1754335, upload-time = "2025-07-29T05:50:55.394Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fb/c1/49524ed553f9a0bec1a11fac09e790f49ff669bcd14164f9fab608831c4d/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6990ef617f14450bc6b34941dba4f12d5613cbf4e33805932f853fbd1cf18bfb", size = 1800491, upload-time = "2025-07-29T05:50:57.202Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/de/5e/3bf5acea47a96a28c121b167f5ef659cf71208b19e52a88cdfa5c37f1fcc/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd736ed420f4db2b8148b52b46b88ed038d0354255f9a73196b7bbce3ea97545", size = 1719929, upload-time = "2025-07-29T05:50:59.192Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/39/94/8ae30b806835bcd1cba799ba35347dee6961a11bd507db634516210e91d8/aiohttp-3.12.15-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c5092ce14361a73086b90c6efb3948ffa5be2f5b6fbcf52e8d8c8b8848bb97c", size = 1635733, upload-time = "2025-07-29T05:51:01.394Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7a/46/06cdef71dd03acd9da7f51ab3a9107318aee12ad38d273f654e4f981583a/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aaa2234bb60c4dbf82893e934d8ee8dea30446f0647e024074237a56a08c01bd", size = 1696790, upload-time = "2025-07-29T05:51:03.657Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/02/90/6b4cfaaf92ed98d0ec4d173e78b99b4b1a7551250be8937d9d67ecb356b4/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6d86a2fbdd14192e2f234a92d3b494dd4457e683ba07e5905a0b3ee25389ac9f", size = 1718245, upload-time = "2025-07-29T05:51:05.911Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2e/e6/2593751670fa06f080a846f37f112cbe6f873ba510d070136a6ed46117c6/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a041e7e2612041a6ddf1c6a33b883be6a421247c7afd47e885969ee4cc58bd8d", size = 1658899, upload-time = "2025-07-29T05:51:07.753Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8f/28/c15bacbdb8b8eb5bf39b10680d129ea7410b859e379b03190f02fa104ffd/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5015082477abeafad7203757ae44299a610e89ee82a1503e3d4184e6bafdd519", size = 1738459, upload-time = "2025-07-29T05:51:09.56Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/00/de/c269cbc4faa01fb10f143b1670633a8ddd5b2e1ffd0548f7aa49cb5c70e2/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:56822ff5ddfd1b745534e658faba944012346184fbfe732e0d6134b744516eea", size = 1766434, upload-time = "2025-07-29T05:51:11.423Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/52/b0/4ff3abd81aa7d929b27d2e1403722a65fc87b763e3a97b3a2a494bfc63bc/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b2acbbfff69019d9014508c4ba0401822e8bae5a5fdc3b6814285b71231b60f3", size = 1726045, upload-time = "2025-07-29T05:51:13.689Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/71/16/949225a6a2dd6efcbd855fbd90cf476052e648fb011aa538e3b15b89a57a/aiohttp-3.12.15-cp312-cp312-win32.whl", hash = "sha256:d849b0901b50f2185874b9a232f38e26b9b3d4810095a7572eacea939132d4e1", size = 423591, upload-time = "2025-07-29T05:51:15.452Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2b/d8/fa65d2a349fe938b76d309db1a56a75c4fb8cc7b17a398b698488a939903/aiohttp-3.12.15-cp312-cp312-win_amd64.whl", hash = "sha256:b390ef5f62bb508a9d67cb3bba9b8356e23b3996da7062f1a57ce1a79d2b3d34", size = 450266, upload-time = "2025-07-29T05:51:17.239Z" }, +] + +[[package]] +name = "aiolimiter" +version = "1.2.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f1/23/b52debf471f7a1e42e362d959a3982bdcb4fe13a5d46e63d28868807a79c/aiolimiter-1.2.1.tar.gz", hash = "sha256:e02a37ea1a855d9e832252a105420ad4d15011505512a1a1d814647451b5cca9", size = 7185, upload-time = "2024-12-08T15:31:51.496Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f3/ba/df6e8e1045aebc4778d19b8a3a9bc1808adb1619ba94ca354d9ba17d86c3/aiolimiter-1.2.1-py3-none-any.whl", hash = "sha256:d3f249e9059a20badcb56b61601a83556133655c11d1eb3dd3e04ff069e5f3c7", size = 6711, upload-time = "2024-12-08T15:31:49.874Z" }, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "frozenlist" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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.20.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0d/3a/22ff5415bf4d296c1e92b07fd746ad42c96781f13295a074d58e77747848/aiosqlite-0.20.0.tar.gz", hash = "sha256:6d35c8c256637f4672f843c31021464090805bf925385ac39473fb16eaaca3d7", size = 21691, upload-time = "2024-02-20T06:12:53.915Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/00/c4/c93eb22025a2de6b83263dfe3d7df2e19138e345bca6f18dba7394120930/aiosqlite-0.20.0-py3-none-any.whl", hash = "sha256:36a1deaca0cac40ebe32aac9977a6e2bbc7f5189f23f4a54d5908986729e5bd6", size = 15564, upload-time = "2024-02-20T06:12:50.657Z" }, +] + +[[package]] +name = "akracer" +version = "0.0.13" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b7/76/424358add4fb060ab24c7a4d6e90c5c05cc1871cf27b6afc3f39ff5774fe/akracer-0.0.13.tar.gz", hash = "sha256:8ab67dabda34f38604d037f2cac67078d253d8c4c316ffe0d80d27ed03cdbb5e", size = 10047445, upload-time = "2023-10-25T08:02:31.084Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/15/86/6ef05f0b51a36dbec2b260da7a93ed0096dea32e708e127c5051b875af2d/akracer-0.0.13-py3-none-any.whl", hash = "sha256:55bd04c69e35130994d26795f00183e0c33d4e237f7ebfa35074a760c30209d1", size = 10078023, upload-time = "2023-10-25T08:02:27.193Z" }, +] + +[[package]] +name = "akshare" +version = "1.17.26" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "akracer", marker = "sys_platform == 'linux'" }, + { name = "beautifulsoup4" }, + { name = "decorator" }, + { name = "html5lib" }, + { name = "jsonpath" }, + { name = "lxml" }, + { name = "mini-racer", marker = "sys_platform != 'linux'" }, + { name = "nest-asyncio" }, + { name = "openpyxl" }, + { name = "pandas" }, + { name = "py-mini-racer", marker = "sys_platform == 'linux'" }, + { name = "requests" }, + { name = "tabulate" }, + { name = "tqdm" }, + { name = "urllib3" }, + { name = "xlrd" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/82/ca/b5eea0a3f2783ce8934495c7364425acbc70a021bdf7e79e8ba02d0ce0a8/akshare-1.17.26.tar.gz", hash = "sha256:3245df116965c46b16bb23da9c0372e61ea802d1f996243d26a64c3e16b513f1", size = 839911, upload-time = "2025-07-24T08:40:48.939Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a5/4a/f40c8adb970514eac84f3c0728ebb5e069f2e2ca202d9e94cbc6f50057fe/akshare-1.17.26-py3-none-any.whl", hash = "sha256:b8216f81336349d1069972c2916e7083a5e06eb21b4a9db2d5051e771c9fd4df", size = 1054174, upload-time = "2025-07-24T08:40:47.67Z" }, +] + +[[package]] +name = "alabaster" +version = "1.0.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210, upload-time = "2024-07-26T18:15:03.762Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anthropic" +version = "0.34.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "tokenizers" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/87/e2/98ff733ff75c1d371c029fb27eb9308f9c8e694749cea70382338a8e7e88/anthropic-0.34.1.tar.gz", hash = "sha256:69e822bd7a31ec11c2edb85f2147e8f0ee0cfd3288fea70b0ca8808b2f9bf91d", size = 901462, upload-time = "2024-08-20T00:44:35.633Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a4/1c/1ce9edec76885badebacb4e31d42acffbdfd30dbaa839d5c378d57ac9aa9/anthropic-0.34.1-py3-none-any.whl", hash = "sha256:2fa26710809d0960d970f26cd0be3686437250a481edb95c33d837aa5fa24158", size = 891537, upload-time = "2024-08-20T00:44:34.033Z" }, +] + +[[package]] +name = "anyio" +version = "4.9.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "idna" }, + { name = "sniffio" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" }, +] + +[[package]] +name = "anytree" +version = "2.13.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bc/a8/eb55fab589c56f9b6be2b3fd6997aa04bb6f3da93b01154ce6fc8e799db2/anytree-2.13.0.tar.gz", hash = "sha256:c9d3aa6825fdd06af7ebb05b4ef291d2db63e62bb1f9b7d9b71354be9d362714", size = 48389, upload-time = "2025-04-08T21:06:30.662Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7b/98/f6aa7fe0783e42be3093d8ef1b0ecdc22c34c0d69640dfb37f56925cb141/anytree-2.13.0-py3-none-any.whl", hash = "sha256:4cbcf10df36b1f1cba131b7e487ff3edafc9d6e932a3c70071b5b768bab901ff", size = 45077, upload-time = "2025-04-08T21:06:29.494Z" }, +] + +[[package]] +name = "argon2-cffi" +version = "25.1.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "argon2-cffi-bindings" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0e/89/ce5af8a7d472a67cc819d5d998aa8c82c5d860608c4db9f46f1162d7dab9/argon2_cffi-25.1.0.tar.gz", hash = "sha256:694ae5cc8a42f4c4e2bf2ca0e64e51e23a040c6a517a85074683d3959e1346c1", size = 45706, upload-time = "2025-06-03T06:55:32.073Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl", hash = "sha256:fdc8b074db390fccb6eb4a3604ae7231f219aa669a2652e0f20e16ba513d5741", size = 14657, upload-time = "2025-06-03T06:55:30.804Z" }, +] + +[[package]] +name = "argon2-cffi-bindings" +version = "21.2.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "cffi" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b9/e9/184b8ccce6683b0aa2fbb7ba5683ea4b9c5763f1356347f1312c32e3c66e/argon2-cffi-bindings-21.2.0.tar.gz", hash = "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3", size = 1779911, upload-time = "2021-12-01T08:52:55.68Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d4/13/838ce2620025e9666aa8f686431f67a29052241692a3dd1ae9d3692a89d3/argon2_cffi_bindings-21.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367", size = 29658, upload-time = "2021-12-01T09:09:17.016Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b3/02/f7f7bb6b6af6031edb11037639c697b912e1dea2db94d436e681aea2f495/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d", size = 80583, upload-time = "2021-12-01T09:09:19.546Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ec/f7/378254e6dd7ae6f31fe40c8649eea7d4832a42243acaf0f1fff9083b2bed/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae", size = 86168, upload-time = "2021-12-01T09:09:21.445Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/74/f6/4a34a37a98311ed73bb80efe422fed95f2ac25a4cacc5ae1d7ae6a144505/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c", size = 82709, upload-time = "2021-12-01T09:09:18.182Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/74/2b/73d767bfdaab25484f7e7901379d5f8793cccbb86c6e0cbc4c1b96f63896/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86", size = 83613, upload-time = "2021-12-01T09:09:22.741Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4f/fd/37f86deef67ff57c76f137a67181949c2d408077e2e3dd70c6c42912c9bf/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_i686.whl", hash = "sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f", size = 84583, upload-time = "2021-12-01T09:09:24.177Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6f/52/5a60085a3dae8fded8327a4f564223029f5f54b0cb0455a31131b5363a01/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e", size = 88475, upload-time = "2021-12-01T09:09:26.673Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8b/95/143cd64feb24a15fa4b189a3e1e7efbaeeb00f39a51e99b26fc62fbacabd/argon2_cffi_bindings-21.2.0-cp36-abi3-win32.whl", hash = "sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082", size = 27698, upload-time = "2021-12-01T09:09:27.87Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/37/2c/e34e47c7dee97ba6f01a6203e0383e15b60fb85d78ac9a15cd066f6fe28b/argon2_cffi_bindings-21.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f", size = 30817, upload-time = "2021-12-01T09:09:30.267Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5a/e4/bf8034d25edaa495da3c8a3405627d2e35758e44ff6eaa7948092646fdcc/argon2_cffi_bindings-21.2.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93", size = 53104, upload-time = "2021-12-01T09:09:31.335Z" }, +] + +[[package]] +name = "arrow" +version = "1.3.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "python-dateutil" }, + { name = "types-python-dateutil" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2e/00/0f6e8fcdb23ea632c866620cc872729ff43ed91d284c866b515c6342b173/arrow-1.3.0.tar.gz", hash = "sha256:d4540617648cb5f895730f1ad8c82a65f2dad0166f57b75f3ca54759c4d67a85", size = 131960, upload-time = "2023-09-30T22:11:18.25Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl", hash = "sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80", size = 66419, upload-time = "2023-09-30T22:11:16.072Z" }, +] + +[[package]] +name = "arxiv" +version = "2.1.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "feedparser" }, + { name = "requests" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fe/59/fe41f54bdfed776c2e9bcd6289e4c71349eb938241d89b4c97d0f33e8013/arxiv-2.1.3.tar.gz", hash = "sha256:32365221994d2cf05657c1fadf63a26efc8ccdec18590281ee03515bfef8bc4e", size = 16747, upload-time = "2024-06-25T02:56:20.062Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b7/7b/7bf42178d227b26d3daf94cdd22a72a4ed5bf235548c4f5aea49c51c6458/arxiv-2.1.3-py3-none-any.whl", hash = "sha256:6f43673ab770a9e848d7d4fc1894824df55edeac3c3572ea280c9ba2e3c0f39f", size = 11478, upload-time = "2024-06-25T02:56:17.032Z" }, +] + +[[package]] +name = "aspose-slides" +version = "24.12.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6e/db/680408b92f47aa9ff2c70f80b2f5d02155a8ff81ac493c3061099bf56c37/Aspose.Slides-24.12.0-py3-none-macosx_10_14_x86_64.whl", hash = "sha256:ccfaa61a863ed28cd37b221e31a0edf4a83802599d76fb50861c25149ac5e5e3", size = 87164865, upload-time = "2024-12-05T00:51:15.328Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/01/ac/29838004784acb72c9d93f0b327a8e5105f35eb925cdaeccd07907464018/Aspose.Slides-24.12.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b050659129c5ca92e52fbcd7d5091caa244db731adb68fbea1fd0a8b9fd62a5a", size = 68916630, upload-time = "2024-12-05T00:51:21.587Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2b/6e/0b9da3757ce46b63f3fbb10ee352009c20260813d369306438bd3552fc18/Aspose.Slides-24.12.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:a5eb8407bd93fa7851584c3b143000c09d9f5285f3c1da99677bf1d9c0abefe9", size = 102438903, upload-time = "2024-12-05T00:51:27.926Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/58/0b/af65314b471766709627a65096f69e8b70b7840edd98cabaa9b74fda671d/Aspose.Slides-24.12.0-py3-none-win_amd64.whl", hash = "sha256:e816e37a621221e8a73fc631c879ada37cf6a80513a817b687d6f7e189d5a978", size = 72093115, upload-time = "2024-12-05T00:51:40.848Z" }, +] + +[[package]] +name = "async-timeout" +version = "5.0.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a5/ae/136395dfbfe00dfc94da3f3e136d0b13f394cba8f4841120e34226265780/async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3", size = 9274, upload-time = "2024-11-06T16:41:39.6Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233, upload-time = "2024-11-06T16:41:37.9Z" }, +] + +[[package]] +name = "attrs" +version = "25.3.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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 = "autograd" +version = "1.8.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/67/1c/3c24ec03c8ba4decc742b1df5a10c52f98c84ca8797757f313e7bdcdf276/autograd-1.8.0.tar.gz", hash = "sha256:107374ded5b09fc8643ac925348c0369e7b0e73bbed9565ffd61b8fd04425683", size = 2562146, upload-time = "2025-05-05T12:49:02.502Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/84/ea/e16f0c423f7d83cf8b79cae9452040fb7b2e020c7439a167ee7c317de448/autograd-1.8.0-py3-none-any.whl", hash = "sha256:4ab9084294f814cf56c280adbe19612546a35574d67c574b04933c7d2ecb7d78", size = 51478, upload-time = "2025-05-05T12:49:00.585Z" }, +] + +[[package]] +name = "azure-core" +version = "1.35.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "requests" }, + { name = "six" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ce/89/f53968635b1b2e53e4aad2dd641488929fef4ca9dfb0b97927fa7697ddf3/azure_core-1.35.0.tar.gz", hash = "sha256:c0be528489485e9ede59b6971eb63c1eaacf83ef53001bfe3904e475e972be5c", size = 339689, upload-time = "2025-07-03T00:55:23.496Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d4/78/bf94897361fdd650850f0f2e405b2293e2f12808239046232bdedf554301/azure_core-1.35.0-py3-none-any.whl", hash = "sha256:8db78c72868a58f3de8991eb4d22c4d368fae226dac1002998d6c50437e7dad1", size = 210708, upload-time = "2025-07-03T00:55:25.238Z" }, +] + +[[package]] +name = "azure-identity" +version = "1.17.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "azure-core" }, + { name = "cryptography" }, + { name = "msal" }, + { name = "msal-extensions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/51/c9/f7e3926686a89670ce641b360bd2da9a2d7a12b3e532403462d99f81e9d5/azure-identity-1.17.1.tar.gz", hash = "sha256:32ecc67cc73f4bd0595e4f64b1ca65cd05186f4fe6f98ed2ae9f1aa32646efea", size = 246652, upload-time = "2024-06-22T01:41:45.525Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/49/83/a777861351e7b99e7c84ff3b36bab35e87b6e5d36e50b6905e148c696515/azure_identity-1.17.1-py3-none-any.whl", hash = "sha256:db8d59c183b680e763722bfe8ebc45930e6c57df510620985939f7f3191e0382", size = 173229, upload-time = "2024-06-22T01:41:49.309Z" }, +] + +[[package]] +name = "azure-storage-blob" +version = "12.22.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "azure-core" }, + { name = "cryptography" }, + { name = "isodate" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/92/de/9cea85c0d5fc21f99bcf9f060fc2287cb95236b70431fa63cb69890a121e/azure-storage-blob-12.22.0.tar.gz", hash = "sha256:b3804bb4fe8ab1c32771fa464053da772a682c2737b19da438a3f4e5e3b3736e", size = 564873, upload-time = "2024-08-06T20:54:41.054Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a8/52/b578c94048469fbf9f6378e2b2a46a2d0ccba3d59a7845dbed22ebf61601/azure_storage_blob-12.22.0-py3-none-any.whl", hash = "sha256:bb7d2d824ce3f11f14a27ee7d9281289f7e072ac8311c52e3652672455b7d5e8", size = 404892, upload-time = "2024-08-06T20:54:43.612Z" }, +] + +[[package]] +name = "azure-storage-file-datalake" +version = "12.16.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "azure-core" }, + { name = "azure-storage-blob" }, + { name = "isodate" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/71/a6/980d2d1405ae5397b618cc9a21b4530fb7e6c9078ccf48b5ce0eec1b25cd/azure-storage-file-datalake-12.16.0.tar.gz", hash = "sha256:3185580e4e438162ef84fb88cb46b2ef248dafbfb07f53297762417bb7000333", size = 274485, upload-time = "2024-07-18T21:55:41.784Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/42/56/a30d062af3100b3ec3e515fc1f40d38979e8508bb962231530702309aa4b/azure_storage_file_datalake-12.16.0-py3-none-any.whl", hash = "sha256:da57ec6cf5640b92bbd0ba61478f51e67c63b94843fa748b3b6599f1adba5837", size = 255558, upload-time = "2024-07-18T21:55:44.204Z" }, +] + +[[package]] +name = "babel" +version = "2.17.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, +] + +[[package]] +name = "backoff" +version = "2.2.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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 = "bce-python-sdk" +version = "0.9.42" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "future" }, + { name = "pycryptodome" }, + { name = "six" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/58/37/bee21dab6b4547538a576982de5b7e4c864bab999c46163a7a4c9f385826/bce_python_sdk-0.9.42.tar.gz", hash = "sha256:41a92b785799b41f25753136ddb6a8bb96afcb69015e2fda6a5a429030ffc6b9", size = 252391, upload-time = "2025-07-24T13:38:56.763Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b0/bb/94071e19b45d9922b2d8014ebe553304bfed9ae02a6eaa20a15a6249c49f/bce_python_sdk-0.9.42-py3-none-any.whl", hash = "sha256:b62207e974e9c1a53ef0d71183b3004cca6c8155da3208b51b3dc48b1dee6b86", size = 351625, upload-time = "2025-07-24T13:38:54.45Z" }, +] + +[[package]] +name = "bcembedding" +version = "0.1.5" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "datasets" }, + { name = "sentence-transformers" }, + { name = "torch" }, + { name = "transformers" }, +] +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/52/85/126569029a643837c1e369dc050334d0dff81fc0a9c2a63ce00b466fb9e6/BCEmbedding-0.1.5-py3-none-any.whl", hash = "sha256:7d46c876380dae51ad52c9a8e5e51373691e85d50b6f485e0bcd38ac31f6b144", size = 30132, upload-time = "2024-05-13T09:07:26.043Z" }, +] + +[[package]] +name = "beartype" +version = "0.18.5" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/96/15/4e623478a9628ad4cee2391f19aba0b16c1dd6fedcb2a399f0928097b597/beartype-0.18.5.tar.gz", hash = "sha256:264ddc2f1da9ec94ff639141fbe33d22e12a9f75aa863b83b7046ffff1381927", size = 1193506, upload-time = "2024-04-21T07:25:58.64Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/64/43/7a1259741bd989723272ac7d381a43be932422abcff09a1d9f7ba212cb74/beartype-0.18.5-py3-none-any.whl", hash = "sha256:5301a14f2a9a5540fe47ec6d34d758e9cd8331d36c4760fc7a5499ab86310089", size = 917762, upload-time = "2024-04-21T07:25:55.758Z" }, +] + +[[package]] +name = "beautifulsoup4" +version = "4.12.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "soupsieve" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b3/ca/824b1195773ce6166d388573fc106ce56d4a805bd7427b624e063596ec58/beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051", size = 581181, upload-time = "2024-01-17T16:53:17.902Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b1/fe/e8c672695b37eecc5cbf43e1d0638d88d66ba3a44c4d321c796f4e59167f/beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed", size = 147925, upload-time = "2024-01-17T16:53:12.779Z" }, +] + +[[package]] +name = "bibtexparser" +version = "1.4.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "pyparsing" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/92/8d/e296c7af03757debd8fc80df2898cbed4fb69fc61ed2c9b4a1d42e923a9e/bibtexparser-1.4.3.tar.gz", hash = "sha256:a9c7ded64bc137720e4df0b1b7f12734edc1361185f1c9097048ff7c35af2b8f", size = 55582, upload-time = "2024-12-19T20:41:57.754Z" } + +[[package]] +name = "bio" +version = "1.7.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "biopython" }, + { name = "gprofiler-official" }, + { name = "mygene" }, + { name = "pandas" }, + { name = "pooch" }, + { name = "requests" }, + { name = "tqdm" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/58/ba/fdaa4c286ed50f96835a5f81c72d6c76933fb890ee1ff2269b6110ea851e/bio-1.7.1.tar.gz", hash = "sha256:df3252905b0b1e739eca3760c91fd519d5af07b09632df25c2bd4ecd20da2724", size = 241383, upload-time = "2024-05-29T16:32:55.426Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cb/40/747f3038ac636e520da52f7b9f5721779a50f88fdfc165847b0d8127dae2/bio-1.7.1-py3-none-any.whl", hash = "sha256:851545804b08413a3f27fd5131edefc30acfdee513919eebabb29678d8632218", size = 280992, upload-time = "2024-05-29T16:32:56.712Z" }, +] + +[[package]] +name = "biopython" +version = "1.85" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/db/ca/1d5fab0fedaf5c2f376d9746d447cdce04241c433602c3861693361ce54c/biopython-1.85.tar.gz", hash = "sha256:5dafab74059de4e78f49f6b5684eddae6e7ce46f09cfa059c1d1339e8b1ea0a6", size = 19909902, upload-time = "2025-01-15T15:06:51.997Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/79/de/79824470fdc5c2aedeb1bab637d24eab654a7e9fbb7070f779fd944fd1ca/biopython-1.85-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a6308053a61f3bdbb11504ece4cf24e264c6f1d6fad278f7e59e6b84b0d9a7b4", size = 2787511, upload-time = "2025-01-15T15:11:56.656Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4b/d6/3c90df2ebc4f2a522235f4391392253e528f19f5fb6a52396a2316e17064/biopython-1.85-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:434dd23e972b0c89e128f2ebbd16b38075d609184f4f1fd16368035f923019c2", size = 2765456, upload-time = "2025-01-15T15:12:05.177Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ff/d7/34233c4ee906b088b199eb2af9c4107384a547d44b5960257ef2bf2cc3c8/biopython-1.85-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a08d082e85778259a83501063871e00ba57336136403b75050eea14d523c00a", size = 3225137, upload-time = "2025-01-15T15:12:15.847Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ae/6b/e7a5ae49ef7e8f81720593db22e884c1c8e3504e0e235da0395758a5c7d0/biopython-1.85-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d93464e629950e4df87d810125dc4e904538a4344b924f340908ea5bc95db986", size = 3245258, upload-time = "2025-01-15T15:12:21.012Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/68/2c/0035656dbe51ac0bbe81321172b43c37b38408f8d136abaac443e9f44b62/biopython-1.85-cp310-cp310-win32.whl", hash = "sha256:f2f45ab3f1e43fdaa697fd753148999090298623278097c19c2c3c0ba134e57c", size = 2786505, upload-time = "2025-01-15T15:12:26.545Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/32/a4/f20d5830dbd8654c13e763daef429fa32ef0b2b09749ac427d045cc81976/biopython-1.85-cp310-cp310-win_amd64.whl", hash = "sha256:7c8326cd2825ba166abecaf72843b9b15823affd6cec04fde65f0d2526767da4", size = 2820961, upload-time = "2025-01-15T15:12:32.364Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c3/73/c3a1323a3fe0d07212b09c04fb903e2cbb98aebfbb58e55e8717473e1bc0/biopython-1.85-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:db8822adab0cd75a6e6ae845acf312addd8eab5f9b731c191454b961fc2c2cdc", size = 2787585, upload-time = "2025-01-15T15:12:36.927Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ff/cf/299524e896fa49beb7588143e1509cce4848572215ebafb8eea83a861820/biopython-1.85-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8e2bbe58cc1a592b239ef6d68396745d3fbfaafc668ce38283871d8ff070dbab", size = 2765608, upload-time = "2025-01-15T15:12:43.853Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b1/dd/be3e95b72a35ee6d52c84412e15af828951e5c69175080d4619985fd54ce/biopython-1.85-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5916eb56df7ecd4a3babc07a48d4894c40cfb45dc18ccda1c148d0131017ce04", size = 3237552, upload-time = "2025-01-15T15:12:49.046Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/25/9c/612821b946930b6caa5d795cfe4169ed6a522562eced9776914be7efaf21/biopython-1.85-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0cec8833bf3036049129944ee5382dd576dac9670c3814ff2314b52aa94f199", size = 3257287, upload-time = "2025-01-15T15:12:56.168Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a1/77/316e51dd42fd8225429574a268bdc627ce4f42067a3976c4c8c457a42023/biopython-1.85-cp311-cp311-win32.whl", hash = "sha256:cf88a4c8d8af13138be115949639a5e4a201618185a72ff09adbe175b7946b28", size = 2786411, upload-time = "2025-01-15T15:13:02.754Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f2/11/3c4e8c049b91998bbbd51ddebc6f790b1aa66211babfbf5ff008a72fb1f9/biopython-1.85-cp311-cp311-win_amd64.whl", hash = "sha256:d3c99db65d57ae4fc5034e42ac6cd8ddce069e664903f04c8a4f684d7609d6fa", size = 2820912, upload-time = "2025-01-15T15:13:10.499Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a3/25/e46f05359df7f0049c3adc5eaeb9aee0f5fbde1d959d05c78eb1de8f4d12/biopython-1.85-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc5b981b9e3060db7c355b6145dfe3ce0b6572e1601b31211f6d742b10543874", size = 2789327, upload-time = "2025-01-15T15:13:17.086Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/54/5b/8b3b029c94c63ab4c1781d141615b4a837e658422381d460c5573d5d8262/biopython-1.85-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6fe47d704c2d3afac99aeb461219ec5f00273120d2d99835dc0a9a617f520141", size = 2765805, upload-time = "2025-01-15T15:13:26.92Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/69/0a/9a8a38eff03c4607b9cec8d0e08c76b346b1cee1f77bc6d00efebfc7ec83/biopython-1.85-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e54e495239e623660ad367498c2f7a1a294b1997ba603f2ceafb36fd18f0eba6", size = 3249276, upload-time = "2025-01-15T15:13:36.639Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b4/0d/b7a0f10f5100dcf51ae36ba31490169bfa45617323bd82af43e1fb0098fb/biopython-1.85-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d024ad48997ad53d53a77da24b072aaba8a550bd816af8f2e7e606a9918a3b43", size = 3268869, upload-time = "2025-01-15T15:13:44.009Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/07/51/646a4b7bdb4c1153786a70d33588ed09178bfcdda0542dfdc976294f4312/biopython-1.85-cp312-cp312-win32.whl", hash = "sha256:6985e17a2911defcbd30275a12f5ed5de2765e4bc91a60439740d572fdbfdf43", size = 2787011, upload-time = "2025-01-15T15:13:48.958Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c1/84/c583fa2ac6e7d392d24ebdc5c99e95e517507de22cf143efb6cf1fc93ff5/biopython-1.85-cp312-cp312-win_amd64.whl", hash = "sha256:d6f8efb2db03f2ec115c5e8c764dbadf635e0c9ecd4c0e91fc8216c1b62f85f5", size = 2820972, upload-time = "2025-01-15T15:13:54.475Z" }, +] + +[[package]] +name = "biothings-client" +version = "0.4.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "httpx" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ef/2f/2ef9115e317f4c2acb690b341f40de751b16d14169fdbb8d6eb86964166c/biothings_client-0.4.1.tar.gz", hash = "sha256:5b34e09c905280b5bd2538f1f34b6fc780c53c8da9b4074e3ff304836046f613", size = 56096, upload-time = "2025-01-14T22:55:39.926Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/dc/6d/130477cfbd7294949b919c45cc1ed14a642cec95afba06a54400a4419235/biothings_client-0.4.1-py3-none-any.whl", hash = "sha256:9cbc17461b2bf6af6ed200929b886d6670d450af2034b428cd833f725695265a", size = 46698, upload-time = "2025-01-14T22:55:37.44Z" }, +] + +[[package]] +name = "blinker" +version = "1.7.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a1/13/6df5fc090ff4e5d246baf1f45fe9e5623aa8565757dfa5bd243f6a545f9e/blinker-1.7.0.tar.gz", hash = "sha256:e6820ff6fa4e4d1d8e2747c2283749c3f547e4fee112b98555cdcdae32996182", size = 28134, upload-time = "2023-11-01T22:06:01.588Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fa/2a/7f3714cbc6356a0efec525ce7a0613d581072ed6eb53eb7b9754f33db807/blinker-1.7.0-py3-none-any.whl", hash = "sha256:c3f865d4d54db7abc53758a01601cf343fe55b84c1de4e3fa910e420b438d5b9", size = 13068, upload-time = "2023-11-01T22:06:00.162Z" }, +] + +[[package]] +name = "boto3" +version = "1.34.140" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "botocore" }, + { name = "jmespath" }, + { name = "s3transfer" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3f/79/c9a3651e563c5ac762080b8cef8e85659400a78d9ccffcf83916f68f4b04/boto3-1.34.140.tar.gz", hash = "sha256:578bbd5e356005719b6b610d03edff7ea1b0824d078afe62d3fb8bea72f83a87", size = 108704, upload-time = "2024-07-05T19:20:32.085Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7b/26/76ee022975d33c9460ba0b3edefade5569597aa43bfb57c72722b1c5356a/boto3-1.34.140-py3-none-any.whl", hash = "sha256:23ca8d8f7a30c3bbd989808056b5fc5d68ff5121c02c722c6167b6b1bb7f8726", size = 139171, upload-time = "2024-07-05T19:20:11.416Z" }, +] + +[[package]] +name = "botocore" +version = "1.34.140" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "jmespath" }, + { name = "python-dateutil" }, + { name = "urllib3" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a5/9a/3ae02c5dcc8f9a188e03d897aac898bd20d7f3eb5b910c9e071caf70f172/botocore-1.34.140.tar.gz", hash = "sha256:86302b2226c743b9eec7915a4c6cfaffd338ae03989cd9ee181078ef39d1ab39", size = 12565133, upload-time = "2024-07-05T19:19:26.79Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f5/23/91c8b50588470d80317f4afca93d3d542139bdc38ed5ad1b512fba416af3/botocore-1.34.140-py3-none-any.whl", hash = "sha256:43940d3a67d946ba3301631ba4078476a75f1015d4fb0fb0272d0b754b2cf9de", size = 12354845, upload-time = "2024-07-05T19:19:10.578Z" }, +] + +[[package]] +name = "brotli" +version = "1.1.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2f/c2/f9e977608bdf958650638c3f1e28f85a1b075f075ebbe77db8555463787b/Brotli-1.1.0.tar.gz", hash = "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724", size = 7372270, upload-time = "2023-09-07T14:05:41.643Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6d/3a/dbf4fb970c1019a57b5e492e1e0eae745d32e59ba4d6161ab5422b08eefe/Brotli-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1140c64812cb9b06c922e77f1c26a75ec5e3f0fb2bf92cc8c58720dec276752", size = 873045, upload-time = "2023-09-07T14:03:16.894Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/dd/11/afc14026ea7f44bd6eb9316d800d439d092c8d508752055ce8d03086079a/Brotli-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8fd5270e906eef71d4a8d19b7c6a43760c6abcfcc10c9101d14eb2357418de9", size = 446218, upload-time = "2023-09-07T14:03:18.917Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/36/83/7545a6e7729db43cb36c4287ae388d6885c85a86dd251768a47015dfde32/Brotli-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ae56aca0402a0f9a3431cddda62ad71666ca9d4dc3a10a142b9dce2e3c0cda3", size = 2903872, upload-time = "2023-09-07T14:03:20.398Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/32/23/35331c4d9391fcc0f29fd9bec2c76e4b4eeab769afbc4b11dd2e1098fb13/Brotli-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43ce1b9935bfa1ede40028054d7f48b5469cd02733a365eec8a329ffd342915d", size = 2941254, upload-time = "2023-09-07T14:03:21.914Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3b/24/1671acb450c902edb64bd765d73603797c6c7280a9ada85a195f6b78c6e5/Brotli-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:7c4855522edb2e6ae7fdb58e07c3ba9111e7621a8956f481c68d5d979c93032e", size = 2857293, upload-time = "2023-09-07T14:03:24Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d5/00/40f760cc27007912b327fe15bf6bfd8eaecbe451687f72a8abc587d503b3/Brotli-1.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:38025d9f30cf4634f8309c6874ef871b841eb3c347e90b0851f63d1ded5212da", size = 3002385, upload-time = "2023-09-07T14:03:26.248Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b8/cb/8aaa83f7a4caa131757668c0fb0c4b6384b09ffa77f2fba9570d87ab587d/Brotli-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e6a904cb26bfefc2f0a6f240bdf5233be78cd2488900a2f846f3c3ac8489ab80", size = 2911104, upload-time = "2023-09-07T14:03:27.849Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bc/c4/65456561d89d3c49f46b7fbeb8fe6e449f13bdc8ea7791832c5d476b2faf/Brotli-1.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a37b8f0391212d29b3a91a799c8e4a2855e0576911cdfb2515487e30e322253d", size = 2809981, upload-time = "2023-09-07T14:03:29.92Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/05/1b/cf49528437bae28abce5f6e059f0d0be6fecdcc1d3e33e7c54b3ca498425/Brotli-1.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e84799f09591700a4154154cab9787452925578841a94321d5ee8fb9a9a328f0", size = 2935297, upload-time = "2023-09-07T14:03:32.035Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/81/ff/190d4af610680bf0c5a09eb5d1eac6e99c7c8e216440f9c7cfd42b7adab5/Brotli-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f66b5337fa213f1da0d9000bc8dc0cb5b896b726eefd9c6046f699b169c41b9e", size = 2930735, upload-time = "2023-09-07T14:03:33.801Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/80/7d/f1abbc0c98f6e09abd3cad63ec34af17abc4c44f308a7a539010f79aae7a/Brotli-1.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5dab0844f2cf82be357a0eb11a9087f70c5430b2c241493fc122bb6f2bb0917c", size = 2933107, upload-time = "2024-10-18T12:32:09.016Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/34/ce/5a5020ba48f2b5a4ad1c0522d095ad5847a0be508e7d7569c8630ce25062/Brotli-1.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e4fe605b917c70283db7dfe5ada75e04561479075761a0b3866c081d035b01c1", size = 2845400, upload-time = "2024-10-18T12:32:11.134Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/44/89/fa2c4355ab1eecf3994e5a0a7f5492c6ff81dfcb5f9ba7859bd534bb5c1a/Brotli-1.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:1e9a65b5736232e7a7f91ff3d02277f11d339bf34099a56cdab6a8b3410a02b2", size = 3031985, upload-time = "2024-10-18T12:32:12.813Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/af/a4/79196b4a1674143d19dca400866b1a4d1a089040df7b93b88ebae81f3447/Brotli-1.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:58d4b711689366d4a03ac7957ab8c28890415e267f9b6589969e74b6e42225ec", size = 2927099, upload-time = "2024-10-18T12:32:14.733Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e9/54/1c0278556a097f9651e657b873ab08f01b9a9ae4cac128ceb66427d7cd20/Brotli-1.1.0-cp310-cp310-win32.whl", hash = "sha256:be36e3d172dc816333f33520154d708a2657ea63762ec16b62ece02ab5e4daf2", size = 333172, upload-time = "2023-09-07T14:03:35.212Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f7/65/b785722e941193fd8b571afd9edbec2a9b838ddec4375d8af33a50b8dab9/Brotli-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:0c6244521dda65ea562d5a69b9a26120769b7a9fb3db2fe9545935ed6735b128", size = 357255, upload-time = "2023-09-07T14:03:36.447Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/96/12/ad41e7fadd5db55459c4c401842b47f7fee51068f86dd2894dd0dcfc2d2a/Brotli-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a3daabb76a78f829cafc365531c972016e4aa8d5b4bf60660ad8ecee19df7ccc", size = 873068, upload-time = "2023-09-07T14:03:37.779Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/95/4e/5afab7b2b4b61a84e9c75b17814198ce515343a44e2ed4488fac314cd0a9/Brotli-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c8146669223164fc87a7e3de9f81e9423c67a79d6b3447994dfb9c95da16e2d6", size = 446244, upload-time = "2023-09-07T14:03:39.223Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9d/e6/f305eb61fb9a8580c525478a4a34c5ae1a9bcb12c3aee619114940bc513d/Brotli-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30924eb4c57903d5a7526b08ef4a584acc22ab1ffa085faceb521521d2de32dd", size = 2906500, upload-time = "2023-09-07T14:03:40.858Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3e/4f/af6846cfbc1550a3024e5d3775ede1e00474c40882c7bf5b37a43ca35e91/Brotli-1.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ceb64bbc6eac5a140ca649003756940f8d6a7c444a68af170b3187623b43bebf", size = 2943950, upload-time = "2023-09-07T14:03:42.896Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b3/e7/ca2993c7682d8629b62630ebf0d1f3bb3d579e667ce8e7ca03a0a0576a2d/Brotli-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a469274ad18dc0e4d316eefa616d1d0c2ff9da369af19fa6f3daa4f09671fd61", size = 2918527, upload-time = "2023-09-07T14:03:44.552Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b3/96/da98e7bedc4c51104d29cc61e5f449a502dd3dbc211944546a4cc65500d3/Brotli-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:524f35912131cc2cabb00edfd8d573b07f2d9f21fa824bd3fb19725a9cf06327", size = 2845489, upload-time = "2023-09-07T14:03:46.594Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e8/ef/ccbc16947d6ce943a7f57e1a40596c75859eeb6d279c6994eddd69615265/Brotli-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5b3cc074004d968722f51e550b41a27be656ec48f8afaeeb45ebf65b561481dd", size = 2914080, upload-time = "2023-09-07T14:03:48.204Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/80/d6/0bd38d758d1afa62a5524172f0b18626bb2392d717ff94806f741fcd5ee9/Brotli-1.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:19c116e796420b0cee3da1ccec3b764ed2952ccfcc298b55a10e5610ad7885f9", size = 2813051, upload-time = "2023-09-07T14:03:50.348Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/14/56/48859dd5d129d7519e001f06dcfbb6e2cf6db92b2702c0c2ce7d97e086c1/Brotli-1.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:510b5b1bfbe20e1a7b3baf5fed9e9451873559a976c1a78eebaa3b86c57b4265", size = 2938172, upload-time = "2023-09-07T14:03:52.395Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3d/77/a236d5f8cd9e9f4348da5acc75ab032ab1ab2c03cc8f430d24eea2672888/Brotli-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a1fd8a29719ccce974d523580987b7f8229aeace506952fa9ce1d53a033873c8", size = 2933023, upload-time = "2023-09-07T14:03:53.96Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f1/87/3b283efc0f5cb35f7f84c0c240b1e1a1003a5e47141a4881bf87c86d0ce2/Brotli-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c247dd99d39e0338a604f8c2b3bc7061d5c2e9e2ac7ba9cc1be5a69cb6cd832f", size = 2935871, upload-time = "2024-10-18T12:32:16.688Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f3/eb/2be4cc3e2141dc1a43ad4ca1875a72088229de38c68e842746b342667b2a/Brotli-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1b2c248cd517c222d89e74669a4adfa5577e06ab68771a529060cf5a156e9757", size = 2847784, upload-time = "2024-10-18T12:32:18.459Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/66/13/b58ddebfd35edde572ccefe6890cf7c493f0c319aad2a5badee134b4d8ec/Brotli-1.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:2a24c50840d89ded6c9a8fdc7b6ed3692ed4e86f1c4a4a938e1e92def92933e0", size = 3034905, upload-time = "2024-10-18T12:32:20.192Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/84/9c/bc96b6c7db824998a49ed3b38e441a2cae9234da6fa11f6ed17e8cf4f147/Brotli-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f31859074d57b4639318523d6ffdca586ace54271a73ad23ad021acd807eb14b", size = 2929467, upload-time = "2024-10-18T12:32:21.774Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e7/71/8f161dee223c7ff7fea9d44893fba953ce97cf2c3c33f78ba260a91bcff5/Brotli-1.1.0-cp311-cp311-win32.whl", hash = "sha256:39da8adedf6942d76dc3e46653e52df937a3c4d6d18fdc94a7c29d263b1f5b50", size = 333169, upload-time = "2023-09-07T14:03:55.404Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/02/8a/fece0ee1057643cb2a5bbf59682de13f1725f8482b2c057d4e799d7ade75/Brotli-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:aac0411d20e345dc0920bdec5548e438e999ff68d77564d5e9463a7ca9d3e7b1", size = 357253, upload-time = "2023-09-07T14:03:56.643Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5c/d0/5373ae13b93fe00095a58efcbce837fd470ca39f703a235d2a999baadfbc/Brotli-1.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:32d95b80260d79926f5fab3c41701dbb818fde1c9da590e77e571eefd14abe28", size = 815693, upload-time = "2024-10-18T12:32:23.824Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8e/48/f6e1cdf86751300c288c1459724bfa6917a80e30dbfc326f92cea5d3683a/Brotli-1.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b760c65308ff1e462f65d69c12e4ae085cff3b332d894637f6273a12a482d09f", size = 422489, upload-time = "2024-10-18T12:32:25.641Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/06/88/564958cedce636d0f1bed313381dfc4b4e3d3f6015a63dae6146e1b8c65c/Brotli-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:316cc9b17edf613ac76b1f1f305d2a748f1b976b033b049a6ecdfd5612c70409", size = 873081, upload-time = "2023-09-07T14:03:57.967Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/58/79/b7026a8bb65da9a6bb7d14329fd2bd48d2b7f86d7329d5cc8ddc6a90526f/Brotli-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:caf9ee9a5775f3111642d33b86237b05808dafcd6268faa492250e9b78046eb2", size = 446244, upload-time = "2023-09-07T14:03:59.319Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e5/18/c18c32ecea41b6c0004e15606e274006366fe19436b6adccc1ae7b2e50c2/Brotli-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70051525001750221daa10907c77830bc889cb6d865cc0b813d9db7fefc21451", size = 2906505, upload-time = "2023-09-07T14:04:01.327Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/08/c8/69ec0496b1ada7569b62d85893d928e865df29b90736558d6c98c2031208/Brotli-1.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f4bf76817c14aa98cc6697ac02f3972cb8c3da93e9ef16b9c66573a68014f91", size = 2944152, upload-time = "2023-09-07T14:04:03.033Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ab/fb/0517cea182219d6768113a38167ef6d4eb157a033178cc938033a552ed6d/Brotli-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0c5516f0aed654134a2fc936325cc2e642f8a0e096d075209672eb321cff408", size = 2919252, upload-time = "2023-09-07T14:04:04.675Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c7/53/73a3431662e33ae61a5c80b1b9d2d18f58dfa910ae8dd696e57d39f1a2f5/Brotli-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c3020404e0b5eefd7c9485ccf8393cfb75ec38ce75586e046573c9dc29967a0", size = 2845955, upload-time = "2023-09-07T14:04:06.585Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/55/ac/bd280708d9c5ebdbf9de01459e625a3e3803cce0784f47d633562cf40e83/Brotli-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4ed11165dd45ce798d99a136808a794a748d5dc38511303239d4e2363c0695dc", size = 2914304, upload-time = "2023-09-07T14:04:08.668Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/76/58/5c391b41ecfc4527d2cc3350719b02e87cb424ef8ba2023fb662f9bf743c/Brotli-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4093c631e96fdd49e0377a9c167bfd75b6d0bad2ace734c6eb20b348bc3ea180", size = 2814452, upload-time = "2023-09-07T14:04:10.736Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c7/4e/91b8256dfe99c407f174924b65a01f5305e303f486cc7a2e8a5d43c8bec3/Brotli-1.1.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e4c4629ddad63006efa0ef968c8e4751c5868ff0b1c5c40f76524e894c50248", size = 2938751, upload-time = "2023-09-07T14:04:12.875Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5a/a6/e2a39a5d3b412938362bbbeba5af904092bf3f95b867b4a3eb856104074e/Brotli-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:861bf317735688269936f755fa136a99d1ed526883859f86e41a5d43c61d8966", size = 2933757, upload-time = "2023-09-07T14:04:14.551Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/13/f0/358354786280a509482e0e77c1a5459e439766597d280f28cb097642fc26/Brotli-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:87a3044c3a35055527ac75e419dfa9f4f3667a1e887ee80360589eb8c90aabb9", size = 2936146, upload-time = "2024-10-18T12:32:27.257Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/80/f7/daf538c1060d3a88266b80ecc1d1c98b79553b3f117a485653f17070ea2a/Brotli-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c5529b34c1c9d937168297f2c1fde7ebe9ebdd5e121297ff9c043bdb2ae3d6fb", size = 2848055, upload-time = "2024-10-18T12:32:29.376Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ad/cf/0eaa0585c4077d3c2d1edf322d8e97aabf317941d3a72d7b3ad8bce004b0/Brotli-1.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ca63e1890ede90b2e4454f9a65135a4d387a4585ff8282bb72964fab893f2111", size = 3035102, upload-time = "2024-10-18T12:32:31.371Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d8/63/1c1585b2aa554fe6dbce30f0c18bdbc877fa9a1bf5ff17677d9cca0ac122/Brotli-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e79e6520141d792237c70bcd7a3b122d00f2613769ae0cb61c52e89fd3443839", size = 2930029, upload-time = "2024-10-18T12:32:33.293Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5f/3b/4e3fd1893eb3bbfef8e5a80d4508bec17a57bb92d586c85c12d28666bb13/Brotli-1.1.0-cp312-cp312-win32.whl", hash = "sha256:5f4d5ea15c9382135076d2fb28dde923352fe02951e66935a9efaac8f10e81b0", size = 333276, upload-time = "2023-09-07T14:04:16.49Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3d/d5/942051b45a9e883b5b6e98c041698b1eb2012d25e5948c58d6bf85b1bb43/Brotli-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:906bc3a79de8c4ae5b86d3d75a8b77e44404b0f4261714306e3ad248d8ab0951", size = 357255, upload-time = "2023-09-07T14:04:17.83Z" }, +] + +[[package]] +name = "cachelib" +version = "0.13.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1d/69/0b5c1259e12fbcf5c2abe5934b5c0c1294ec0f845e2b4b2a51a91d79a4fb/cachelib-0.13.0.tar.gz", hash = "sha256:209d8996e3c57595bee274ff97116d1d73c4980b2fd9a34c7846cd07fd2e1a48", size = 34418, upload-time = "2024-04-13T14:18:27.782Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9b/42/960fc9896ddeb301716fdd554bab7941c35fb90a1dc7260b77df3366f87f/cachelib-0.13.0-py3-none-any.whl", hash = "sha256:8c8019e53b6302967d4e8329a504acf75e7bc46130291d30188a6e4e58162516", size = 20914, upload-time = "2024-04-13T14:18:26.361Z" }, +] + +[[package]] +name = "cachetools" +version = "5.3.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b3/4d/27a3e6dd09011649ad5210bdf963765bc8fa81a0827a4fc01bafd2705c5b/cachetools-5.3.3.tar.gz", hash = "sha256:ba29e2dfa0b8b556606f097407ed1aa62080ee108ab0dc5ec9d6a723a007d105", size = 26522, upload-time = "2024-02-26T20:33:23.386Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fb/2b/a64c2d25a37aeb921fddb929111413049fc5f8b9a4c1aefaffaafe768d54/cachetools-5.3.3-py3-none-any.whl", hash = "sha256:0abad1021d3f8325b2fc1d2e9c8b9c9d57b04c3932657a72465447332c24d945", size = 9325, upload-time = "2024-02-26T20:33:20.308Z" }, +] + +[[package]] +name = "captcha" +version = "0.7.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "pillow" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b4/65/8e186bb798f33ba390eab897c995b0fcee92bc030e0f40cb8ea01f34dd07/captcha-0.7.1.tar.gz", hash = "sha256:a1b462bcc633a64d8db5efa7754548a877c698d98f87716c620a707364cabd6b", size = 226561, upload-time = "2025-03-01T05:00:13.395Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/da/ff/3f0982ecd37c2d6a7266c22e7ea2e47d0773fe449984184c5316459d2776/captcha-0.7.1-py3-none-any.whl", hash = "sha256:8b73b5aba841ad1e5bdb856205bf5f09560b728ee890eb9dae42901219c8c599", size = 147606, upload-time = "2025-03-01T05:00:10.433Z" }, +] + +[[package]] +name = "cbor" +version = "1.0.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9b/99/01c6a987c920500189eb74a291bd3a388e6c7cf85736bb6b066d9833315e/cbor-1.0.0.tar.gz", hash = "sha256:13225a262ddf5615cbd9fd55a76a0d53069d18b07d2e9f19c39e6acb8609bbb6", size = 20096, upload-time = "2016-02-09T23:11:12.726Z" } + +[[package]] +name = "cbor2" +version = "5.6.5" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e4/aa/ba55b47d51d27911981a18743b4d3cebfabccbb0598c09801b734cec4184/cbor2-5.6.5.tar.gz", hash = "sha256:b682820677ee1dbba45f7da11898d2720f92e06be36acec290867d5ebf3d7e09", size = 100886, upload-time = "2024-10-09T12:26:24.106Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f1/f2/eada1d3fcaeddbd0f9a25381eed6b150f2858cf615d33ef7c14f9f86218a/cbor2-5.6.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e16c4a87fc999b4926f5c8f6c696b0d251b4745bc40f6c5aee51d69b30b15ca2", size = 66473, upload-time = "2024-10-09T12:25:27.157Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/62/d8/33ec188eeff9c5e2e8ee8e214d15a0edf8fd001eefa01730e27834cfd410/cbor2-5.6.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:87026fc838370d69f23ed8572939bd71cea2b3f6c8f8bb8283f573374b4d7f33", size = 67421, upload-time = "2024-10-09T12:25:28.962Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fa/88/9b5fc312f21a10e048c348cead933e9f891cc09a295757957d8a5726641f/cbor2-5.6.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a88f029522aec5425fc2f941b3df90da7688b6756bd3f0472ab886d21208acbd", size = 253856, upload-time = "2024-10-09T12:25:29.968Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bb/41/debb6f35d240caa8078a4a27c03e7eed09737575c564d619e1ee0e829616/cbor2-5.6.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9d15b638539b68aa5d5eacc56099b4543a38b2d2c896055dccf7e83d24b7955", size = 242074, upload-time = "2024-10-09T12:25:31.585Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0b/ba/5c07b001bb26bd91d96e4b3b720676a909e1acdbaf7d250a47c5d7a5a0f7/cbor2-5.6.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:47261f54a024839ec649b950013c4de5b5f521afe592a2688eebbe22430df1dc", size = 241723, upload-time = "2024-10-09T12:25:33.264Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b2/d5/27eced2cee4725bb876875f26d659063e2b7750d0dfb7572f451b569c4d1/cbor2-5.6.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:559dcf0d897260a9e95e7b43556a62253e84550b77147a1ad4d2c389a2a30192", size = 240534, upload-time = "2024-10-09T12:25:34.29Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/20/55/bd657fdedb0d046762f9bf567b262b233175995d40302cdaa71d9aadcec8/cbor2-5.6.5-cp310-cp310-win_amd64.whl", hash = "sha256:5b856fda4c50c5bc73ed3664e64211fa4f015970ed7a15a4d6361bd48462feaf", size = 66291, upload-time = "2024-10-09T12:25:35.763Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bd/b7/ef045245180510305648fd604244d3bb1ecf1b20de68f42ab5bc20198024/cbor2-5.6.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:863e0983989d56d5071270790e7ed8ddbda88c9e5288efdb759aba2efee670bc", size = 66452, upload-time = "2024-10-09T12:25:36.676Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/41/20/5a9d93f86b1e8fd9d9db33aff39c0e3a8459e0803ec24bd837d8b56d4a1d/cbor2-5.6.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5cff06464b8f4ca6eb9abcba67bda8f8334a058abc01005c8e616728c387ad32", size = 67421, upload-time = "2024-10-09T12:25:38.114Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0f/1e/2010f6d02dd117df88df64baf3eeca6aa6614cc81bdd6bfabf615889cf1f/cbor2-5.6.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4c7dbcdc59ea7f5a745d3e30ee5e6b6ff5ce7ac244aa3de6786391b10027bb3", size = 260756, upload-time = "2024-10-09T12:25:39.657Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e1/84/e177d9bef4749d14f31c513b25e341ac84e403e2ffa2bde562eac9e6184b/cbor2-5.6.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34cf5ab0dc310c3d0196caa6ae062dc09f6c242e2544bea01691fe60c0230596", size = 249210, upload-time = "2024-10-09T12:25:41.316Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/38/75/ebfdbb281104b46419fe7cb65979de9927b75acebcb6afa0af291f728cd2/cbor2-5.6.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6797b824b26a30794f2b169c0575301ca9b74ae99064e71d16e6ba0c9057de51", size = 249138, upload-time = "2024-10-09T12:25:42.432Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b2/1e/12d887fb1a8227a16181eeec5d43057e251204626d73e1c20a77046ac1b1/cbor2-5.6.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:73b9647eed1493097db6aad61e03d8f1252080ee041a1755de18000dd2c05f37", size = 247156, upload-time = "2024-10-09T12:25:43.588Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6f/76/478c12193de9517ce691bb8a3f7c00eafdd6a1bc3f7f23282ecdd99d02ec/cbor2-5.6.5-cp311-cp311-win_amd64.whl", hash = "sha256:6e14a1bf6269d25e02ef1d4008e0ce8880aa271d7c6b4c329dba48645764f60e", size = 66319, upload-time = "2024-10-09T12:25:44.621Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/57/af/84ced14c541451696825b7b8ccbb7668f688372ad8ee74aaca4311e79672/cbor2-5.6.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e25c2aebc9db99af7190e2261168cdde8ed3d639ca06868e4f477cf3a228a8e9", size = 67553, upload-time = "2024-10-09T12:25:45.767Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f2/d6/f63a840c68fed4de67d5441947af2dc695152cc488bb0e57312832fb923a/cbor2-5.6.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fde21ac1cf29336a31615a2c469a9cb03cf0add3ae480672d4d38cda467d07fc", size = 67569, upload-time = "2024-10-09T12:25:46.665Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/77/ac/5fb79db6e882ec29680f4a974d35c098020a1b4709cad077667a8c3f4676/cbor2-5.6.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a8947c102cac79d049eadbd5e2ffb8189952890df7cbc3ee262bbc2f95b011a9", size = 276610, upload-time = "2024-10-09T12:25:48.14Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cf/cb/70751377d94112001d46c311b5c40b45f34863dfa78a6bc71b71f40c8c7f/cbor2-5.6.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38886c41bebcd7dca57739439455bce759f1e4c551b511f618b8e9c1295b431b", size = 270004, upload-time = "2024-10-09T12:25:49.769Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f1/90/08800367e920aef31b93bd7b0cd6fadcb3a3f2243f4ed77a0d1c76f22b99/cbor2-5.6.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ae2b49226224e92851c333b91d83292ec62eba53a19c68a79890ce35f1230d70", size = 264913, upload-time = "2024-10-09T12:25:50.92Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a8/9c/76b11a5ea7548bccb0dfef3e8fb3ede48bfeb39348f0c217519e0c40d33a/cbor2-5.6.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f2764804ffb6553283fc4afb10a280715905a4cea4d6dc7c90d3e89c4a93bc8d", size = 266751, upload-time = "2024-10-09T12:25:52.777Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/10/18/3866693a87c90cb12f7942e791d0f03a40ba44887dde7b7fc85319647efe/cbor2-5.6.5-cp312-cp312-win_amd64.whl", hash = "sha256:a3ac50485cf67dfaab170a3e7b527630e93cb0a6af8cdaa403054215dff93adf", size = 66739, upload-time = "2024-10-09T12:25:54.606Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9b/ef/1c4698cac96d792005ef0611832f38eaee477c275ab4b02cbfc4daba7ad3/cbor2-5.6.5-py3-none-any.whl", hash = "sha256:3038523b8fc7de312bb9cdcbbbd599987e64307c4db357cd2030c472a6c7d468", size = 23752, upload-time = "2024-10-09T12:26:23.167Z" }, +] + +[[package]] +name = "certifi" +version = "2025.7.14" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b3/76/52c535bcebe74590f296d6c77c86dabf761c41980e1347a2422e4aa2ae41/certifi-2025.7.14.tar.gz", hash = "sha256:8ea99dbdfaaf2ba2f9bac77b9249ef62ec5218e7c2b2e903378ed5fccf765995", size = 163981, upload-time = "2025-07-14T03:29:28.449Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4f/52/34c6cf5bb9285074dc3531c437b3919e825d976fde097a7a73f79e726d03/certifi-2025.7.14-py3-none-any.whl", hash = "sha256:6b31f564a415d79ee77df69d757bb49a5bb53bd9f756cbbe24394ffd6fc1f4b2", size = 162722, upload-time = "2025-07-14T03:29:26.863Z" }, +] + +[[package]] +name = "cffi" +version = "1.17.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "pycparser" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621, upload-time = "2024-09-04T20:45:21.852Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/90/07/f44ca684db4e4f08a3fdc6eeb9a0d15dc6883efc7b8c90357fdbf74e186c/cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14", size = 182191, upload-time = "2024-09-04T20:43:30.027Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/08/fd/cc2fedbd887223f9f5d170c96e57cbf655df9831a6546c1727ae13fa977a/cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67", size = 178592, upload-time = "2024-09-04T20:43:32.108Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/de/cc/4635c320081c78d6ffc2cab0a76025b691a91204f4aa317d568ff9280a2d/cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", size = 426024, upload-time = "2024-09-04T20:43:34.186Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b6/7b/3b2b250f3aab91abe5f8a51ada1b717935fdaec53f790ad4100fe2ec64d1/cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", size = 448188, upload-time = "2024-09-04T20:43:36.286Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d3/48/1b9283ebbf0ec065148d8de05d647a986c5f22586b18120020452fff8f5d/cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", size = 455571, upload-time = "2024-09-04T20:43:38.586Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/40/87/3b8452525437b40f39ca7ff70276679772ee7e8b394934ff60e63b7b090c/cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", size = 436687, upload-time = "2024-09-04T20:43:40.084Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8d/fb/4da72871d177d63649ac449aec2e8a29efe0274035880c7af59101ca2232/cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", size = 446211, upload-time = "2024-09-04T20:43:41.526Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ab/a0/62f00bcb411332106c02b663b26f3545a9ef136f80d5df746c05878f8c4b/cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", size = 461325, upload-time = "2024-09-04T20:43:43.117Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/36/83/76127035ed2e7e27b0787604d99da630ac3123bfb02d8e80c633f218a11d/cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", size = 438784, upload-time = "2024-09-04T20:43:45.256Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/21/81/a6cd025db2f08ac88b901b745c163d884641909641f9b826e8cb87645942/cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", size = 461564, upload-time = "2024-09-04T20:43:46.779Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f8/fe/4d41c2f200c4a457933dbd98d3cf4e911870877bd94d9656cc0fcb390681/cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", size = 171804, upload-time = "2024-09-04T20:43:48.186Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d1/b6/0b0f5ab93b0df4acc49cae758c81fe4e5ef26c3ae2e10cc69249dfd8b3ab/cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", size = 181299, upload-time = "2024-09-04T20:43:49.812Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264, upload-time = "2024-09-04T20:43:51.124Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651, upload-time = "2024-09-04T20:43:52.872Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259, upload-time = "2024-09-04T20:43:56.123Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200, upload-time = "2024-09-04T20:43:57.891Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235, upload-time = "2024-09-04T20:44:00.18Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721, upload-time = "2024-09-04T20:44:01.585Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242, upload-time = "2024-09-04T20:44:03.467Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999, upload-time = "2024-09-04T20:44:05.023Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242, upload-time = "2024-09-04T20:44:06.444Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604, upload-time = "2024-09-04T20:44:08.206Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727, upload-time = "2024-09-04T20:44:09.481Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400, upload-time = "2024-09-04T20:44:10.873Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178, upload-time = "2024-09-04T20:44:12.232Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840, upload-time = "2024-09-04T20:44:13.739Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803, upload-time = "2024-09-04T20:44:15.231Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850, upload-time = "2024-09-04T20:44:17.188Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729, upload-time = "2024-09-04T20:44:18.688Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256, upload-time = "2024-09-04T20:44:20.248Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424, upload-time = "2024-09-04T20:44:21.673Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568, upload-time = "2024-09-04T20:44:23.245Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736, upload-time = "2024-09-04T20:44:24.757Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448, upload-time = "2024-09-04T20:44:26.208Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976, upload-time = "2024-09-04T20:44:27.578Z" }, +] + +[[package]] +name = "chardet" +version = "5.2.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f3/0d/f7b6ab21ec75897ed80c17d79b15951a719226b9fababf1e40ea74d69079/chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/38/6f/f5fbc992a329ee4e0f288c1fe0e2ad9485ed064cac731ed2fe47dcc38cbf/chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/95/28/9901804da60055b406e1a1c5ba7aac1276fb77f1dde635aabfc7fd84b8ab/charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941", size = 201818, upload-time = "2025-05-02T08:31:46.725Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d9/9b/892a8c8af9110935e5adcbb06d9c6fe741b6bb02608c6513983048ba1a18/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd", size = 144649, upload-time = "2025-05-02T08:31:48.889Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7b/a5/4179abd063ff6414223575e008593861d62abfc22455b5d1a44995b7c101/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6", size = 155045, upload-time = "2025-05-02T08:31:50.757Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3b/95/bc08c7dfeddd26b4be8c8287b9bb055716f31077c8b0ea1cd09553794665/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d", size = 147356, upload-time = "2025-05-02T08:31:52.634Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a8/2d/7a5b635aa65284bf3eab7653e8b4151ab420ecbae918d3e359d1947b4d61/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86", size = 149471, upload-time = "2025-05-02T08:31:56.207Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ae/38/51fc6ac74251fd331a8cfdb7ec57beba8c23fd5493f1050f71c87ef77ed0/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c", size = 151317, upload-time = "2025-05-02T08:31:57.613Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b7/17/edee1e32215ee6e9e46c3e482645b46575a44a2d72c7dfd49e49f60ce6bf/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0", size = 146368, upload-time = "2025-05-02T08:31:59.468Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/26/2c/ea3e66f2b5f21fd00b2825c94cafb8c326ea6240cd80a91eb09e4a285830/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef", size = 154491, upload-time = "2025-05-02T08:32:01.219Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/52/47/7be7fa972422ad062e909fd62460d45c3ef4c141805b7078dbab15904ff7/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6", size = 157695, upload-time = "2025-05-02T08:32:03.045Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2f/42/9f02c194da282b2b340f28e5fb60762de1151387a36842a92b533685c61e/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366", size = 154849, upload-time = "2025-05-02T08:32:04.651Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/67/44/89cacd6628f31fb0b63201a618049be4be2a7435a31b55b5eb1c3674547a/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db", size = 150091, upload-time = "2025-05-02T08:32:06.719Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1f/79/4b8da9f712bc079c0f16b6d67b099b0b8d808c2292c937f267d816ec5ecc/charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a", size = 98445, upload-time = "2025-05-02T08:32:08.66Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7d/d7/96970afb4fb66497a40761cdf7bd4f6fca0fc7bafde3a84f836c1f57a926/charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509", size = 105782, upload-time = "2025-05-02T08:32:10.46Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/05/85/4c40d00dcc6284a1c1ad5de5e0996b06f39d8232f1031cd23c2f5c07ee86/charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2", size = 198794, upload-time = "2025-05-02T08:32:11.945Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/41/d9/7a6c0b9db952598e97e93cbdfcb91bacd89b9b88c7c983250a77c008703c/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645", size = 142846, upload-time = "2025-05-02T08:32:13.946Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/66/82/a37989cda2ace7e37f36c1a8ed16c58cf48965a79c2142713244bf945c89/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd", size = 153350, upload-time = "2025-05-02T08:32:15.873Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/df/68/a576b31b694d07b53807269d05ec3f6f1093e9545e8607121995ba7a8313/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8", size = 145657, upload-time = "2025-05-02T08:32:17.283Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/92/9b/ad67f03d74554bed3aefd56fe836e1623a50780f7c998d00ca128924a499/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f", size = 147260, upload-time = "2025-05-02T08:32:18.807Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a6/e6/8aebae25e328160b20e31a7e9929b1578bbdc7f42e66f46595a432f8539e/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7", size = 149164, upload-time = "2025-05-02T08:32:20.333Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8b/f2/b3c2f07dbcc248805f10e67a0262c93308cfa149a4cd3d1fe01f593e5fd2/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9", size = 144571, upload-time = "2025-05-02T08:32:21.86Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/60/5b/c3f3a94bc345bc211622ea59b4bed9ae63c00920e2e8f11824aa5708e8b7/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544", size = 151952, upload-time = "2025-05-02T08:32:23.434Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e2/4d/ff460c8b474122334c2fa394a3f99a04cf11c646da895f81402ae54f5c42/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82", size = 155959, upload-time = "2025-05-02T08:32:24.993Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a2/2b/b964c6a2fda88611a1fe3d4c400d39c66a42d6c169c924818c848f922415/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0", size = 153030, upload-time = "2025-05-02T08:32:26.435Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/59/2e/d3b9811db26a5ebf444bc0fa4f4be5aa6d76fc6e1c0fd537b16c14e849b6/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5", size = 148015, upload-time = "2025-05-02T08:32:28.376Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/90/07/c5fd7c11eafd561bb51220d600a788f1c8d77c5eef37ee49454cc5c35575/charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a", size = 98106, upload-time = "2025-05-02T08:32:30.281Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a8/05/5e33dbef7e2f773d672b6d79f10ec633d4a71cd96db6673625838a4fd532/charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28", size = 105402, upload-time = "2025-05-02T08:32:32.191Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d7/a4/37f4d6035c89cac7930395a35cc0f1b872e652eaafb76a6075943754f095/charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", size = 199936, upload-time = "2025-05-02T08:32:33.712Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ee/8a/1a5e33b73e0d9287274f899d967907cd0bf9c343e651755d9307e0dbf2b3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", size = 143790, upload-time = "2025-05-02T08:32:35.768Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/66/52/59521f1d8e6ab1482164fa21409c5ef44da3e9f653c13ba71becdd98dec3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", size = 153924, upload-time = "2025-05-02T08:32:37.284Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/86/2d/fb55fdf41964ec782febbf33cb64be480a6b8f16ded2dbe8db27a405c09f/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", size = 146626, upload-time = "2025-05-02T08:32:38.803Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8c/73/6ede2ec59bce19b3edf4209d70004253ec5f4e319f9a2e3f2f15601ed5f7/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", size = 148567, upload-time = "2025-05-02T08:32:40.251Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/09/14/957d03c6dc343c04904530b6bef4e5efae5ec7d7990a7cbb868e4595ee30/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", size = 150957, upload-time = "2025-05-02T08:32:41.705Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0d/c8/8174d0e5c10ccebdcb1b53cc959591c4c722a3ad92461a273e86b9f5a302/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", size = 145408, upload-time = "2025-05-02T08:32:43.709Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/58/aa/8904b84bc8084ac19dc52feb4f5952c6df03ffb460a887b42615ee1382e8/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", size = 153399, upload-time = "2025-05-02T08:32:46.197Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c2/26/89ee1f0e264d201cb65cf054aca6038c03b1a0c6b4ae998070392a3ce605/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", size = 156815, upload-time = "2025-05-02T08:32:48.105Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fd/07/68e95b4b345bad3dbbd3a8681737b4338ff2c9df29856a6d6d23ac4c73cb/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", size = 154537, upload-time = "2025-05-02T08:32:49.719Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/77/1a/5eefc0ce04affb98af07bc05f3bac9094513c0e23b0562d64af46a06aae4/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", size = 149565, upload-time = "2025-05-02T08:32:51.404Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/37/a0/2410e5e6032a174c95e0806b1a6585eb21e12f445ebe239fac441995226a/charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", size = 98357, upload-time = "2025-05-02T08:32:53.079Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6c/4f/c02d5c493967af3eda9c771ad4d2bbc8df6f99ddbeb37ceea6e8716a32bc/charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", size = 105776, upload-time = "2025-05-02T08:32:54.573Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" }, +] + +[[package]] +name = "click" +version = "8.2.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, +] + +[[package]] +name = "cn2an" +version = "0.5.22" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "proces" }, + { name = "setuptools" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a1/5c/f565259e568316e5fde4dba292e2ca9cff0619657e4ec9f254f415543f59/cn2an-0.5.22.tar.gz", hash = "sha256:27ae5b56441d7329ed2ececffa026bfa8fc353dcf1fb0d9146b303b9cce3ac37", size = 21399, upload-time = "2023-08-21T11:13:16.535Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1c/3d/3e04a822b8615904269f7126d8b019ae5c3b5c3c78397ec8bab056b02099/cn2an-0.5.22-py3-none-any.whl", hash = "sha256:cba4c8f305b43da01f50696047cca3116c727424ac62338da6a3426e01454f3e", size = 224956, upload-time = "2023-08-21T11:13:14.369Z" }, +] + +[[package]] +name = "cobble" +version = "0.1.4" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/54/7a/a507c709be2c96e1bb6102eb7b7f4026c5e5e223ef7d745a17d239e9d844/cobble-0.1.4.tar.gz", hash = "sha256:de38be1539992c8a06e569630717c485a5f91be2192c461ea2b220607dfa78aa", size = 3805, upload-time = "2024-06-01T18:11:09.528Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d5/e1/3714a2f371985215c219c2a70953d38e3eed81ef165aed061d21de0e998b/cobble-0.1.4-py3-none-any.whl", hash = "sha256:36c91b1655e599fd428e2b95fdd5f0da1ca2e9f1abb0bc871dec21a0e78a2b44", size = 3984, upload-time = "2024-06-01T18:11:07.911Z" }, +] + +[[package]] +name = "cohere" +version = "5.6.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "boto3" }, + { name = "fastavro" }, + { name = "httpx" }, + { name = "httpx-sse" }, + { name = "parameterized" }, + { name = "pydantic" }, + { name = "requests" }, + { name = "tokenizers" }, + { name = "types-requests" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2d/e0/d821fe7b3c0b30893b89a22f4e58d431211156499ca00805568b90aafcf6/cohere-5.6.2.tar.gz", hash = "sha256:6bb901afdfb02f62ad8ed2d82f12d8ea87a6869710f5f880cb89190c4e994805", size = 87357, upload-time = "2024-07-22T14:23:50.446Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b2/a7/0572d6ab1d947bd11aa8fc40fe908635fabc5abf254175943c2228c9d108/cohere-5.6.2-py3-none-any.whl", hash = "sha256:cfecf1343bcaa4091266c5a231fbcb3ccbd80cad05ea093ef80024a117aa3a2f", size = 177409, upload-time = "2024-07-22T14:23:47.907Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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 = "colorclass" +version = "2.2.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d7/1a/31ff00a33569a3b59d65bbdc445c73e12f92ad28195b7ace299f68b9af70/colorclass-2.2.2.tar.gz", hash = "sha256:6d4fe287766166a98ca7bc6f6312daf04a0481b1eda43e7173484051c0ab4366", size = 16709, upload-time = "2021-12-09T00:41:35.661Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/30/b6/daf3e2976932da4ed3579cff7a30a53d22ea9323ee4f0d8e43be60454897/colorclass-2.2.2-py2.py3-none-any.whl", hash = "sha256:6f10c273a0ef7a1150b1120b6095cbdd68e5cf36dfd5d0fc957a2500bbf99a55", size = 18995, upload-time = "2021-12-09T00:41:34.653Z" }, +] + +[[package]] +name = "coloredlogs" +version = "15.0.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "humanfriendly" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cc/c7/eed8f27100517e8c0e6b923d5f0845d0cb99763da6fdee00478f91db7325/coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0", size = 278520, upload-time = "2021-06-11T10:22:45.202Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a7/06/3d6badcf13db419e25b07041d9c7b4a2c331d3f4e7134445ec5df57714cd/coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934", size = 46018, upload-time = "2021-06-11T10:22:42.561Z" }, +] + +[[package]] +name = "compressed-rtf" +version = "1.0.7" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b7/0c/929a4e8ef9d7143f54d77dadb5f370cc7b98534b1bd6e1124d0abe8efb24/compressed_rtf-1.0.7.tar.gz", hash = "sha256:7c30859334839f3cdc7d10796af5b434bb326b9df7cb5a65e95a8eacb2951b0e", size = 8152, upload-time = "2025-03-24T22:39:32.062Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/07/1d/62f5bf92e12335eb63517f42671ed78512d48bbc69e02a942dd7b90f03f0/compressed_rtf-1.0.7-py3-none-any.whl", hash = "sha256:b7904921d78c67a0a4b7fff9fb361a00ae2b447b6edca010ce321cd98fa0fcc0", size = 7968, upload-time = "2025-03-24T23:03:57.433Z" }, +] + +[[package]] +name = "contourpy" +version = "1.3.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +resolution-markers = [ + "python_full_version < '3.11' and sys_platform == 'darwin'", + "python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "(python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux')", +] +dependencies = [ + { name = "numpy", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/66/54/eb9bfc647b19f2009dd5c7f5ec51c4e6ca831725f1aea7a993034f483147/contourpy-1.3.2.tar.gz", hash = "sha256:b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54", size = 13466130, upload-time = "2025-04-15T17:47:53.79Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/12/a3/da4153ec8fe25d263aa48c1a4cbde7f49b59af86f0b6f7862788c60da737/contourpy-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ba38e3f9f330af820c4b27ceb4b9c7feee5fe0493ea53a8720f4792667465934", size = 268551, upload-time = "2025-04-15T17:34:46.581Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2f/6c/330de89ae1087eb622bfca0177d32a7ece50c3ef07b28002de4757d9d875/contourpy-1.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc41ba0714aa2968d1f8674ec97504a8f7e334f48eeacebcaa6256213acb0989", size = 253399, upload-time = "2025-04-15T17:34:51.427Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c1/bd/20c6726b1b7f81a8bee5271bed5c165f0a8e1f572578a9d27e2ccb763cb2/contourpy-1.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9be002b31c558d1ddf1b9b415b162c603405414bacd6932d031c5b5a8b757f0d", size = 312061, upload-time = "2025-04-15T17:34:55.961Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/22/fc/a9665c88f8a2473f823cf1ec601de9e5375050f1958cbb356cdf06ef1ab6/contourpy-1.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8d2e74acbcba3bfdb6d9d8384cdc4f9260cae86ed9beee8bd5f54fee49a430b9", size = 351956, upload-time = "2025-04-15T17:35:00.992Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/25/eb/9f0a0238f305ad8fb7ef42481020d6e20cf15e46be99a1fcf939546a177e/contourpy-1.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e259bced5549ac64410162adc973c5e2fb77f04df4a439d00b478e57a0e65512", size = 320872, upload-time = "2025-04-15T17:35:06.177Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/32/5c/1ee32d1c7956923202f00cf8d2a14a62ed7517bdc0ee1e55301227fc273c/contourpy-1.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad687a04bc802cbe8b9c399c07162a3c35e227e2daccf1668eb1f278cb698631", size = 325027, upload-time = "2025-04-15T17:35:11.244Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/83/bf/9baed89785ba743ef329c2b07fd0611d12bfecbedbdd3eeecf929d8d3b52/contourpy-1.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cdd22595308f53ef2f891040ab2b93d79192513ffccbd7fe19be7aa773a5e09f", size = 1306641, upload-time = "2025-04-15T17:35:26.701Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d4/cc/74e5e83d1e35de2d28bd97033426b450bc4fd96e092a1f7a63dc7369b55d/contourpy-1.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b4f54d6a2defe9f257327b0f243612dd051cc43825587520b1bf74a31e2f6ef2", size = 1374075, upload-time = "2025-04-15T17:35:43.204Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0c/42/17f3b798fd5e033b46a16f8d9fcb39f1aba051307f5ebf441bad1ecf78f8/contourpy-1.3.2-cp310-cp310-win32.whl", hash = "sha256:f939a054192ddc596e031e50bb13b657ce318cf13d264f095ce9db7dc6ae81c0", size = 177534, upload-time = "2025-04-15T17:35:46.554Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/54/ec/5162b8582f2c994721018d0c9ece9dc6ff769d298a8ac6b6a652c307e7df/contourpy-1.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c440093bbc8fc21c637c03bafcbef95ccd963bc6e0514ad887932c18ca2a759a", size = 221188, upload-time = "2025-04-15T17:35:50.064Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b3/b9/ede788a0b56fc5b071639d06c33cb893f68b1178938f3425debebe2dab78/contourpy-1.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a37a2fb93d4df3fc4c0e363ea4d16f83195fc09c891bc8ce072b9d084853445", size = 269636, upload-time = "2025-04-15T17:35:54.473Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e6/75/3469f011d64b8bbfa04f709bfc23e1dd71be54d05b1b083be9f5b22750d1/contourpy-1.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b7cd50c38f500bbcc9b6a46643a40e0913673f869315d8e70de0438817cb7773", size = 254636, upload-time = "2025-04-15T17:35:58.283Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8d/2f/95adb8dae08ce0ebca4fd8e7ad653159565d9739128b2d5977806656fcd2/contourpy-1.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6658ccc7251a4433eebd89ed2672c2ed96fba367fd25ca9512aa92a4b46c4f1", size = 313053, upload-time = "2025-04-15T17:36:03.235Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c3/a6/8ccf97a50f31adfa36917707fe39c9a0cbc24b3bbb58185577f119736cc9/contourpy-1.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:70771a461aaeb335df14deb6c97439973d253ae70660ca085eec25241137ef43", size = 352985, upload-time = "2025-04-15T17:36:08.275Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1d/b6/7925ab9b77386143f39d9c3243fdd101621b4532eb126743201160ffa7e6/contourpy-1.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65a887a6e8c4cd0897507d814b14c54a8c2e2aa4ac9f7686292f9769fcf9a6ab", size = 323750, upload-time = "2025-04-15T17:36:13.29Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c2/f3/20c5d1ef4f4748e52d60771b8560cf00b69d5c6368b5c2e9311bcfa2a08b/contourpy-1.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3859783aefa2b8355697f16642695a5b9792e7a46ab86da1118a4a23a51a33d7", size = 326246, upload-time = "2025-04-15T17:36:18.329Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8c/e5/9dae809e7e0b2d9d70c52b3d24cba134dd3dad979eb3e5e71f5df22ed1f5/contourpy-1.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:eab0f6db315fa4d70f1d8ab514e527f0366ec021ff853d7ed6a2d33605cf4b83", size = 1308728, upload-time = "2025-04-15T17:36:33.878Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e2/4a/0058ba34aeea35c0b442ae61a4f4d4ca84d6df8f91309bc2d43bb8dd248f/contourpy-1.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d91a3ccc7fea94ca0acab82ceb77f396d50a1f67412efe4c526f5d20264e6ecd", size = 1375762, upload-time = "2025-04-15T17:36:51.295Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/09/33/7174bdfc8b7767ef2c08ed81244762d93d5c579336fc0b51ca57b33d1b80/contourpy-1.3.2-cp311-cp311-win32.whl", hash = "sha256:1c48188778d4d2f3d48e4643fb15d8608b1d01e4b4d6b0548d9b336c28fc9b6f", size = 178196, upload-time = "2025-04-15T17:36:55.002Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5e/fe/4029038b4e1c4485cef18e480b0e2cd2d755448bb071eb9977caac80b77b/contourpy-1.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:5ebac872ba09cb8f2131c46b8739a7ff71de28a24c869bcad554477eb089a878", size = 222017, upload-time = "2025-04-15T17:36:58.576Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/34/f7/44785876384eff370c251d58fd65f6ad7f39adce4a093c934d4a67a7c6b6/contourpy-1.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4caf2bcd2969402bf77edc4cb6034c7dd7c0803213b3523f111eb7460a51b8d2", size = 271580, upload-time = "2025-04-15T17:37:03.105Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/93/3b/0004767622a9826ea3d95f0e9d98cd8729015768075d61f9fea8eeca42a8/contourpy-1.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:82199cb78276249796419fe36b7386bd8d2cc3f28b3bc19fe2454fe2e26c4c15", size = 255530, upload-time = "2025-04-15T17:37:07.026Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e7/bb/7bd49e1f4fa805772d9fd130e0d375554ebc771ed7172f48dfcd4ca61549/contourpy-1.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:106fab697af11456fcba3e352ad50effe493a90f893fca6c2ca5c033820cea92", size = 307688, upload-time = "2025-04-15T17:37:11.481Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fc/97/e1d5dbbfa170725ef78357a9a0edc996b09ae4af170927ba8ce977e60a5f/contourpy-1.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d14f12932a8d620e307f715857107b1d1845cc44fdb5da2bc8e850f5ceba9f87", size = 347331, upload-time = "2025-04-15T17:37:18.212Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6f/66/e69e6e904f5ecf6901be3dd16e7e54d41b6ec6ae3405a535286d4418ffb4/contourpy-1.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:532fd26e715560721bb0d5fc7610fce279b3699b018600ab999d1be895b09415", size = 318963, upload-time = "2025-04-15T17:37:22.76Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a8/32/b8a1c8965e4f72482ff2d1ac2cd670ce0b542f203c8e1d34e7c3e6925da7/contourpy-1.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b383144cf2d2c29f01a1e8170f50dacf0eac02d64139dcd709a8ac4eb3cfe", size = 323681, upload-time = "2025-04-15T17:37:33.001Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/30/c6/12a7e6811d08757c7162a541ca4c5c6a34c0f4e98ef2b338791093518e40/contourpy-1.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c49f73e61f1f774650a55d221803b101d966ca0c5a2d6d5e4320ec3997489441", size = 1308674, upload-time = "2025-04-15T17:37:48.64Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2a/8a/bebe5a3f68b484d3a2b8ffaf84704b3e343ef1addea528132ef148e22b3b/contourpy-1.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3d80b2c0300583228ac98d0a927a1ba6a2ba6b8a742463c564f1d419ee5b211e", size = 1380480, upload-time = "2025-04-15T17:38:06.7Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/34/db/fcd325f19b5978fb509a7d55e06d99f5f856294c1991097534360b307cf1/contourpy-1.3.2-cp312-cp312-win32.whl", hash = "sha256:90df94c89a91b7362e1142cbee7568f86514412ab8a2c0d0fca72d7e91b62912", size = 178489, upload-time = "2025-04-15T17:38:10.338Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/01/c8/fadd0b92ffa7b5eb5949bf340a63a4a496a6930a6c37a7ba0f12acb076d6/contourpy-1.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:8c942a01d9163e2e5cfb05cb66110121b8d07ad438a17f9e766317bcb62abf73", size = 223042, upload-time = "2025-04-15T17:38:14.239Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/33/05/b26e3c6ecc05f349ee0013f0bb850a761016d89cec528a98193a48c34033/contourpy-1.3.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fd93cc7f3139b6dd7aab2f26a90dde0aa9fc264dbf70f6740d498a70b860b82c", size = 265681, upload-time = "2025-04-15T17:44:59.314Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2b/25/ac07d6ad12affa7d1ffed11b77417d0a6308170f44ff20fa1d5aa6333f03/contourpy-1.3.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:107ba8a6a7eec58bb475329e6d3b95deba9440667c4d62b9b6063942b61d7f16", size = 315101, upload-time = "2025-04-15T17:45:04.165Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8f/4d/5bb3192bbe9d3f27e3061a6a8e7733c9120e203cb8515767d30973f71030/contourpy-1.3.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ded1706ed0c1049224531b81128efbd5084598f18d8a2d9efae833edbd2b40ad", size = 220599, upload-time = "2025-04-15T17:45:08.456Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ff/c0/91f1215d0d9f9f343e4773ba6c9b89e8c0cc7a64a6263f21139da639d848/contourpy-1.3.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5f5964cdad279256c084b69c3f412b7801e15356b16efa9d78aa974041903da0", size = 266807, upload-time = "2025-04-15T17:45:15.535Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d4/79/6be7e90c955c0487e7712660d6cead01fa17bff98e0ea275737cc2bc8e71/contourpy-1.3.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49b65a95d642d4efa8f64ba12558fcb83407e58a2dfba9d796d77b63ccfcaff5", size = 318729, upload-time = "2025-04-15T17:45:20.166Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/87/68/7f46fb537958e87427d98a4074bcde4b67a70b04900cfc5ce29bc2f556c1/contourpy-1.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8c5acb8dddb0752bf252e01a3035b21443158910ac16a3b0d20e7fed7d534ce5", size = 221791, upload-time = "2025-04-15T17:45:24.794Z" }, +] + +[[package]] +name = "contourpy" +version = "1.3.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +resolution-markers = [ + "python_full_version >= '3.12' and sys_platform == 'darwin'", + "python_full_version >= '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "(python_full_version >= '3.12' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.12' and sys_platform != 'darwin' and sys_platform != 'linux')", + "python_full_version == '3.11.*' and sys_platform == 'darwin'", + "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "(python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'linux')", +] +dependencies = [ + { name = "numpy", marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/91/2e/c4390a31919d8a78b90e8ecf87cd4b4c4f05a5b48d05ec17db8e5404c6f4/contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1", size = 288773, upload-time = "2025-07-26T12:01:02.277Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381", size = 270149, upload-time = "2025-07-26T12:01:04.072Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/30/2e/dd4ced42fefac8470661d7cb7e264808425e6c5d56d175291e93890cce09/contourpy-1.3.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7", size = 329222, upload-time = "2025-07-26T12:01:05.688Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f2/74/cc6ec2548e3d276c71389ea4802a774b7aa3558223b7bade3f25787fafc2/contourpy-1.3.3-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1", size = 377234, upload-time = "2025-07-26T12:01:07.054Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/03/b3/64ef723029f917410f75c09da54254c5f9ea90ef89b143ccadb09df14c15/contourpy-1.3.3-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a", size = 380555, upload-time = "2025-07-26T12:01:08.801Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db", size = 355238, upload-time = "2025-07-26T12:01:10.319Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/98/56/f914f0dd678480708a04cfd2206e7c382533249bc5001eb9f58aa693e200/contourpy-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620", size = 1326218, upload-time = "2025-07-26T12:01:12.659Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fb/d7/4a972334a0c971acd5172389671113ae82aa7527073980c38d5868ff1161/contourpy-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f", size = 1392867, upload-time = "2025-07-26T12:01:15.533Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/75/3e/f2cc6cd56dc8cff46b1a56232eabc6feea52720083ea71ab15523daab796/contourpy-1.3.3-cp311-cp311-win32.whl", hash = "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff", size = 183677, upload-time = "2025-07-26T12:01:17.088Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42", size = 225234, upload-time = "2025-07-26T12:01:18.256Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d9/b6/71771e02c2e004450c12b1120a5f488cad2e4d5b590b1af8bad060360fe4/contourpy-1.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470", size = 193123, upload-time = "2025-07-26T12:01:19.848Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419, upload-time = "2025-07-26T12:01:21.16Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979, upload-time = "2025-07-26T12:01:22.448Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653, upload-time = "2025-07-26T12:01:24.155Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536, upload-time = "2025-07-26T12:01:25.91Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397, upload-time = "2025-07-26T12:01:27.152Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601, upload-time = "2025-07-26T12:01:28.808Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288, upload-time = "2025-07-26T12:01:31.198Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386, upload-time = "2025-07-26T12:01:33.947Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018, upload-time = "2025-07-26T12:01:35.64Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567, upload-time = "2025-07-26T12:01:36.804Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655, upload-time = "2025-07-26T12:01:37.999Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a5/29/8dcfe16f0107943fa92388c23f6e05cff0ba58058c4c95b00280d4c75a14/contourpy-1.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497", size = 278809, upload-time = "2025-07-26T12:02:52.74Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/85/a9/8b37ef4f7dafeb335daee3c8254645ef5725be4d9c6aa70b50ec46ef2f7e/contourpy-1.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8", size = 261593, upload-time = "2025-07-26T12:02:54.037Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0a/59/ebfb8c677c75605cc27f7122c90313fd2f375ff3c8d19a1694bda74aaa63/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e", size = 302202, upload-time = "2025-07-26T12:02:55.947Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3c/37/21972a15834d90bfbfb009b9d004779bd5a07a0ec0234e5ba8f64d5736f4/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989", size = 329207, upload-time = "2025-07-26T12:02:57.468Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0c/58/bd257695f39d05594ca4ad60df5bcb7e32247f9951fd09a9b8edb82d1daa/contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77", size = 225315, upload-time = "2025-07-26T12:02:58.801Z" }, +] + +[[package]] +name = "cramjam" +version = "2.11.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/14/12/34bf6e840a79130dfd0da7badfb6f7810b8fcfd60e75b0539372667b41b6/cramjam-2.11.0.tar.gz", hash = "sha256:5c82500ed91605c2d9781380b378397012e25127e89d64f460fea6aeac4389b4", size = 99100, upload-time = "2025-07-27T21:25:07.559Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/53/d3/20d0402e4e983b66603117ad3dd3b864a05d7997a830206d3ff9cacef9a2/cramjam-2.11.0-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:d0859c65775e8ebf2cbc084bfd51bd0ffda10266da6f9306451123b89f8e5a63", size = 3558999, upload-time = "2025-07-27T21:21:34.105Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f5/a8/a6e2744288938ccd320a5c6f6f3653faa790f933f5edd088c6e5782a2354/cramjam-2.11.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:1d77b9b0aca02a3f6eeeff27fcd315ca5972616c0919ee38e522cce257bcd349", size = 1861558, upload-time = "2025-07-27T21:21:36.624Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/96/29/7961e09a849eea7d8302e7baa6f829dd3ef3faf199cb25ed29b318ae799b/cramjam-2.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:66425bc25b5481359b12a6719b6e7c90ffe76d85d0691f1da7df304bfb8ce45c", size = 1699431, upload-time = "2025-07-27T21:21:38.396Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7a/60/6665e52f01a8919bf37c43dcf0e03b6dd3866f5c4e95440b357d508ee14e/cramjam-2.11.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:bd748d3407ec63e049b3aea1595e218814fccab329b7fb10bb51120a30e9fb7e", size = 2025262, upload-time = "2025-07-27T21:21:40.417Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d7/80/79bd84dbeb109e2c6efb74e661b7bd4c3ba393208ebcf69e2ae9454ae80c/cramjam-2.11.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6d9a23a35b3a105c42a8de60fc2e80281ae6e758f05a3baea0b68eb1ddcb679", size = 1766177, upload-time = "2025-07-27T21:21:42.224Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/28/ef/b43280767ebcde022ba31f1e9902137655a956ae30e920d75630fa67e36e/cramjam-2.11.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:40a75b95e05e38a2a055b2446f09994ce1139151721659315151d4ad6289bbff", size = 1854031, upload-time = "2025-07-27T21:21:43.651Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/60/1c/79d522757c494dfd9e9b208b0604cc7e97b481483cc477144f5705a06ab7/cramjam-2.11.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e5d042c376d2025300da37d65192d06a457918b63b31140f697f85fd8e310b29", size = 2035812, upload-time = "2025-07-27T21:21:45.473Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c8/70/3bf0670380069b3abd4c6b53f61d3148f4e08935569c08efbeaf7550e87d/cramjam-2.11.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cb148b35ab20c75b19a06c27f05732e2a321adbd86fadc93f9466dbd7b1154a7", size = 2067661, upload-time = "2025-07-27T21:21:47.901Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/db/7e/4f6ca98a4b474348e965a529b359184785d1119ab7c4c9ec1280b8bea50a/cramjam-2.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ee47c220f0f5179ddc923ab91fc9e282c27b29fabc60c433dfe06f08084f798", size = 1981523, upload-time = "2025-07-27T21:21:49.704Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8a/6c/b241511c7ffd5f1da29641429bb0e19b5fbcffafde5ba1bbcbf9394ea456/cramjam-2.11.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0cf1b5a81b21ea175c976c3ab09e00494258f4b49b7995efc86060cced3f0b2e", size = 2034251, upload-time = "2025-07-27T21:21:51.252Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/14/5c/4ef926c8c3c1bf6da96f9c53450ff334cdb6d0fc1efced0aea97e2090803/cramjam-2.11.0-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:360c00338ecf48921492455007f904be607fc7818de3d681acbcc542aae2fb36", size = 2155322, upload-time = "2025-07-27T21:21:53.348Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/be/fb/eb2aef7fb2730e56c5a2c9000817ee8fb4a95c92f19cc6e441afed42ec29/cramjam-2.11.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:f31fcc0d30dc3f3e94ea6b4d8e1a855071757c6abf6a7b1e284050ab7d4c299c", size = 2169094, upload-time = "2025-07-27T21:21:55.187Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3b/80/925a5c668dcee1c6f61775067185c5dc9a63c766d5393e5c60d2af4217a7/cramjam-2.11.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:033be66fdceb3d63b2c99b257a98380c4ec22c9e4dca54a2bfec3718cd24e184", size = 2159089, upload-time = "2025-07-27T21:21:57.118Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1f/ac/b2819640eef0592a6de7ca832c0d23c69bd1620f765ce88b60dbc8da9ba2/cramjam-2.11.0-cp310-cp310-win32.whl", hash = "sha256:1c6cea67f6000b81f6bd27d14c8a6f62d00336ca7252fd03ee16f6b70eb5c0d2", size = 1605046, upload-time = "2025-07-27T21:21:58.617Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5a/f4/06af04727b9556721049e2127656d727306d275c518e3d97f9ed4cffd0d8/cramjam-2.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:98aa4a351b047b0f7f9e971585982065028adc2c162c5c23c5d5734c5ccc1077", size = 1710647, upload-time = "2025-07-27T21:22:00.279Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d0/89/8001f6a9b6b6e9fa69bec5319789083475d6f26d52aaea209d3ebf939284/cramjam-2.11.0-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:04cfa39118570e70e920a9b75c733299784b6d269733dbc791d9aaed6edd2615", size = 3559272, upload-time = "2025-07-27T21:22:01.988Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0b/f3/001d00070ca92e5fbe6aacc768e455568b0cde46b0eb944561a4ea132300/cramjam-2.11.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:66a18f68506290349a256375d7aa2f645b9f7993c10fc4cc211db214e4e61d2b", size = 1861743, upload-time = "2025-07-27T21:22:03.754Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c9/35/041a3af01bf3f6158f120070f798546d4383b962b63c35cd91dcbf193e17/cramjam-2.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:50e7d65533857736cd56f6509cf2c4866f28ad84dd15b5bdbf2f8a81e77fa28a", size = 1699631, upload-time = "2025-07-27T21:22:05.192Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/17/eb/5358b238808abebd0c949c42635c3751204ca7cf82b29b984abe9f5e33c8/cramjam-2.11.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1f71989668458fc327ac15396db28d92df22f8024bb12963929798b2729d2df5", size = 2025603, upload-time = "2025-07-27T21:22:06.726Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0e/79/19dba7c03a27408d8d11b5a7a4a7908459cfd4e6f375b73264dc66517bf6/cramjam-2.11.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee77ac543f1e2b22af1e8be3ae589f729491b6090582340aacd77d1d757d9569", size = 1766283, upload-time = "2025-07-27T21:22:08.568Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a4/ad/40e4b3408501d886d082db465c33971655fe82573c535428e52ab905f4d0/cramjam-2.11.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ad52784120e7e4d8a0b5b0517d185b8bf7f74f5e17272857ddc8951a628d9be1", size = 1854407, upload-time = "2025-07-27T21:22:10.518Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/36/6e/c1b60ceb6d7ea6ff8b0bf197520aefe23f878bf2bfb0de65f2b0c2f82cd1/cramjam-2.11.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b86f8e6d9c1b3f9a75b2af870c93ceee0f1b827cd2507387540e053b35d7459", size = 2035793, upload-time = "2025-07-27T21:22:12.504Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9c/ad/32a8d5f4b1e3717787945ec6d71bd1c6e6bccba4b7e903fc0d9d4e4b08c3/cramjam-2.11.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:320d61938950d95da2371b46c406ec433e7955fae9f396c8e1bf148ffc187d11", size = 2067499, upload-time = "2025-07-27T21:22:14.067Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ff/cd/3b5a662736ea62ff7fa4c4a10a85e050bfdaad375cc53dc80427e8afe41c/cramjam-2.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41eafc8c1653a35a5c7e75ad48138f9f60085cc05cd99d592e5298552d944e9f", size = 1981853, upload-time = "2025-07-27T21:22:15.908Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/26/8e/1dbcfaaa7a702ee82ee683ec3a81656934dd7e04a7bc4ee854033686f98a/cramjam-2.11.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03a7316c6bf763dfa34279335b27702321da44c455a64de58112968c0818ec4a", size = 2034514, upload-time = "2025-07-27T21:22:17.352Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/50/62/f11709bfdce74af79a88b410dcb76dedc97612166e759136931bf63cfd7b/cramjam-2.11.0-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:244c2ed8bd7ccbb294a2abe7ca6498db7e89d7eb5e744691dc511a7dc82e65ca", size = 2155343, upload-time = "2025-07-27T21:22:18.854Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8a/6d/3b98b61841a5376d9a9b8468ae58753a8e6cf22be9534a0fa5af4d8621cc/cramjam-2.11.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:405f8790bad36ce0b4bbdb964ad51507bfc7942c78447f25cb828b870a1d86a0", size = 2169367, upload-time = "2025-07-27T21:22:20.389Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/11/72/bd5db5c49dbebc8b002f1c4983101b28d2e7fc9419753db1c31ec22b03ef/cramjam-2.11.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6b1b751a5411032b08fb3ac556160229ca01c6bbe4757bb3a9a40b951ebaac23", size = 2159334, upload-time = "2025-07-27T21:22:22.254Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/34/32/203c57acdb6eea727e7078b2219984e64ed4ad043c996ed56321301ba167/cramjam-2.11.0-cp311-cp311-win32.whl", hash = "sha256:5251585608778b9ac8effed544933df7ad85b4ba21ee9738b551f17798b215ac", size = 1605313, upload-time = "2025-07-27T21:22:24.126Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a9/bd/102d6deb87a8524ac11cddcd31a7612b8f20bf9b473c3c645045e3b957c7/cramjam-2.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:dca88bc8b68ce6d35dafd8c4d5d59a238a56c43fa02b74c2ce5f9dfb0d1ccb46", size = 1710991, upload-time = "2025-07-27T21:22:25.661Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0b/0d/7c84c913a5fae85b773a9dcf8874390f9d68ba0fcc6630efa7ff1541b950/cramjam-2.11.0-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:dba5c14b8b4f73ea1e65720f5a3fe4280c1d27761238378be8274135c60bbc6e", size = 3553368, upload-time = "2025-07-27T21:22:27.162Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2b/cc/4f6d185d8a744776f53035e72831ff8eefc2354f46ab836f4bd3c4f6c138/cramjam-2.11.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:11eb40722b3fcf3e6890fba46c711bf60f8dc26360a24876c85e52d76c33b25b", size = 1860014, upload-time = "2025-07-27T21:22:28.738Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1c/a8/626c76263085c6d5ded0e71823b411e9522bfc93ba6cc59855a5869296e7/cramjam-2.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aeb26e2898994b6e8319f19a4d37c481512acdcc6d30e1b5ecc9d8ec57e835cb", size = 1693512, upload-time = "2025-07-27T21:22:30.999Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e9/52/0851a16a62447532e30ba95a80e638926fdea869a34b4b5b9d0a020083ba/cramjam-2.11.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4f8d82081ed7d8fe52c982bd1f06e4c7631a73fe1fb6d4b3b3f2404f87dc40fe", size = 2025285, upload-time = "2025-07-27T21:22:32.954Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/98/76/122e444f59dbc216451d8e3d8282c9665dc79eaf822f5f1470066be1b695/cramjam-2.11.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:092a3ec26e0a679305018380e4f652eae1b6dfe3fc3b154ee76aa6b92221a17c", size = 1761327, upload-time = "2025-07-27T21:22:34.484Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a3/bc/3a0189aef1af2b29632c039c19a7a1b752bc21a4053582a5464183a0ad3d/cramjam-2.11.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:529d6d667c65fd105d10bd83d1cd3f9869f8fd6c66efac9415c1812281196a92", size = 1854075, upload-time = "2025-07-27T21:22:36.157Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2e/80/8a6343b13778ce52d94bb8d5365a30c3aa951276b1857201fe79d7e2ad25/cramjam-2.11.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:555eb9c90c450e0f76e27d9ff064e64a8b8c6478ab1a5594c91b7bc5c82fd9f0", size = 2032710, upload-time = "2025-07-27T21:22:38.17Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/df/6b/cd1778a207c29eda10791e3dfa018b588001928086e179fc71254793c625/cramjam-2.11.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5edf4c9e32493035b514cf2ba0c969d81ccb31de63bd05490cc8bfe3b431674e", size = 2068353, upload-time = "2025-07-27T21:22:39.615Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/dc/f0/5c2a5cd5711032f3b191ca50cb786c17689b4a9255f9f768866e6c9f04d9/cramjam-2.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fa2fe41f48c4d58d923803383b0737f048918b5a0d10390de9628bb6272b107", size = 1978104, upload-time = "2025-07-27T21:22:41.106Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f9/8b/b363a5fb2c3347504fe9a64f8d0f1e276844f0e532aa7162c061cd1ffee4/cramjam-2.11.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9ca14cf1cabdb0b77d606db1bb9e9ca593b1dbd421fcaf251ec9a5431ec449f3", size = 2030779, upload-time = "2025-07-27T21:22:42.969Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/78/7b/d83dad46adb6c988a74361f81ad9c5c22642be53ad88616a19baedd06243/cramjam-2.11.0-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:309e95bf898829476bccf4fd2c358ec00e7ff73a12f95a3cdeeba4bb1d3683d5", size = 2155297, upload-time = "2025-07-27T21:22:44.6Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1a/be/60d9be4cb33d8740a4aa94c7513f2ef3c4eba4fd13536f086facbafade71/cramjam-2.11.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:86dca35d2f15ef22922411496c220f3c9e315d5512f316fe417461971cc1648d", size = 2169255, upload-time = "2025-07-27T21:22:46.534Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/11/b0/4a595f01a243aec8ad272b160b161c44351190c35d98d7787919d962e9e5/cramjam-2.11.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:193c6488bd2f514cbc0bef5c18fad61a5f9c8d059dd56edf773b3b37f0e85496", size = 2155651, upload-time = "2025-07-27T21:22:48.46Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/38/47/7776659aaa677046b77f527106e53ddd47373416d8fcdb1e1a881ec5dc06/cramjam-2.11.0-cp312-cp312-win32.whl", hash = "sha256:514e2c008a8b4fa823122ca3ecab896eac41d9aa0f5fc881bd6264486c204e32", size = 1603568, upload-time = "2025-07-27T21:22:50.084Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/75/b1/d53002729cfd94c5844ddfaf1233c86d29f2dbfc1b764a6562c41c044199/cramjam-2.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:53fed080476d5f6ad7505883ec5d1ec28ba36c2273db3b3e92d7224fe5e463db", size = 1709287, upload-time = "2025-07-27T21:22:51.534Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bf/8f/82e35ec3c5387f1864f46b3c24bce89a07af8bb3ef242ae47281db2c1848/cramjam-2.11.0-pp310-pypy310_pp73-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:37bed927abc4a7ae2d2669baa3675e21904d8a038ed8e4313326ea7b3be62b2b", size = 3573104, upload-time = "2025-07-27T21:24:40.069Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f0/4e/0c821918080a32ba1e52c040e12dd02dada67728f07305c5f778b808a807/cramjam-2.11.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:50e4a58635fa8c6897d84847d6e065eb69f92811670fc5e9f2d9e3b6279a02b6", size = 1873441, upload-time = "2025-07-27T21:24:42.333Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a8/fd/848d077bf6abc4ce84273d8e3f3a70d61a2240519a339462f699d8acf829/cramjam-2.11.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:3d1ba626dd5f81f7f09bbf59f70b534e2b75e0d6582b056b7bd31b397f1c13e9", size = 1702589, upload-time = "2025-07-27T21:24:44.305Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9d/1c/899818999bbdb59c601756b413e87d37fd65875d1315346c10e367bb3505/cramjam-2.11.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c71e140d5eb3145d61d59d0be0bf72f07cc4cf4b32cb136b09f712a3b1040f5f", size = 1773646, upload-time = "2025-07-27T21:24:46.495Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5f/26/c2813c5422c43b3dcd8b6645bc359f08870737c44325ee4accc18f24eee0/cramjam-2.11.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a6ed7926a5cca28edebad7d0fedd2ad492710ae3524d25fc59a2b20546d9ce1", size = 1994179, upload-time = "2025-07-27T21:24:49.131Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2e/4f/af984f8d7f963f0301812cdd620ddcfd8276461ed7a786c0f89e82b14739/cramjam-2.11.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5eb4ed3cea945b164b0513fd491884993acac2153a27b93a84019c522e8eda82", size = 1714790, upload-time = "2025-07-27T21:24:51.045Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/81/da/b3301962ccd6fce9fefa1ecd8ea479edaeaa38fadb1f34d5391d2587216a/cramjam-2.11.0-pp311-pypy311_pp73-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:52d5db3369f95b27b9f3c14d067acb0b183333613363ed34268c9e04560f997f", size = 3573546, upload-time = "2025-07-27T21:24:52.944Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b6/c2/410ddb8ad4b9dfb129284666293cb6559479645da560f7077dc19d6bee9e/cramjam-2.11.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:4820516366d455b549a44d0e2210ee7c4575882dda677564ce79092588321d54", size = 1873654, upload-time = "2025-07-27T21:24:54.958Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d5/99/f68a443c64f7ce7aff5bed369b0aa5b2fac668fa3dfd441837e316e97a1f/cramjam-2.11.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d9e5db525dc0a950a825202f84ee68d89a072479e07da98795a3469df942d301", size = 1702846, upload-time = "2025-07-27T21:24:57.124Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6c/02/0ff358ab773def1ee3383587906c453d289953171e9c92db84fdd01bf172/cramjam-2.11.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62ab4971199b2270005359cdc379bc5736071dc7c9a228581c5122d9ffaac50c", size = 1773683, upload-time = "2025-07-27T21:24:59.28Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e9/31/3298e15f87c9cf2aabdbdd90b153d8644cf989cb42a45d68a1b71e1f7aaf/cramjam-2.11.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24758375cc5414d3035ca967ebb800e8f24604ececcba3c67d6f0218201ebf2d", size = 1994136, upload-time = "2025-07-27T21:25:01.565Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c7/90/20d1747255f1ee69a412e319da51ea594c18cca195e7a4d4c713f045eff5/cramjam-2.11.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6c2eea545fef1065c7dd4eda991666fd9c783fbc1d226592ccca8d8891c02f23", size = 1714982, upload-time = "2025-07-27T21:25:05.79Z" }, +] + +[[package]] +name = "crawl4ai" +version = "0.3.745" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "aiofiles" }, + { name = "aiosqlite" }, + { name = "beautifulsoup4" }, + { name = "colorama" }, + { name = "html2text" }, + { name = "litellm" }, + { name = "lxml" }, + { name = "numpy" }, + { name = "pillow" }, + { name = "playwright" }, + { name = "python-dotenv" }, + { name = "rank-bm25" }, + { name = "requests" }, + { name = "snowballstemmer" }, + { name = "tf-playwright-stealth" }, + { name = "xxhash" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/02/5a/919e64ff2977d7aa1b2cda4d45f16ff8996cd2c2dc1f55936fb6cd214222/crawl4ai-0.3.745.tar.gz", hash = "sha256:990396d57e10ae7ccabf35c34a317dbd8c59a3ceca475eac75320a8808334438", size = 117277, upload-time = "2024-11-28T13:46:00.285Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ed/7e/ebe351a457140330b20b6d8289b8f243b21de6e6bce505cd15b230a83bcb/Crawl4AI-0.3.745-py3-none-any.whl", hash = "sha256:763e6aba80959e60e1fe70cb9d954a4cf257eb230af30f51fcd99ff641a7a88d", size = 121997, upload-time = "2024-11-28T13:45:57.999Z" }, +] + +[[package]] +name = "cryptography" +version = "45.0.5" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/95/1e/49527ac611af559665f71cbb8f92b332b5ec9c6fbc4e88b0f8e92f5e85df/cryptography-45.0.5.tar.gz", hash = "sha256:72e76caa004ab63accdf26023fccd1d087f6d90ec6048ff33ad0445abf7f605a", size = 744903, upload-time = "2025-07-02T13:06:25.941Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f0/fb/09e28bc0c46d2c547085e60897fea96310574c70fb21cd58a730a45f3403/cryptography-45.0.5-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:101ee65078f6dd3e5a028d4f19c07ffa4dd22cce6a20eaa160f8b5219911e7d8", size = 7043092, upload-time = "2025-07-02T13:05:01.514Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b1/05/2194432935e29b91fb649f6149c1a4f9e6d3d9fc880919f4ad1bcc22641e/cryptography-45.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3a264aae5f7fbb089dbc01e0242d3b67dffe3e6292e1f5182122bdf58e65215d", size = 4205926, upload-time = "2025-07-02T13:05:04.741Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/07/8b/9ef5da82350175e32de245646b1884fc01124f53eb31164c77f95a08d682/cryptography-45.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e74d30ec9c7cb2f404af331d5b4099a9b322a8a6b25c4632755c8757345baac5", size = 4429235, upload-time = "2025-07-02T13:05:07.084Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7c/e1/c809f398adde1994ee53438912192d92a1d0fc0f2d7582659d9ef4c28b0c/cryptography-45.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3af26738f2db354aafe492fb3869e955b12b2ef2e16908c8b9cb928128d42c57", size = 4209785, upload-time = "2025-07-02T13:05:09.321Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d0/8b/07eb6bd5acff58406c5e806eff34a124936f41a4fb52909ffa4d00815f8c/cryptography-45.0.5-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e6c00130ed423201c5bc5544c23359141660b07999ad82e34e7bb8f882bb78e0", size = 3893050, upload-time = "2025-07-02T13:05:11.069Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ec/ef/3333295ed58d900a13c92806b67e62f27876845a9a908c939f040887cca9/cryptography-45.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:dd420e577921c8c2d31289536c386aaa30140b473835e97f83bc71ea9d2baf2d", size = 4457379, upload-time = "2025-07-02T13:05:13.32Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d9/9d/44080674dee514dbb82b21d6fa5d1055368f208304e2ab1828d85c9de8f4/cryptography-45.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:d05a38884db2ba215218745f0781775806bde4f32e07b135348355fe8e4991d9", size = 4209355, upload-time = "2025-07-02T13:05:15.017Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c9/d8/0749f7d39f53f8258e5c18a93131919ac465ee1f9dccaf1b3f420235e0b5/cryptography-45.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:ad0caded895a00261a5b4aa9af828baede54638754b51955a0ac75576b831b27", size = 4456087, upload-time = "2025-07-02T13:05:16.945Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/09/d7/92acac187387bf08902b0bf0699816f08553927bdd6ba3654da0010289b4/cryptography-45.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9024beb59aca9d31d36fcdc1604dd9bbeed0a55bface9f1908df19178e2f116e", size = 4332873, upload-time = "2025-07-02T13:05:18.743Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/03/c2/840e0710da5106a7c3d4153c7215b2736151bba60bf4491bdb421df5056d/cryptography-45.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:91098f02ca81579c85f66df8a588c78f331ca19089763d733e34ad359f474174", size = 4564651, upload-time = "2025-07-02T13:05:21.382Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2e/92/cc723dd6d71e9747a887b94eb3827825c6c24b9e6ce2bb33b847d31d5eaa/cryptography-45.0.5-cp311-abi3-win32.whl", hash = "sha256:926c3ea71a6043921050eaa639137e13dbe7b4ab25800932a8498364fc1abec9", size = 2929050, upload-time = "2025-07-02T13:05:23.39Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1f/10/197da38a5911a48dd5389c043de4aec4b3c94cb836299b01253940788d78/cryptography-45.0.5-cp311-abi3-win_amd64.whl", hash = "sha256:b85980d1e345fe769cfc57c57db2b59cff5464ee0c045d52c0df087e926fbe63", size = 3403224, upload-time = "2025-07-02T13:05:25.202Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fe/2b/160ce8c2765e7a481ce57d55eba1546148583e7b6f85514472b1d151711d/cryptography-45.0.5-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:f3562c2f23c612f2e4a6964a61d942f891d29ee320edb62ff48ffb99f3de9ae8", size = 7017143, upload-time = "2025-07-02T13:05:27.229Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c2/e7/2187be2f871c0221a81f55ee3105d3cf3e273c0a0853651d7011eada0d7e/cryptography-45.0.5-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3fcfbefc4a7f332dece7272a88e410f611e79458fab97b5efe14e54fe476f4fd", size = 4197780, upload-time = "2025-07-02T13:05:29.299Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b9/cf/84210c447c06104e6be9122661159ad4ce7a8190011669afceeaea150524/cryptography-45.0.5-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:460f8c39ba66af7db0545a8c6f2eabcbc5a5528fc1cf6c3fa9a1e44cec33385e", size = 4420091, upload-time = "2025-07-02T13:05:31.221Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3e/6a/cb8b5c8bb82fafffa23aeff8d3a39822593cee6e2f16c5ca5c2ecca344f7/cryptography-45.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:9b4cf6318915dccfe218e69bbec417fdd7c7185aa7aab139a2c0beb7468c89f0", size = 4198711, upload-time = "2025-07-02T13:05:33.062Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/04/f7/36d2d69df69c94cbb2473871926daf0f01ad8e00fe3986ac3c1e8c4ca4b3/cryptography-45.0.5-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2089cc8f70a6e454601525e5bf2779e665d7865af002a5dec8d14e561002e135", size = 3883299, upload-time = "2025-07-02T13:05:34.94Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/82/c7/f0ea40f016de72f81288e9fe8d1f6748036cb5ba6118774317a3ffc6022d/cryptography-45.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0027d566d65a38497bc37e0dd7c2f8ceda73597d2ac9ba93810204f56f52ebc7", size = 4450558, upload-time = "2025-07-02T13:05:37.288Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/06/ae/94b504dc1a3cdf642d710407c62e86296f7da9e66f27ab12a1ee6fdf005b/cryptography-45.0.5-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:be97d3a19c16a9be00edf79dca949c8fa7eff621763666a145f9f9535a5d7f42", size = 4198020, upload-time = "2025-07-02T13:05:39.102Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/05/2b/aaf0adb845d5dabb43480f18f7ca72e94f92c280aa983ddbd0bcd6ecd037/cryptography-45.0.5-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:7760c1c2e1a7084153a0f68fab76e754083b126a47d0117c9ed15e69e2103492", size = 4449759, upload-time = "2025-07-02T13:05:41.398Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/91/e4/f17e02066de63e0100a3a01b56f8f1016973a1d67551beaf585157a86b3f/cryptography-45.0.5-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6ff8728d8d890b3dda5765276d1bc6fb099252915a2cd3aff960c4c195745dd0", size = 4319991, upload-time = "2025-07-02T13:05:43.64Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f2/2e/e2dbd629481b499b14516eed933f3276eb3239f7cee2dcfa4ee6b44d4711/cryptography-45.0.5-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7259038202a47fdecee7e62e0fd0b0738b6daa335354396c6ddebdbe1206af2a", size = 4554189, upload-time = "2025-07-02T13:05:46.045Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f8/ea/a78a0c38f4c8736287b71c2ea3799d173d5ce778c7d6e3c163a95a05ad2a/cryptography-45.0.5-cp37-abi3-win32.whl", hash = "sha256:1e1da5accc0c750056c556a93c3e9cb828970206c68867712ca5805e46dc806f", size = 2911769, upload-time = "2025-07-02T13:05:48.329Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/79/b3/28ac139109d9005ad3f6b6f8976ffede6706a6478e21c889ce36c840918e/cryptography-45.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:90cb0a7bb35959f37e23303b7eed0a32280510030daba3f7fdfbb65defde6a97", size = 3390016, upload-time = "2025-07-02T13:05:50.811Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f8/8b/34394337abe4566848a2bd49b26bcd4b07fd466afd3e8cce4cb79a390869/cryptography-45.0.5-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:206210d03c1193f4e1ff681d22885181d47efa1ab3018766a7b32a7b3d6e6afd", size = 3575762, upload-time = "2025-07-02T13:05:53.166Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8b/5d/a19441c1e89afb0f173ac13178606ca6fab0d3bd3ebc29e9ed1318b507fc/cryptography-45.0.5-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c648025b6840fe62e57107e0a25f604db740e728bd67da4f6f060f03017d5097", size = 4140906, upload-time = "2025-07-02T13:05:55.914Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4b/db/daceb259982a3c2da4e619f45b5bfdec0e922a23de213b2636e78ef0919b/cryptography-45.0.5-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b8fa8b0a35a9982a3c60ec79905ba5bb090fc0b9addcfd3dc2dd04267e45f25e", size = 4374411, upload-time = "2025-07-02T13:05:57.814Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6a/35/5d06ad06402fc522c8bf7eab73422d05e789b4e38fe3206a85e3d6966c11/cryptography-45.0.5-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:14d96584701a887763384f3c47f0ca7c1cce322aa1c31172680eb596b890ec30", size = 4140942, upload-time = "2025-07-02T13:06:00.137Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/65/79/020a5413347e44c382ef1f7f7e7a66817cd6273e3e6b5a72d18177b08b2f/cryptography-45.0.5-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:57c816dfbd1659a367831baca4b775b2a5b43c003daf52e9d57e1d30bc2e1b0e", size = 4374079, upload-time = "2025-07-02T13:06:02.043Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9b/c5/c0e07d84a9a2a8a0ed4f865e58f37c71af3eab7d5e094ff1b21f3f3af3bc/cryptography-45.0.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b9e38e0a83cd51e07f5a48ff9691cae95a79bea28fe4ded168a8e5c6c77e819d", size = 3321362, upload-time = "2025-07-02T13:06:04.463Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c0/71/9bdbcfd58d6ff5084687fe722c58ac718ebedbc98b9f8f93781354e6d286/cryptography-45.0.5-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8c4a6ff8a30e9e3d38ac0539e9a9e02540ab3f827a3394f8852432f6b0ea152e", size = 3587878, upload-time = "2025-07-02T13:06:06.339Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f0/63/83516cfb87f4a8756eaa4203f93b283fda23d210fc14e1e594bd5f20edb6/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:bd4c45986472694e5121084c6ebbd112aa919a25e783b87eb95953c9573906d6", size = 4152447, upload-time = "2025-07-02T13:06:08.345Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/22/11/d2823d2a5a0bd5802b3565437add16f5c8ce1f0778bf3822f89ad2740a38/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:982518cd64c54fcada9d7e5cf28eabd3ee76bd03ab18e08a48cad7e8b6f31b18", size = 4386778, upload-time = "2025-07-02T13:06:10.263Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5f/38/6bf177ca6bce4fe14704ab3e93627c5b0ca05242261a2e43ef3168472540/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:12e55281d993a793b0e883066f590c1ae1e802e3acb67f8b442e721e475e6463", size = 4151627, upload-time = "2025-07-02T13:06:13.097Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/38/6a/69fc67e5266bff68a91bcb81dff8fb0aba4d79a78521a08812048913e16f/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:5aa1e32983d4443e310f726ee4b071ab7569f58eedfdd65e9675484a4eb67bd1", size = 4385593, upload-time = "2025-07-02T13:06:15.689Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f6/34/31a1604c9a9ade0fdab61eb48570e09a796f4d9836121266447b0eaf7feb/cryptography-45.0.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:e357286c1b76403dd384d938f93c46b2b058ed4dfcdce64a770f0537ed3feb6f", size = 3331106, upload-time = "2025-07-02T13:06:18.058Z" }, +] + +[[package]] +name = "cssselect" +version = "1.3.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/72/0a/c3ea9573b1dc2e151abfe88c7fe0c26d1892fe6ed02d0cdb30f0d57029d5/cssselect-1.3.0.tar.gz", hash = "sha256:57f8a99424cfab289a1b6a816a43075a4b00948c86b4dcf3ef4ee7e15f7ab0c7", size = 42870, upload-time = "2025-03-10T09:30:29.638Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ee/58/257350f7db99b4ae12b614a36256d9cc870d71d9e451e79c2dc3b23d7c3c/cssselect-1.3.0-py3-none-any.whl", hash = "sha256:56d1bf3e198080cc1667e137bc51de9cadfca259f03c2d4e09037b3e01e30f0d", size = 18786, upload-time = "2025-03-10T09:30:28.048Z" }, +] + +[[package]] +name = "curl-cffi" +version = "0.12.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "certifi" }, + { name = "cffi" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f4/91/feaf237ab7c5e06cc0a87ed7acdfbd4103beaa3ca5666d78e80b4a044e47/curl_cffi-0.12.0.tar.gz", hash = "sha256:01770d120e2ab82ad076687c7038abe4d614c7e13d7a5378520b027df529dbe7", size = 150452, upload-time = "2025-07-11T05:27:10.607Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/da/58/587f76b06fd7c4176b033275ab63b90e32b6904e5d7c7844311584376cdc/curl_cffi-0.12.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:e663d6692aa923a60fd2f050dad4cccd317dc7dd3d9edceb5230f68017e811eb", size = 5681054, upload-time = "2025-07-11T05:26:56.671Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cd/74/e63d74451dcd0a793da1983024f36bbe400e8a3be8eaf860c004f66d489a/curl_cffi-0.12.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:8f7f1745700fcd4f6d5681cdca27426cc3005c3e17ca9a66fe5753c5001a7314", size = 2990114, upload-time = "2025-07-11T05:26:58.833Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/13/83/10addc9189c8eeb35e7b4c13033e84e174f534caef5df9f520403bcd546d/curl_cffi-0.12.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:368dd6e354933c62d3b35afbecdc5e7e7817ba748db0d23f7276f89b7eec49e8", size = 7947282, upload-time = "2025-07-11T05:27:00.497Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3f/fd/700edf216767c0e25632e24ec66dfb4a047cf6966d3246507f8a384970cd/curl_cffi-0.12.0-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1df994f9ccf27b391259ba0157d415b1e60f01e59914f75280cb9c1eceb3a3c8", size = 7501158, upload-time = "2025-07-11T05:27:02.17Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fe/0b/559443dd7fcdeb424f353c239db3aa0421659f9bab26a89fc65b703cb486/curl_cffi-0.12.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18aadb313a1fe23098e867f9e6e42c57c5c68985521a1fe5fb8ca15bb990341b", size = 8347933, upload-time = "2025-07-11T05:27:03.579Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/57/4f/000397ecd769d663dbfdaea10173e4ee860379118c81e40d27faea4382c6/curl_cffi-0.12.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:c0875e85eda5a5314bf1974ad1ecdcdd61811759b820b6617ec7be6daf85a1a3", size = 8757614, upload-time = "2025-07-11T05:27:05.736Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/aa/01/2ff55d2d73b0d4605fff32112b33894e6b6d79106406d9e1185d680e8930/curl_cffi-0.12.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:b9650b85964ed06c634cfff4ce926255b80195f73edf629a1272b1a19908811d", size = 8731023, upload-time = "2025-07-11T05:27:07.685Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/22/6e/0194d04312fbf6eed0d0fea6dfd361795fcfd53e9dca259a8ad45ff1ccca/curl_cffi-0.12.0-cp39-abi3-win_amd64.whl", hash = "sha256:cdcbc492a68b7f3592a4dc4eb742281aa74d220f55affbd84645795a7fdb3ddc", size = 1610837, upload-time = "2025-07-11T05:27:09.275Z" }, +] + +[[package]] +name = "cycler" +version = "0.12.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30" }, +] + +[[package]] +name = "dashscope" +version = "1.20.11" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "requests" }, + { name = "websocket-client" }, +] +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/25/21/0ddfa1aae7f45b3039d10d61ede77dedfc70d24ff946e7d0ecb92e9a2c85/dashscope-1.20.11-py3-none-any.whl", hash = "sha256:7367802c5ae136c6c1f4f8a16f9aba628e97adefae8afdebce6bbf518d0065d1", size = 1264221, upload-time = "2024-10-14T05:30:25.083Z" }, +] + +[[package]] +name = "datasets" +version = "4.0.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "dill" }, + { name = "filelock" }, + { name = "fsspec", extra = ["http"] }, + { name = "huggingface-hub" }, + { name = "multiprocess" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "pyarrow" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "tqdm" }, + { name = "xxhash" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e3/9d/348ed92110ba5f9b70b51ca1078d4809767a835aa2b7ce7e74ad2b98323d/datasets-4.0.0.tar.gz", hash = "sha256:9657e7140a9050db13443ba21cb5de185af8af944479b00e7ff1e00a61c8dbf1", size = 569566, upload-time = "2025-07-09T14:35:52.431Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/eb/62/eb8157afb21bd229c864521c1ab4fa8e9b4f1b06bafdd8c4668a7a31b5dd/datasets-4.0.0-py3-none-any.whl", hash = "sha256:7ef95e62025fd122882dbce6cb904c8cd3fbc829de6669a5eb939c77d50e203d", size = 494825, upload-time = "2025-07-09T14:35:50.658Z" }, +] + +[[package]] +name = "datrie" +version = "0.8.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9d/fe/db74bd405d515f06657f11ad529878fd389576dca4812bea6f98d9b31574/datrie-0.8.2.tar.gz", hash = "sha256:525b08f638d5cf6115df6ccd818e5a01298cd230b2dac91c8ff2e6499d18765d", size = 63278, upload-time = "2020-03-26T03:31:53.681Z" } + +[[package]] +name = "debugpy" +version = "1.8.15" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8c/8b/3a9a28ddb750a76eaec445c7f4d3147ea2c579a97dbd9e25d39001b92b21/debugpy-1.8.15.tar.gz", hash = "sha256:58d7a20b7773ab5ee6bdfb2e6cf622fdf1e40c9d5aef2857d85391526719ac00", size = 1643279, upload-time = "2025-07-15T16:43:29.135Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/69/51/0b4315169f0d945271db037ae6b98c0548a2d48cc036335cd1b2f5516c1b/debugpy-1.8.15-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:e9a8125c85172e3ec30985012e7a81ea5e70bbb836637f8a4104f454f9b06c97", size = 2084890, upload-time = "2025-07-15T16:43:31.239Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/36/cc/a5391dedb079280d7b72418022e00ba8227ae0b5bc8b2e3d1ecffc5d6b01/debugpy-1.8.15-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fd0b6b5eccaa745c214fd240ea82f46049d99ef74b185a3517dad3ea1ec55d9", size = 3561470, upload-time = "2025-07-15T16:43:32.515Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e8/92/acf64b92010c66b33c077dee3862c733798a2c90e7d14b25c01d771e2a0d/debugpy-1.8.15-cp310-cp310-win32.whl", hash = "sha256:8181cce4d344010f6bfe94a531c351a46a96b0f7987750932b2908e7a1e14a55", size = 5229194, upload-time = "2025-07-15T16:43:33.997Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3f/f5/c58c015c9ff78de35901bea3ab4dbf7946d7a4aa867ee73875df06ba6468/debugpy-1.8.15-cp310-cp310-win_amd64.whl", hash = "sha256:af2dcae4e4cd6e8b35f982ccab29fe65f7e8766e10720a717bc80c464584ee21", size = 5260900, upload-time = "2025-07-15T16:43:35.413Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d2/b3/1c44a2ed311199ab11c2299c9474a6c7cd80d19278defd333aeb7c287995/debugpy-1.8.15-cp311-cp311-macosx_14_0_universal2.whl", hash = "sha256:babc4fb1962dd6a37e94d611280e3d0d11a1f5e6c72ac9b3d87a08212c4b6dd3", size = 2183442, upload-time = "2025-07-15T16:43:36.733Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f6/69/e2dcb721491e1c294d348681227c9b44fb95218f379aa88e12a19d85528d/debugpy-1.8.15-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f778e68f2986a58479d0ac4f643e0b8c82fdd97c2e200d4d61e7c2d13838eb53", size = 3134215, upload-time = "2025-07-15T16:43:38.116Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/17/76/4ce63b95d8294dcf2fd1820860b300a420d077df4e93afcaa25a984c2ca7/debugpy-1.8.15-cp311-cp311-win32.whl", hash = "sha256:f9d1b5abd75cd965e2deabb1a06b0e93a1546f31f9f621d2705e78104377c702", size = 5154037, upload-time = "2025-07-15T16:43:39.471Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c2/a7/e5a7c784465eb9c976d84408873d597dc7ce74a0fc69ed009548a1a94813/debugpy-1.8.15-cp311-cp311-win_amd64.whl", hash = "sha256:62954fb904bec463e2b5a415777f6d1926c97febb08ef1694da0e5d1463c5c3b", size = 5178133, upload-time = "2025-07-15T16:43:40.969Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ab/4a/4508d256e52897f5cdfee6a6d7580974811e911c6d01321df3264508a5ac/debugpy-1.8.15-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:3dcc7225cb317469721ab5136cda9ff9c8b6e6fb43e87c9e15d5b108b99d01ba", size = 2511197, upload-time = "2025-07-15T16:43:42.343Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/99/8d/7f6ef1097e7fecf26b4ef72338d08e41644a41b7ee958a19f494ffcffc29/debugpy-1.8.15-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:047a493ca93c85ccede1dbbaf4e66816794bdc214213dde41a9a61e42d27f8fc", size = 4229517, upload-time = "2025-07-15T16:43:44.14Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3f/e8/e8c6a9aa33a9c9c6dacbf31747384f6ed2adde4de2e9693c766bdf323aa3/debugpy-1.8.15-cp312-cp312-win32.whl", hash = "sha256:b08e9b0bc260cf324c890626961dad4ffd973f7568fbf57feb3c3a65ab6b6327", size = 5276132, upload-time = "2025-07-15T16:43:45.529Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e9/ad/231050c6177b3476b85fcea01e565dac83607b5233d003ff067e2ee44d8f/debugpy-1.8.15-cp312-cp312-win_amd64.whl", hash = "sha256:e2a4fe357c92334272eb2845fcfcdbec3ef9f22c16cf613c388ac0887aed15fa", size = 5317645, upload-time = "2025-07-15T16:43:46.968Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/07/d5/98748d9860e767a1248b5e31ffa7ce8cb7006e97bf8abbf3d891d0a8ba4e/debugpy-1.8.15-py2.py3-none-any.whl", hash = "sha256:bce2e6c5ff4f2e00b98d45e7e01a49c7b489ff6df5f12d881c67d2f1ac635f3d", size = 5282697, upload-time = "2025-07-15T16:44:07.996Z" }, +] + +[[package]] +name = "decorator" +version = "5.2.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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 = "deepl" +version = "1.18.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "requests" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/82/46/1dfe623c24aac5a341bf7eedabb8d1f719df8fd3a6f45aefcd0b83e96ce0/deepl-1.18.0.tar.gz", hash = "sha256:5ae41763939441edbca7640fd344280cbee47d490641ce35206910a8b01e778e", size = 38888, upload-time = "2024-04-26T10:09:03.701Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d3/02/fe0df955618c204ea51a2cee85461e736a939cf9d593b314adeb1c2c5d2e/deepl-1.18.0-py3-none-any.whl", hash = "sha256:2afe9adc459f5c591282e4d74570a0dc5041554d54dd687f72d3b0b77936e9ce", size = 35265, upload-time = "2024-04-26T10:09:00.978Z" }, +] + +[[package]] +name = "demjson3" +version = "3.0.6" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f7/d2/6a81a9b5311d50542e11218b470dafd8adbaf1b3e51fc1fddd8a57eed691/demjson3-3.0.6.tar.gz", hash = "sha256:37c83b0c6eb08d25defc88df0a2a4875d58a7809a9650bd6eee7afd8053cdbac", size = 131477, upload-time = "2022-10-22T19:09:05.379Z" } + +[[package]] +name = "deprecated" +version = "1.2.18" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "wrapt" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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 = "dill" +version = "0.3.8" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/17/4d/ac7ffa80c69ea1df30a8aa11b3578692a5118e7cd1aa157e3ef73b092d15/dill-0.3.8.tar.gz", hash = "sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca", size = 184847, upload-time = "2024-01-27T23:42:16.145Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c9/7a/cef76fd8438a42f96db64ddaa85280485a9c395e7df3db8158cfec1eee34/dill-0.3.8-py3-none-any.whl", hash = "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7", size = 116252, upload-time = "2024-01-27T23:42:14.239Z" }, +] + +[[package]] +name = "discord-py" +version = "2.3.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "aiohttp" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6f/cb/a360101905102684a4fe6fc543976842383f54ddeeef020959e4965c416e/discord.py-2.3.2.tar.gz", hash = "sha256:4560f70f2eddba7e83370ecebd237ac09fbb4980dc66507482b0c0e5b8f76b9c", size = 978172, upload-time = "2023-08-10T21:44:07.733Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9c/7e/5f1b24b2ced0c4b3042204f7827b57c7dcb26d368e9b0fde8cec7853cf30/discord.py-2.3.2-py3-none-any.whl", hash = "sha256:9da4679fc3cb10c64b388284700dc998663e0e57328283bbfcfc2525ec5960a6", size = 1084904, upload-time = "2023-08-10T21:44:05.285Z" }, +] + +[[package]] +name = "diskcache" +version = "5.6.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3f/21/1c1ffc1a039ddcc459db43cc108658f32c57d271d7289a2794e401d0fdb6/diskcache-5.6.3.tar.gz", hash = "sha256:2c3a3fa2743d8535d832ec61c2054a1641f41775aa7c556758a109941e33e4fc" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3f/27/4570e78fc0bf5ea0ca45eb1de3818a23787af9b390c0b0a0033a1b8236f9/diskcache-5.6.3-py3-none-any.whl", hash = "sha256:5e31b2d5fbad117cc363ebaf6b689474db18a1f6438bc82358b024abd4c2ca19" }, +] + +[[package]] +name = "distro" +version = "1.9.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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 = "docstring-parser" +version = "0.17.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b2/9d/c3b43da9515bd270df0f80548d9944e389870713cc1fe2b8fb35fe2bcefd/docstring_parser-0.17.0.tar.gz", hash = "sha256:583de4a309722b3315439bb31d64ba3eebada841f2e2cee23b99df001434c912", size = 27442, upload-time = "2025-07-21T07:35:01.868Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl", hash = "sha256:cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708", size = 36896, upload-time = "2025-07-21T07:35:00.684Z" }, +] + +[[package]] +name = "docutils" +version = "0.21.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444, upload-time = "2024-04-23T18:57:18.24Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408, upload-time = "2024-04-23T18:57:14.835Z" }, +] + +[[package]] +name = "duckduckgo-search" +version = "7.5.5" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "click" }, + { name = "lxml" }, + { name = "primp" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/13/dc/919d3d51ed702890a3e6e736e1e152d5d90856393200306e82fb54fde39e/duckduckgo_search-7.5.5.tar.gz", hash = "sha256:44ef03bfa5484bada786590f2d4c213251131765721383a177a0da6fa5c5e41a", size = 24768, upload-time = "2025-03-27T08:11:26.951Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fc/da/8376678b4a9ae0f9418d93df9c9cf851dced49c95ceb38daac6651e38f7a/duckduckgo_search-7.5.5-py3-none-any.whl", hash = "sha256:c71a0661aa436f215d9a05d653af424affb58825ab3e79f3b788053cbdee9ebc", size = 20421, upload-time = "2025-03-27T08:11:25.515Z" }, +] + +[[package]] +name = "easygui" +version = "0.98.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cc/ad/e35f7a30272d322be09dc98592d2f55d27cc933a7fde8baccbbeb2bd9409/easygui-0.98.3.tar.gz", hash = "sha256:d653ff79ee1f42f63b5a090f2f98ce02335d86ad8963b3ce2661805cafe99a04", size = 85583, upload-time = "2022-04-01T13:15:50.752Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8e/a7/b276ff776533b423710a285c8168b52551cb2ab0855443131fdc7fd8c16f/easygui-0.98.3-py2.py3-none-any.whl", hash = "sha256:33498710c68b5376b459cd3fc48d1d1f33822139eb3ed01defbc0528326da3ba", size = 92655, upload-time = "2022-04-01T13:15:49.568Z" }, +] + +[[package]] +name = "ebcdic" +version = "1.1.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0d/2f/633031205333bee5f9f93761af8268746aa75f38754823aabb8570eb245b/ebcdic-1.1.1-py2.py3-none-any.whl", hash = "sha256:33b4cb729bc2d0bf46cc1847b0e5946897cb8d3f53520c5b9aa5fa98d7e735f1", size = 128537, upload-time = "2019-08-09T00:54:35.544Z" }, +] + +[[package]] +name = "editdistance" +version = "0.8.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d5/18/9f4f975ca87a390832b1c22478f3702fcdf739f83211e24d054b7551270d/editdistance-0.8.1.tar.gz", hash = "sha256:d1cdf80a5d5014b0c9126a69a42ce55a457b457f6986ff69ca98e4fe4d2d8fed", size = 50006, upload-time = "2024-02-10T07:44:53.914Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/09/c9/302658ce7f4c537a4e85cf578d11bbf7af120a712e1d78fedc6cb8823c65/editdistance-0.8.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:adeb705f32b93accc74960d227875abff150ee42d676e428536361fe5f8f5388", size = 106150, upload-time = "2024-02-10T07:43:15.903Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/45/80/0b3c7d2c0e183725986fea5dd2df11f0b4b46320e9a64f6077a121ab1f64/editdistance-0.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3de77951b105d0972deec7684a0b3d1a9dee69c9b5d34f6e2acc0d76cd4a1c52", size = 80551, upload-time = "2024-02-10T07:43:17.64Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b5/14/681460965c6a4a48321b07f88de2273d097fdca0491ff55db891aacbd291/editdistance-0.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5e88efb052d45e924606c305cb833a80579dca3e8e4ff01309d50ba2c1c0bbd5", size = 79142, upload-time = "2024-02-10T07:43:19.195Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ed/0d/abdbc8e394a9461cf2ae27c16564fadaa65f52bd242dd1582ae5e7736dc3/editdistance-0.8.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0247e7a1e9c66ea75211a97e725366bff19a52aac2c838ed5f90025630e976dd", size = 396768, upload-time = "2024-02-10T07:43:20.912Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c2/fb/2940d26ebda12efd280ae939436f17ac482930d862df9e774cb8b771ab03/editdistance-0.8.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67d143429a49ab552411505f550a0fb4285a1d4336e096804d233ec495ac20fc", size = 401846, upload-time = "2024-02-10T07:43:23.169Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/53/cc/c63d75c7f387d4df0645682c1ab8706c2dfe5c9c0c4999723ce9a3ba0853/editdistance-0.8.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca9d3be2b10e5d44a950a4bd1e84bca9ebbecd364bce0cf5693bf8224c78eaef", size = 397543, upload-time = "2024-02-10T07:43:24.621Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8e/38/bb0f734a7571e093184606b930734b12da5b6bff2635eba9312fe4536dd9/editdistance-0.8.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5c72aa1df8535f2e2b3d8773a1a7da091bc1a7e52bb396e7e48d375ba687e7b2", size = 898934, upload-time = "2024-02-10T07:43:26.926Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1c/9f/624fc7a09918f850a057465f02e86f269e139a457f48ff8cabfb12701756/editdistance-0.8.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9a606c34a2a6cc190e4fffc856b36333cdcf1f1fab5b22bd3088e585c22d6ca0", size = 959637, upload-time = "2024-02-10T07:43:28.997Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5e/5c/7fa6cc277f91c477ee370807d51c1826891dc6dfc307544223ce7f2687de/editdistance-0.8.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5af173d442ffac33b7c7990132f97f88818a3abf4b21c0c702a7022df37c0c5c", size = 911024, upload-time = "2024-02-10T07:43:30.449Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ad/97/556215f71184291155aee340a6d34f0676e7238fdfd10615b6b775ce25fe/editdistance-0.8.1-cp310-cp310-win32.whl", hash = "sha256:fd64b58f5a7b59afd9d75982aaeeacd2a98498bf472fa0360c122ffe6ea4c871", size = 80834, upload-time = "2024-02-10T07:43:31.634Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c8/d1/7ec5f5cbb95838d0eff7f980a660c81acd1363d658f2f5d4ceba38877c5a/editdistance-0.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:6c7c62c3cae45ca1fa01bb2722b297b9de1e3a244ac44cfba88bdcb488fe6aee", size = 79614, upload-time = "2024-02-10T07:43:33.255Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e2/dc/d0c29fd52d8f9e795653ed2b838a2a48c739cdfff04ac5b79c6c0ecbdf79/editdistance-0.8.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:486105603a273d73d12a54f347dffa70ab281749d7c3879658b377bc49e4b98c", size = 106079, upload-time = "2024-02-10T07:43:34.34Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b4/c6/75fa45d7b78fbea6fd894f4e48895a75bd3c83d4a9a6b57673881d74d3e0/editdistance-0.8.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fad081f5f86a175c1a09a4e9e45b95c9349e454c21e181e842e01c85f1f536fc", size = 80580, upload-time = "2024-02-10T07:43:35.947Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b7/a3/058d823b6285c3511dc94ed80620c3fb0c18b4aaa708f70ba71f3af28436/editdistance-0.8.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8cb78e125f6759398885a775f5eed07c2bb72b2f86da43e674c6b6a3335b273b", size = 79087, upload-time = "2024-02-10T07:43:36.923Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a0/3a/0b13c7864c93b1e9b9952bd2a33c5ef3c4fd1bf70a5fad6924789e70e5eb/editdistance-0.8.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3778ca60aa89def9144b70e330bcec5330c7da1d69cb28c612e90b84510a1d3d", size = 409296, upload-time = "2024-02-10T07:43:38.52Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/96/8a/db0fd79e8ddb9b5f86f274107c5d0a27ec4f2af88877df1f26c2c6d150cc/editdistance-0.8.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fba945eaa0436cf40bc53d7e299dc537c7c71353379a095b7459ff4af910da33", size = 412913, upload-time = "2024-02-10T07:43:39.852Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0d/d2/98be7112750ff17b436dd76f988f1e38570dcec0df8578ee19ef046f22fe/editdistance-0.8.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:877f2a0d801f32bc1a1878901ffb947b974361e849c66e314a7f1d786a446b58", size = 407430, upload-time = "2024-02-10T07:43:41.048Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/03/62/1815e3bf164910c47ba1948c8b5e937a40c7f9763b64e98fb6666b01dd06/editdistance-0.8.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e79d351ca40a6ead5f3763253fd7521572ee0d3e5d42538630e56d10f48db481", size = 909217, upload-time = "2024-02-10T07:43:42.916Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0c/d3/a832cea7b507a9be54e4ac3d1340fb66dca5f9c16c70bf38d5039e8fdede/editdistance-0.8.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:70ed382b3052a51161bad0149d4665003bf3b949fce0b01bf1253a4cc1a88239", size = 969407, upload-time = "2024-02-10T07:43:44.912Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a3/b4/db291d2a3845cbf8047b4b5aad3b3e038a8a2994d87027b40e1a1b0f4b74/editdistance-0.8.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a529bfb384c4000775d76739c4e64f73337f0f5a3784933b1321b577a62bed4e", size = 922112, upload-time = "2024-02-10T07:43:47.047Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c4/26/7ddeacada4982d0b892a28897e21871d0f25bca165e3663e37c3a272808a/editdistance-0.8.1-cp311-cp311-win32.whl", hash = "sha256:b082232429e731f181af7f7d2bcf79da6ca8fadd04e9086c11e2973f7d330c81", size = 80799, upload-time = "2024-02-10T07:43:48.231Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/52/a1/778af8590b8b12f03f62eacc3c8744407ade9e3d69be6dabe38d0afbf2dd/editdistance-0.8.1-cp311-cp311-win_amd64.whl", hash = "sha256:cef1a4359252a49f2c4718e64e9d40027d9d951b289d045bdb278656e59f6af8", size = 79698, upload-time = "2024-02-10T07:43:49.234Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cb/4c/7f195588949b4e72436dc7fc902632381f96e586af829685b56daebb38b8/editdistance-0.8.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b04af61b3fcdd287a07c15b6ae3b02af01c5e3e9c3aca76b8c1d13bd266b6f57", size = 106723, upload-time = "2024-02-10T07:43:50.268Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8d/82/31dc1640d830cd7d36865098329f34e4dad3b77f31cfb9404b347e700196/editdistance-0.8.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:18fc8b6eaae01bfd9cf999af726c1e8dcf667d120e81aa7dbd515bea7427f62f", size = 80998, upload-time = "2024-02-10T07:43:51.259Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ea/2a/6b823e71cef694d6f070a1d82be2842706fa193541aab8856a8f42044cd0/editdistance-0.8.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6a87839450a5987028738d061ffa5ef6a68bac2ddc68c9147a8aae9806629c7f", size = 79248, upload-time = "2024-02-10T07:43:52.873Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e1/31/bfb8e590f922089dc3471ed7828a6da2fc9453eba38c332efa9ee8749fd7/editdistance-0.8.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24b5f9c9673c823d91b5973d0af8b39f883f414a55ade2b9d097138acd10f31e", size = 415262, upload-time = "2024-02-10T07:43:54.498Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a9/c7/57423942b2f847cdbbb46494568d00cd8a45500904ea026f0aad6ca01bc7/editdistance-0.8.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c59248eabfad603f0fba47b0c263d5dc728fb01c2b6b50fb6ca187cec547fdb3", size = 418905, upload-time = "2024-02-10T07:43:55.779Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1b/05/dfa4cdcce063596cbf0d7a32c46cd0f4fa70980311b7da64d35f33ad02a0/editdistance-0.8.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84e239d88ff52821cf64023fabd06a1d9a07654f364b64bf1284577fd3a79d0e", size = 412511, upload-time = "2024-02-10T07:43:57.567Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0e/14/39608ff724a9523f187c4e28926d78bc68f2798f74777ac6757981108345/editdistance-0.8.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:2f7f71698f83e8c83839ac0d876a0f4ef996c86c5460aebd26d85568d4afd0db", size = 917293, upload-time = "2024-02-10T07:43:59.559Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/df/92/4a1c61d72da40dedfd0ff950fdc71ae83f478330c58a8bccfd776518bd67/editdistance-0.8.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:04e229d6f4ce0c12abc9f4cd4023a5b5fa9620226e0207b119c3c2778b036250", size = 975580, upload-time = "2024-02-10T07:44:01.328Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/47/3d/9877566e724c8a37f2228a84ec5cbf66dbfd0673515baf68a0fe07caff40/editdistance-0.8.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e16721636da6d6b68a2c09eaced35a94f4a4a704ec09f45756d4fd5e128ed18d", size = 929121, upload-time = "2024-02-10T07:44:02.764Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d2/f5/8c50757d198b8ca30ddb91e8b8f0247a8dca04ff2ec30755245f0ab1ff0c/editdistance-0.8.1-cp312-cp312-win32.whl", hash = "sha256:87533cf2ebc3777088d991947274cd7e1014b9c861a8aa65257bcdc0ee492526", size = 81039, upload-time = "2024-02-10T07:44:04.134Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/28/f0/65101e51dc7c850e7b7581a5d8fa8721a1d7479a0dca6c08386328e19882/editdistance-0.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:09f01ed51746d90178af7dd7ea4ebb41497ef19f53c7f327e864421743dffb0a", size = 79853, upload-time = "2024-02-10T07:44:05.687Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d4/4c/c9d02eeb47815d35f8d324b52f6704ea7beb032bcb209358cac44047d413/editdistance-0.8.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a4a90c6b03094c07358572027a8d0a13cca7450b1aa6caca98a5f1fa4f0b8961", size = 76455, upload-time = "2024-02-10T07:44:36.838Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/af/b0/2818fa6a24595dac069b0bfb9d05658406779a1ded8fd2b0c9066396cf99/editdistance-0.8.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:510a4f9ced348a4fd89ae2e102357d4d801a771e29bb2bc2f130a1692193407f", size = 84104, upload-time = "2024-02-10T07:44:37.928Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1f/d1/3d5e09bcf7fdb7aed705bf74047a8634bd2b8fd92177c25a2547e6dbadfb/editdistance-0.8.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4787fa7228ba6a34b430066d174320f011d605015baa7299c2c4911e6ea6bd46", size = 89058, upload-time = "2024-02-10T07:44:39.113Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cd/88/fca5d7b1a1edf66ce1e5b6b60bff75842e6814b4f5facbdf4585d88c912d/editdistance-0.8.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee02601375073afccd6b4d811129ce1cb696d47db734784d8dbd1fddcea75447", size = 84635, upload-time = "2024-02-10T07:44:40.714Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a9/91/0e6285bbe2358d81fd16313d30306b2d0036387348f7bc11d8c076ca3c72/editdistance-0.8.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:bc7ad9f9a20e6f351523de77c59249f005242e3f317b5de45d02c378d24f6531", size = 77389, upload-time = "2024-02-10T07:44:41.725Z" }, +] + +[[package]] +name = "elastic-transport" +version = "8.12.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "certifi" }, + { name = "urllib3" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f1/5e/9d697ca2511c2ecb3a239be91d5186a14fdbc97e15369c4ca6524c2929e8/elastic-transport-8.12.0.tar.gz", hash = "sha256:48839b942fcce199eece1558ecea6272e116c58da87ca8d495ef12eb61effaf7", size = 68977, upload-time = "2024-01-19T08:56:39.983Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d6/35/94475b9a18eec053ebce144ff1e28c175772ce82244ada6ffc10b1a65bcc/elastic_transport-8.12.0-py3-none-any.whl", hash = "sha256:87d9dc9dee64a05235e7624ed7e6ab6e5ca16619aa7a6d22e853273b9f1cfbee", size = 59880, upload-time = "2024-01-19T08:56:37.877Z" }, +] + +[[package]] +name = "elasticsearch" +version = "8.12.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "elastic-transport" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/dc/e3/9be84318c57c7c1f488586fcf1f37edb907dfad8c9450f66429e04d7568a/elasticsearch-8.12.1.tar.gz", hash = "sha256:00c997720fbd0f2afe5417c8193cf65d116817a0250de0521e30c3e81f00b8ac", size = 345835, upload-time = "2024-02-22T04:50:52.634Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d6/6f/79f61e0c869363eccc85322b3004bee26ebabf038e84ce2798c872c69fa8/elasticsearch-8.12.1-py3-none-any.whl", hash = "sha256:cc459b7e0fb88dc85b43b9d7d254cffad552b0063a3e0a12290c8fa5f138c038", size = 432136, upload-time = "2024-02-22T04:50:48.223Z" }, +] + +[[package]] +name = "elasticsearch-dsl" +version = "8.12.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "elasticsearch" }, + { name = "python-dateutil" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/96/85/152eb3ed7af5f4d4a6cca563125491b61109a265a6e7a950a239209f4564/elasticsearch-dsl-8.12.0.tar.gz", hash = "sha256:ce32b8529888a97be911531e7590816cf3b1f608263eff6fb75aa7106e233c88", size = 78878, upload-time = "2024-01-19T10:51:25.281Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6b/ee/4699000ef357e476a3984fd1eff236f820e3346c4aef7c7772e580b81b31/elasticsearch_dsl-8.12.0-py3-none-any.whl", hash = "sha256:2ea9e6ded64d21a8f1ef72477a4d116c6fbeea631ac32a2e2490b9c0d09a99a6", size = 63976, upload-time = "2024-01-19T10:51:21.894Z" }, +] + +[[package]] +name = "et-xmlfile" +version = "2.0.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d3/38/af70d7ab1ae9d4da450eeec1fa3918940a5fafb9055e934af8d6eb0c2313/et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54", size = 17234, upload-time = "2024-10-25T17:25:40.039Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c1/8b/5fe2cc11fee489817272089c4203e679c63b570a5aaeb18d852ae3cbba6a/et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa", size = 18059, upload-time = "2024-10-25T17:25:39.051Z" }, +] + +[[package]] +name = "events" +version = "0.5" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/25/ed/e47dec0626edd468c84c04d97769e7ab4ea6457b7f54dcb3f72b17fcd876/Events-0.5-py3-none-any.whl", hash = "sha256:a7286af378ba3e46640ac9825156c93bdba7502174dd696090fdfcd4d80a1abd" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" }, +] + +[[package]] +name = "extract-msg" +version = "0.41.5" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "chardet" }, + { name = "compressed-rtf" }, + { name = "ebcdic" }, + { name = "imapclient" }, + { name = "olefile" }, + { name = "red-black-tree-mod" }, + { name = "rtfde" }, + { name = "tzlocal" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ef/fa/67443d9b9f505c32cba96e34745223378b84cd4795c387310788cc8b6d7d/extract_msg-0.41.5.tar.gz", hash = "sha256:99d4fdc0c0912c836370bf9fbb6e77558bb978499c1b5fdd31634684e323885c", size = 181877, upload-time = "2023-06-11T17:19:42.931Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/be/e2/f0ed8df3907ad6e90e762d8e90adb4e25d12fea851a8371611fa14405782/extract_msg-0.41.5-py2.py3-none-any.whl", hash = "sha256:ad70dcdab3701b0fae554168c9642ad4ebef7f2ec283313c55e895a6518911e5", size = 185222, upload-time = "2023-06-11T17:19:40.781Z" }, +] + +[[package]] +name = "fake-http-header" +version = "0.3.5" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e3/0b/2849c87d9f13766e29c0a2f4d31681aa72e035016b251ab19d99bde7b592/fake_http_header-0.3.5-py3-none-any.whl", hash = "sha256:cd05f4bebf1b7e38b5f5c03d7fb820c0c17e87d9614fbee0afa39c32c7a2ad3c", size = 14938, upload-time = "2024-10-15T07:27:10.671Z" }, +] + +[[package]] +name = "fake-useragent" +version = "1.5.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/24/a1/1f662631ab153975fa8dbf09296324ecbaf53370dce922054e8de6b57370/fake-useragent-1.5.1.tar.gz", hash = "sha256:6387269f5a2196b5ba7ed8935852f75486845a1c95c50e72460e6a8e762f5c49", size = 22631, upload-time = "2024-03-16T14:28:32.271Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e4/99/60d8cf1b26938c2e0a57e232f7f15641dfcd6f8deda454d73e4145910ff6/fake_useragent-1.5.1-py3-none-any.whl", hash = "sha256:57415096557c8a4e23b62a375c21c55af5fd4ba30549227f562d2c4f5b60e3b3", size = 17190, upload-time = "2024-03-16T14:28:30.259Z" }, +] + +[[package]] +name = "fastavro" +version = "1.11.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/48/8f/32664a3245247b13702d13d2657ea534daf64e58a3f72a3a2d10598d6916/fastavro-1.11.1.tar.gz", hash = "sha256:bf6acde5ee633a29fb8dfd6dfea13b164722bc3adc05a0e055df080549c1c2f8", size = 1016250, upload-time = "2025-05-18T04:54:31.413Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ae/be/53df3fec7fdabc1848896a76afb0f01ab96b58abb29611aa68a994290167/fastavro-1.11.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:603aa1c1d1be21fb4bcb63e1efb0711a9ddb337de81391c32dac95c6e0dacfcc", size = 944225, upload-time = "2025-05-18T04:54:34.586Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d0/cc/c7c76a082fbf5aaaf82ab7da7b9ede6fc99eb8f008c084c67d230b29c446/fastavro-1.11.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45653b312d4ce297e2bd802ea3ffd17ecbe718e5e8b6e2ae04cd72cb50bb99d5", size = 3105189, upload-time = "2025-05-18T04:54:36.855Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/48/ff/5f1f0b5e3835e788ba8121d6dd6426cd4c6e58ce1bff02cb7810278648b0/fastavro-1.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:998a53fc552e6bee9acda32af258f02557313c85fb5b48becba5b71ec82f421e", size = 3113124, upload-time = "2025-05-18T04:54:40.013Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e5/b8/1ac01433b55460dabeb6d3fbb05ba1c971d57137041e8f53b2e9f46cd033/fastavro-1.11.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9f878c9ad819467120cb066f1c73496c42eb24ecdd7c992ec996f465ef4cedad", size = 3155196, upload-time = "2025-05-18T04:54:42.307Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5e/a8/66e599b946ead031a5caba12772e614a7802d95476e8732e2e9481369973/fastavro-1.11.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da9e4c231ac4951092c2230ca423d8a3f2966718f072ac1e2c5d2d44c70b2a50", size = 3229028, upload-time = "2025-05-18T04:54:44.503Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0e/e7/17c35e2dfe8a9e4f3735eabdeec366b0edc4041bb1a84fcd528c8efd12af/fastavro-1.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:7423bfad3199567eeee7ad6816402c7c0ee1658b959e8c10540cfbc60ce96c2a", size = 449177, upload-time = "2025-05-18T04:54:46.127Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8e/63/f33d6fd50d8711f305f07ad8c7b4a25f2092288f376f484c979dcf277b07/fastavro-1.11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3573340e4564e8962e22f814ac937ffe0d4be5eabbd2250f77738dc47e3c8fe9", size = 957526, upload-time = "2025-05-18T04:54:47.701Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f4/09/a57ad9d8cb9b8affb2e43c29d8fb8cbdc0f1156f8496067a0712c944bacc/fastavro-1.11.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7291cf47735b8bd6ff5d9b33120e6e0974f52fd5dff90cd24151b22018e7fd29", size = 3322808, upload-time = "2025-05-18T04:54:50.419Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/86/70/d6df59309d3754d6d4b0c7beca45b9b1a957d6725aed8da3aca247db3475/fastavro-1.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf3bb065d657d5bac8b2cb39945194aa086a9b3354f2da7f89c30e4dc20e08e2", size = 3330870, upload-time = "2025-05-18T04:54:52.406Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ad/ea/122315154d2a799a2787058435ef0d4d289c0e8e575245419436e9b702ca/fastavro-1.11.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8758317c85296b848698132efb13bc44a4fbd6017431cc0f26eaeb0d6fa13d35", size = 3343369, upload-time = "2025-05-18T04:54:54.652Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/62/12/7800de5fec36d55a818adf3db3b085b1a033c4edd60323cf6ca0754cf8cb/fastavro-1.11.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ad99d57228f83bf3e2214d183fbf6e2fda97fd649b2bdaf8e9110c36cbb02624", size = 3430629, upload-time = "2025-05-18T04:54:56.513Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/48/65/2b74ccfeba9dcc3f7dbe64907307386b4a0af3f71d2846f63254df0f1e1d/fastavro-1.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:9134090178bdbf9eefd467717ced3dc151e27a7e7bfc728260ce512697efe5a4", size = 451621, upload-time = "2025-05-18T04:54:58.156Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/99/58/8e789b0a2f532b22e2d090c20d27c88f26a5faadcba4c445c6958ae566cf/fastavro-1.11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e8bc238f2637cd5d15238adbe8fb8c58d2e6f1870e0fb28d89508584670bae4b", size = 939583, upload-time = "2025-05-18T04:54:59.853Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/34/3f/02ed44742b1224fe23c9fc9b9b037fc61769df716c083cf80b59a02b9785/fastavro-1.11.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b403933081c83fc4d8a012ee64b86e560a024b1280e3711ee74f2abc904886e8", size = 3257734, upload-time = "2025-05-18T04:55:02.366Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cc/bc/9cc8b19eeee9039dd49719f8b4020771e805def262435f823fa8f27ddeea/fastavro-1.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f6ecb4b5f77aa756d973b7dd1c2fb4e4c95b4832a3c98b059aa96c61870c709", size = 3318218, upload-time = "2025-05-18T04:55:04.352Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/39/77/3b73a986606494596b6d3032eadf813a05b59d1623f54384a23de4217d5f/fastavro-1.11.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:059893df63ef823b0231b485c9d43016c7e32850cae7bf69f4e9d46dd41c28f2", size = 3297296, upload-time = "2025-05-18T04:55:06.175Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8e/1c/b69ceef6494bd0df14752b5d8648b159ad52566127bfd575e9f5ecc0c092/fastavro-1.11.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5120ffc9a200699218e01777e695a2f08afb3547ba818184198c757dc39417bd", size = 3438056, upload-time = "2025-05-18T04:55:08.276Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ef/11/5c2d0db3bd0e6407546fabae9e267bb0824eacfeba79e7dd81ad88afa27d/fastavro-1.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:7bb9d0d2233f33a52908b6ea9b376fe0baf1144bdfdfb3c6ad326e200a8b56b0", size = 442824, upload-time = "2025-05-18T04:55:10.385Z" }, +] + +[[package]] +name = "fastembed" +version = "0.3.6" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "huggingface-hub" }, + { name = "loguru" }, + { name = "mmh3" }, + { name = "numpy" }, + { name = "onnx" }, + { name = "onnxruntime" }, + { name = "pillow" }, + { name = "pystemmer" }, + { name = "requests" }, + { name = "snowballstemmer" }, + { name = "tokenizers" }, + { name = "tqdm" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ae/20/68a109c8def842ed47a2951873fb2d7d23ee296ef8c195aedbb735670fff/fastembed-0.3.6.tar.gz", hash = "sha256:c93c8ec99b8c008c2d192d6297866b8d70ec7ac8f5696b34eb5ea91f85efd15f", size = 35058, upload-time = "2024-08-23T19:34:06.761Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ad/21/f0efccf36cb16a18e32bd151d4469fbf86113b2ec63f45bcc0868401af68/fastembed-0.3.6-py3-none-any.whl", hash = "sha256:2bf70edae28bb4ccd9e01617098c2075b0ba35b88025a3d22b0e1e85b2c488ce", size = 55647, upload-time = "2024-08-23T19:34:05.72Z" }, +] + +[[package]] +name = "fastembed-gpu" +version = "0.3.6" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "huggingface-hub", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "loguru", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "mmh3", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "numpy", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "onnxruntime-gpu", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "pillow", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "pystemmer", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "requests", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "snowballstemmer", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "tokenizers", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "tqdm", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/da/07/7336c7f3d7ee47f33b407eeb50f5eeb152889de538a52a8f1cc637192816/fastembed_gpu-0.3.6.tar.gz", hash = "sha256:ee2de8918b142adbbf48caaffec0c492f864d73c073eea5a3dcd0e8c1041c50d", size = 35051, upload-time = "2024-08-23T19:34:11.866Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2b/8d/38a2e63594e91bffe076e861955c8674fa0e7a7ebbda709a9bb1d85f4bd7/fastembed_gpu-0.3.6-py3-none-any.whl", hash = "sha256:4a8ef0ef5e344dc2ede9c4f2ffb4573c9e65c51391eef31d8d3f67b45e82c1c4", size = 55680, upload-time = "2024-08-23T19:34:10.203Z" }, +] + +[[package]] +name = "fastparquet" +version = "2024.11.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "cramjam" }, + { name = "fsspec" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pandas" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b4/66/862da14f5fde4eff2cedc0f51a8dc34ba145088e5041b45b2d57ac54f922/fastparquet-2024.11.0.tar.gz", hash = "sha256:e3b1fc73fd3e1b70b0de254bae7feb890436cb67e99458b88cb9bd3cc44db419", size = 467192, upload-time = "2024-11-15T19:30:10.413Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3d/56/476f5b83476a256489879b78513bee737691a80905e246a2daa30ebcc362/fastparquet-2024.11.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:60ccf587410f0979105e17036df61bb60e1c2b81880dc91895cdb4ee65b71e7f", size = 910272, upload-time = "2024-11-12T20:37:19.594Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3b/ad/4ce73440df874479f7205fe5445090f71ed4e9bd77fdb3b740253ce82703/fastparquet-2024.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a5ad5fc14b0567e700bea3cd528a0bd45a6f9371370b49de8889fb3d10a6574a", size = 684095, upload-time = "2024-11-12T20:37:22.957Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/20/37/c3164261d6183d529a59afef2749821b262c8581d837faa91043837c6f76/fastparquet-2024.11.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b74333914f454344458dab9d1432fda9b70d62e28dc7acb1512d937ef1424ee", size = 1700355, upload-time = "2024-11-12T20:37:25.792Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e6/95/cf4b175c22160ec21e4664830763bfaa80b2cf05133ef854c3f436d01c16/fastparquet-2024.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41d1610130b5cb1ce36467766191c5418cba8631e2bfe3affffaf13f9be4e7a8", size = 1714663, upload-time = "2024-11-12T20:37:28.369Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2c/31/b6c8cdb6d5df964a192e4e8c8ecd979718afb9ca7e2dc9243a4368b370e9/fastparquet-2024.11.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d281edd625c33628ba028d3221180283d6161bc5ceb55eae1f0ca1678f864f26", size = 1666729, upload-time = "2024-11-12T20:37:30.243Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/31/e5/8a0575c46a7973849f8f2a88af16618b9c7efe98f249f03e3e3de69c2b86/fastparquet-2024.11.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fa56b19a29008c34cfe8831e810f770080debcbffc69aabd1df4d47572181f9c", size = 1741669, upload-time = "2024-11-12T20:37:32.067Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bb/6a/669f8c9cf2fc6e30c9353832f870e5a2e170b458d12c5080837f742d963d/fastparquet-2024.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5914ecfa766b7763201b9f49d832a5e89c2dccad470ca4f9c9b228d9a8349756", size = 1782359, upload-time = "2024-11-12T20:37:33.806Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/70/c0/1374cb43924739f4542e39d972481c1f4c7dd96808a1947450808e4e7df7/fastparquet-2024.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:561202e8f0e859ccc1aa77c4aaad1d7901b2d50fd6f624ca018bae4c3c7a62ce", size = 670700, upload-time = "2024-11-12T20:37:35.312Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7c/51/e0d6e702523ac923ede6c05e240f4a02533ccf2cea9fec7a43491078e920/fastparquet-2024.11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:374cdfa745aa7d5188430528d5841cf823eb9ad16df72ad6dadd898ccccce3be", size = 909934, upload-time = "2024-11-12T20:37:37.049Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0a/c8/5c0fb644c19a8d80b2ae4d8aa7d90c2d85d0bd4a948c5c700bea5c2802ea/fastparquet-2024.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c8401bfd86cccaf0ab7c0ade58c91ae19317ff6092e1d4ad96c2178197d8124", size = 683844, upload-time = "2024-11-12T20:37:38.456Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/33/4a/1e532fd1a0d4d8af7ffc7e3a8106c0bcd13ed914a93a61e299b3832dd3d2/fastparquet-2024.11.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9cca4c6b5969df5561c13786f9d116300db1ec22c7941e237cfca4ce602f59b", size = 1791698, upload-time = "2024-11-12T20:37:41.101Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8d/e8/e1ede861bea68394a755d8be1aa2e2d60a3b9f6b551bfd56aeca74987e2e/fastparquet-2024.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a9387e77ac608d8978774caaf1e19de67eaa1386806e514dcb19f741b19cfe5", size = 1804289, upload-time = "2024-11-12T20:37:43.08Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4f/1e/957090cccaede805583ca3f3e46e2762d0f9bf8860ecbce65197e47d84c1/fastparquet-2024.11.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6595d3771b3d587a31137e985f751b4d599d5c8e9af9c4858e373fdf5c3f8720", size = 1753638, upload-time = "2024-11-12T20:37:45.498Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/85/72/344787c685fd1531f07ae712a855a7c34d13deaa26c3fd4a9231bea7dbab/fastparquet-2024.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:053695c2f730b78a2d3925df7cd5c6444d6c1560076af907993361cc7accf3e2", size = 1814407, upload-time = "2024-11-12T20:37:47.25Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6c/ec/ab9d5685f776a1965797eb68c4364c72edf57cd35beed2df49b34425d1df/fastparquet-2024.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0a52eecc6270ae15f0d51347c3f762703dd667ca486f127dc0a21e7e59856ae5", size = 1874462, upload-time = "2024-11-12T20:37:49.755Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/90/4f/7a4ea9a7ddf0a3409873f0787f355806f9e0b73f42f2acecacdd9a8eff0a/fastparquet-2024.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:e29ff7a367fafa57c6896fb6abc84126e2466811aefd3e4ad4070b9e18820e54", size = 671023, upload-time = "2024-11-12T20:37:51.461Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/08/76/068ac7ec9b4fc783be21a75a6a90b8c0654da4d46934d969e524ce287787/fastparquet-2024.11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:dbad4b014782bd38b58b8e9f514fe958cfa7a6c4e187859232d29fd5c5ddd849", size = 915968, upload-time = "2024-11-12T20:37:52.861Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c7/9e/6d3b4188ad64ed51173263c07109a5f18f9c84a44fa39ab524fca7420cda/fastparquet-2024.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:403d31109d398b6be7ce84fa3483fc277c6a23f0b321348c0a505eb098a041cb", size = 685399, upload-time = "2024-11-12T20:37:54.899Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8f/6c/809220bc9fbe83d107df2d664c3fb62fb81867be8f5218ac66c2e6b6a358/fastparquet-2024.11.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cbbb9057a26acf0abad7adf58781ee357258b7708ee44a289e3bee97e2f55d42", size = 1758557, upload-time = "2024-11-12T20:37:56.553Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e0/2c/b3b3e6ca2e531484289024138cd4709c22512b3fe68066d7f9849da4a76c/fastparquet-2024.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63e0e416e25c15daa174aad8ba991c2e9e5b0dc347e5aed5562124261400f87b", size = 1781052, upload-time = "2024-11-12T20:37:58.339Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/21/fe/97ed45092d0311c013996dae633122b7a51c5d9fe8dcbc2c840dc491201e/fastparquet-2024.11.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e2d7f02f57231e6c86d26e9ea71953737202f20e948790e5d4db6d6a1a150dc", size = 1715797, upload-time = "2024-11-12T20:38:00.694Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/24/df/02fa6aee6c0d53d1563b5bc22097076c609c4c5baa47056b0b4bed456fcf/fastparquet-2024.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fbe4468146b633d8f09d7b196fea0547f213cb5ce5f76e9d1beb29eaa9593a93", size = 1795682, upload-time = "2024-11-12T20:38:02.38Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b0/25/f4f87557589e1923ee0e3bebbc84f08b7c56962bf90f51b116ddc54f2c9f/fastparquet-2024.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:29d5c718817bcd765fc519b17f759cad4945974421ecc1931d3bdc3e05e57fa9", size = 1857842, upload-time = "2024-11-12T20:38:04.196Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b1/f9/98cd0c39115879be1044d59c9b76e8292776e99bb93565bf990078fd11c4/fastparquet-2024.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:74a0b3c40ab373442c0fda96b75a36e88745d8b138fcc3a6143e04682cbbb8ca", size = 673269, upload-time = "2024-12-11T21:22:48.073Z" }, +] + +[[package]] +name = "feedparser" +version = "6.0.11" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "sgmllib3k" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ff/aa/7af346ebeb42a76bf108027fe7f3328bb4e57a3a96e53e21fd9ef9dd6dd0/feedparser-6.0.11.tar.gz", hash = "sha256:c9d0407b64c6f2a065d0ebb292c2b35c01050cc0dc33757461aaabdc4c4184d5", size = 286197, upload-time = "2023-12-10T16:03:20.854Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7c/d4/8c31aad9cc18f451c49f7f9cfb5799dadffc88177f7917bc90a66459b1d7/feedparser-6.0.11-py3-none-any.whl", hash = "sha256:0be7ee7b395572b19ebeb1d6aafb0028dee11169f1c934e0ed67d54992f4ad45", size = 81343, upload-time = "2023-12-10T16:03:19.484Z" }, +] + +[[package]] +name = "filelock" +version = "3.15.4" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/08/dd/49e06f09b6645156550fb9aee9cc1e59aba7efbc972d665a1bd6ae0435d4/filelock-3.15.4.tar.gz", hash = "sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb", size = 18007, upload-time = "2024-06-22T15:59:14.749Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ae/f0/48285f0262fe47103a4a45972ed2f9b93e4c80b8fd609fa98da78b2a5706/filelock-3.15.4-py3-none-any.whl", hash = "sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7", size = 16159, upload-time = "2024-06-22T15:59:12.695Z" }, +] + +[[package]] +name = "flagembedding" +version = "1.2.10" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "accelerate" }, + { name = "datasets" }, + { name = "sentence-transformers" }, + { name = "torch" }, + { name = "transformers" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d5/4c/6bfbbf677aea4a0eb33da8516c8098dd1e1f57dd9bfae8df396c3b9d68ff/FlagEmbedding-1.2.10.tar.gz", hash = "sha256:0d0a553e5cca935bb80576f031023e8cb1775e6f9b29b0ab6bbe210697755d30", size = 141345, upload-time = "2024-05-23T06:01:32.649Z" } + +[[package]] +name = "flasgger" +version = "0.9.7.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "flask" }, + { name = "jsonschema" }, + { name = "mistune" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "six" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8a/e4/05e80adeadc39f171b51bd29b24a6d9838127f3aaa1b07c1501e662a8cee/flasgger-0.9.7.1.tar.gz", hash = "sha256:ca098e10bfbb12f047acc6299cc70a33851943a746e550d86e65e60d4df245fb" } + +[[package]] +name = "flask" +version = "3.0.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "blinker" }, + { name = "click" }, + { name = "itsdangerous" }, + { name = "jinja2" }, + { name = "werkzeug" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/41/e1/d104c83026f8d35dfd2c261df7d64738341067526406b40190bc063e829a/flask-3.0.3.tar.gz", hash = "sha256:ceb27b0af3823ea2737928a4d99d125a06175b8512c445cbd9a9ce200ef76842", size = 676315, upload-time = "2024-04-07T19:26:11.035Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/61/80/ffe1da13ad9300f87c93af113edd0638c75138c42a0994becfacac078c06/flask-3.0.3-py3-none-any.whl", hash = "sha256:34e815dfaa43340d1d15a5c3a02b8476004037eb4840b34910c6e21679d288f3", size = 101735, upload-time = "2024-04-07T19:26:08.569Z" }, +] + +[[package]] +name = "flask-cors" +version = "5.0.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "flask" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4f/d0/d9e52b154e603b0faccc0b7c2ad36a764d8755ef4036acbf1582a67fb86b/flask_cors-5.0.0.tar.gz", hash = "sha256:5aadb4b950c4e93745034594d9f3ea6591f734bb3662e16e255ffbf5e89c88ef", size = 30954, upload-time = "2024-08-31T00:44:26.395Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/56/07/1afa0514c876282bebc1c9aee83c6bb98fe6415cf57b88d9b06e7e29bf9c/Flask_Cors-5.0.0-py2.py3-none-any.whl", hash = "sha256:b9e307d082a9261c100d8fb0ba909eec6a228ed1b60a8315fd85f783d61910bc", size = 14463, upload-time = "2024-08-31T00:44:24.394Z" }, +] + +[[package]] +name = "flask-login" +version = "0.6.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "flask" }, + { name = "werkzeug" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c3/6e/2f4e13e373bb49e68c02c51ceadd22d172715a06716f9299d9df01b6ddb2/Flask-Login-0.6.3.tar.gz", hash = "sha256:5e23d14a607ef12806c699590b89d0f0e0d67baeec599d75947bf9c147330333" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/59/f5/67e9cc5c2036f58115f9fe0f00d203cf6780c3ff8ae0e705e7a9d9e8ff9e/Flask_Login-0.6.3-py3-none-any.whl", hash = "sha256:849b25b82a436bf830a054e74214074af59097171562ab10bfa999e6b78aae5d" }, +] + +[[package]] +name = "flask-mail" +version = "0.10.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "blinker" }, + { name = "flask" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ba/29/e92dc84c675d1e8d260d5768eb3fb65c70cbd33addecf424187587bee862/flask_mail-0.10.0.tar.gz", hash = "sha256:44083e7b02bbcce792209c06252f8569dd5a325a7aaa76afe7330422bd97881d", size = 8152, upload-time = "2024-05-23T22:30:12.612Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e4/c0/a81083da779f482494d49195d8b6c9fde21072558253e4a9fb2ec969c3c1/flask_mail-0.10.0-py3-none-any.whl", hash = "sha256:a451e490931bb3441d9b11ebab6812a16bfa81855792ae1bf9c1e1e22c4e51e7", size = 8529, upload-time = "2024-05-23T22:30:10.962Z" }, +] + +[[package]] +name = "flask-session" +version = "0.8.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "cachelib" }, + { name = "flask" }, + { name = "msgspec" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/86/d7/0ba4180513abe28eadc208123c76f9f09e290d5939fb2eb68323b9733354/flask_session-0.8.0.tar.gz", hash = "sha256:20e045eb01103694e70be4a49f3a80dbb1b57296a22dc6f44bbf3f83ef0742ff", size = 940269, upload-time = "2024-03-26T07:56:13.747Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/67/1b/f085ceebb825d1cfaf078852b67cd248a33af2905f40ba9860cc006d966b/flask_session-0.8.0-py3-none-any.whl", hash = "sha256:5dae6e9ddab334f8dc4dea4305af37851f4e7dc0f484caf3351184001195e3b7", size = 24410, upload-time = "2024-03-26T07:56:11.377Z" }, +] + +[[package]] +name = "flatbuffers" +version = "25.2.10" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e4/30/eb5dce7994fc71a2f685d98ec33cc660c0a5887db5610137e60d8cbc4489/flatbuffers-25.2.10.tar.gz", hash = "sha256:97e451377a41262f8d9bd4295cc836133415cc03d8cb966410a4af92eb00d26e", size = 22170, upload-time = "2025-02-11T04:26:46.257Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b8/25/155f9f080d5e4bc0082edfda032ea2bc2b8fab3f4d25d46c1e9dd22a1a89/flatbuffers-25.2.10-py2.py3-none-any.whl", hash = "sha256:ebba5f4d5ea615af3f7fd70fc310636fbb2bbd1f566ac0a23d98dd412de50051", size = 30953, upload-time = "2025-02-11T04:26:44.484Z" }, +] + +[[package]] +name = "fonttools" +version = "4.59.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8a/27/ec3c723bfdf86f34c5c82bf6305df3e0f0d8ea798d2d3a7cb0c0a866d286/fonttools-4.59.0.tar.gz", hash = "sha256:be392ec3529e2f57faa28709d60723a763904f71a2b63aabe14fee6648fe3b14", size = 3532521, upload-time = "2025-07-16T12:04:54.613Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1c/1f/3dcae710b7c4b56e79442b03db64f6c9f10c3348f7af40339dffcefb581e/fonttools-4.59.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:524133c1be38445c5c0575eacea42dbd44374b310b1ffc4b60ff01d881fabb96", size = 2761846, upload-time = "2025-07-16T12:03:33.267Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/eb/0e/ae3a1884fa1549acac1191cc9ec039142f6ac0e9cbc139c2e6a3dab967da/fonttools-4.59.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:21e606b2d38fed938dde871c5736822dd6bda7a4631b92e509a1f5cd1b90c5df", size = 2332060, upload-time = "2025-07-16T12:03:36.472Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/75/46/58bff92a7216829159ac7bdb1d05a48ad1b8ab8c539555f12d29fdecfdd4/fonttools-4.59.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e93df708c69a193fc7987192f94df250f83f3851fda49413f02ba5dded639482", size = 4852354, upload-time = "2025-07-16T12:03:39.102Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/05/57/767e31e48861045d89691128bd81fd4c62b62150f9a17a666f731ce4f197/fonttools-4.59.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:62224a9bb85b4b66d1b46d45cbe43d71cbf8f527d332b177e3b96191ffbc1e64", size = 4781132, upload-time = "2025-07-16T12:03:41.415Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d7/78/adb5e9b0af5c6ce469e8b0e112f144eaa84b30dd72a486e9c778a9b03b31/fonttools-4.59.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b8974b2a266b54c96709bd5e239979cddfd2dbceed331aa567ea1d7c4a2202db", size = 4832901, upload-time = "2025-07-16T12:03:43.115Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ac/92/bc3881097fbf3d56d112bec308c863c058e5d4c9c65f534e8ae58450ab8a/fonttools-4.59.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:209b75943d158f610b78320eacb5539aa9e920bee2c775445b2846c65d20e19d", size = 4940140, upload-time = "2025-07-16T12:03:44.781Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4a/54/39cdb23f0eeda2e07ae9cb189f2b6f41da89aabc682d3a387b3ff4a4ed29/fonttools-4.59.0-cp310-cp310-win32.whl", hash = "sha256:4c908a7036f0f3677f8afa577bcd973e3e20ddd2f7c42a33208d18bee95cdb6f", size = 2215890, upload-time = "2025-07-16T12:03:46.961Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d8/eb/f8388d9e19f95d8df2449febe9b1a38ddd758cfdb7d6de3a05198d785d61/fonttools-4.59.0-cp310-cp310-win_amd64.whl", hash = "sha256:8b4309a2775e4feee7356e63b163969a215d663399cce1b3d3b65e7ec2d9680e", size = 2260191, upload-time = "2025-07-16T12:03:48.908Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/06/96/520733d9602fa1bf6592e5354c6721ac6fc9ea72bc98d112d0c38b967199/fonttools-4.59.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:841b2186adce48903c0fef235421ae21549020eca942c1da773ac380b056ab3c", size = 2782387, upload-time = "2025-07-16T12:03:51.424Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/87/6a/170fce30b9bce69077d8eec9bea2cfd9f7995e8911c71be905e2eba6368b/fonttools-4.59.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9bcc1e77fbd1609198966ded6b2a9897bd6c6bcbd2287a2fc7d75f1a254179c5", size = 2342194, upload-time = "2025-07-16T12:03:53.295Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b0/b6/7c8166c0066856f1408092f7968ac744060cf72ca53aec9036106f57eeca/fonttools-4.59.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:37c377f7cb2ab2eca8a0b319c68146d34a339792f9420fca6cd49cf28d370705", size = 5032333, upload-time = "2025-07-16T12:03:55.177Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/eb/0c/707c5a19598eafcafd489b73c4cb1c142102d6197e872f531512d084aa76/fonttools-4.59.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fa39475eaccb98f9199eccfda4298abaf35ae0caec676ffc25b3a5e224044464", size = 4974422, upload-time = "2025-07-16T12:03:57.406Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f6/e7/6d33737d9fe632a0f59289b6f9743a86d2a9d0673de2a0c38c0f54729822/fonttools-4.59.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d3972b13148c1d1fbc092b27678a33b3080d1ac0ca305742b0119b75f9e87e38", size = 5010631, upload-time = "2025-07-16T12:03:59.449Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/63/e1/a4c3d089ab034a578820c8f2dff21ef60daf9668034a1e4fb38bb1cc3398/fonttools-4.59.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a408c3c51358c89b29cfa5317cf11518b7ce5de1717abb55c5ae2d2921027de6", size = 5122198, upload-time = "2025-07-16T12:04:01.542Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/09/77/ca82b9c12fa4de3c520b7760ee61787640cf3fde55ef1b0bfe1de38c8153/fonttools-4.59.0-cp311-cp311-win32.whl", hash = "sha256:6770d7da00f358183d8fd5c4615436189e4f683bdb6affb02cad3d221d7bb757", size = 2214216, upload-time = "2025-07-16T12:04:03.515Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ab/25/5aa7ca24b560b2f00f260acf32c4cf29d7aaf8656e159a336111c18bc345/fonttools-4.59.0-cp311-cp311-win_amd64.whl", hash = "sha256:84fc186980231a287b28560d3123bd255d3c6b6659828c642b4cf961e2b923d0", size = 2261879, upload-time = "2025-07-16T12:04:05.015Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e2/77/b1c8af22f4265e951cd2e5535dbef8859efcef4fb8dee742d368c967cddb/fonttools-4.59.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f9b3a78f69dcbd803cf2fb3f972779875b244c1115481dfbdd567b2c22b31f6b", size = 2767562, upload-time = "2025-07-16T12:04:06.895Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ff/5a/aeb975699588176bb357e8b398dfd27e5d3a2230d92b81ab8cbb6187358d/fonttools-4.59.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:57bb7e26928573ee7c6504f54c05860d867fd35e675769f3ce01b52af38d48e2", size = 2335168, upload-time = "2025-07-16T12:04:08.695Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/54/97/c6101a7e60ae138c4ef75b22434373a0da50a707dad523dd19a4889315bf/fonttools-4.59.0-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4536f2695fe5c1ffb528d84a35a7d3967e5558d2af58b4775e7ab1449d65767b", size = 4909850, upload-time = "2025-07-16T12:04:10.761Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bd/6c/fa4d18d641054f7bff878cbea14aa9433f292b9057cb1700d8e91a4d5f4f/fonttools-4.59.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:885bde7d26e5b40e15c47bd5def48b38cbd50830a65f98122a8fb90962af7cd1", size = 4955131, upload-time = "2025-07-16T12:04:12.846Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/20/5c/331947fc1377deb928a69bde49f9003364f5115e5cbe351eea99e39412a2/fonttools-4.59.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6801aeddb6acb2c42eafa45bc1cb98ba236871ae6f33f31e984670b749a8e58e", size = 4899667, upload-time = "2025-07-16T12:04:14.558Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8a/46/b66469dfa26b8ff0baa7654b2cc7851206c6d57fe3abdabbaab22079a119/fonttools-4.59.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:31003b6a10f70742a63126b80863ab48175fb8272a18ca0846c0482968f0588e", size = 5051349, upload-time = "2025-07-16T12:04:16.388Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2e/05/ebfb6b1f3a4328ab69787d106a7d92ccde77ce66e98659df0f9e3f28d93d/fonttools-4.59.0-cp312-cp312-win32.whl", hash = "sha256:fbce6dae41b692a5973d0f2158f782b9ad05babc2c2019a970a1094a23909b1b", size = 2201315, upload-time = "2025-07-16T12:04:18.557Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/09/45/d2bdc9ea20bbadec1016fd0db45696d573d7a26d95ab5174ffcb6d74340b/fonttools-4.59.0-cp312-cp312-win_amd64.whl", hash = "sha256:332bfe685d1ac58ca8d62b8d6c71c2e52a6c64bc218dc8f7825c9ea51385aa01", size = 2249408, upload-time = "2025-07-16T12:04:20.489Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d0/9c/df0ef2c51845a13043e5088f7bb988ca6cd5bb82d5d4203d6a158aa58cf2/fonttools-4.59.0-py3-none-any.whl", hash = "sha256:241313683afd3baacb32a6bd124d0bce7404bc5280e12e291bae1b9bba28711d", size = 1128050, upload-time = "2025-07-16T12:04:52.687Z" }, +] + +[[package]] +name = "free-proxy" +version = "1.1.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "lxml" }, + { name = "requests" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d5/10/3654b44093aa3e587948c770279baca3a8dfe4d14a616142e8c6bf04b09b/free_proxy-1.1.3.tar.gz", hash = "sha256:6d82aa112e3df7725bdbf177e2110bccdf5f3bbd6e1c70b8616ec12ae3bbf98c", size = 5607, upload-time = "2024-11-07T08:42:48.684Z" } + +[[package]] +name = "frozendict" +version = "2.4.6" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bb/59/19eb300ba28e7547538bdf603f1c6c34793240a90e1a7b61b65d8517e35e/frozendict-2.4.6.tar.gz", hash = "sha256:df7cd16470fbd26fc4969a208efadc46319334eb97def1ddf48919b351192b8e", size = 316416, upload-time = "2024-10-13T12:15:32.449Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a6/7f/e80cdbe0db930b2ba9d46ca35a41b0150156da16dfb79edcc05642690c3b/frozendict-2.4.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c3a05c0a50cab96b4bb0ea25aa752efbfceed5ccb24c007612bc63e51299336f", size = 37927, upload-time = "2024-10-13T12:14:17.927Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/29/98/27e145ff7e8e63caa95fb8ee4fc56c68acb208bef01a89c3678a66f9a34d/frozendict-2.4.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f5b94d5b07c00986f9e37a38dd83c13f5fe3bf3f1ccc8e88edea8fe15d6cd88c", size = 37945, upload-time = "2024-10-13T12:14:19.976Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ac/f1/a10be024a9d53441c997b3661ea80ecba6e3130adc53812a4b95b607cdd1/frozendict-2.4.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4c789fd70879ccb6289a603cdebdc4953e7e5dea047d30c1b180529b28257b5", size = 117656, upload-time = "2024-10-13T12:14:22.038Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/46/a6/34c760975e6f1cb4db59a990d58dcf22287e10241c851804670c74c6a27a/frozendict-2.4.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da6a10164c8a50b34b9ab508a9420df38f4edf286b9ca7b7df8a91767baecb34", size = 117444, upload-time = "2024-10-13T12:14:24.251Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/62/dd/64bddd1ffa9617f50e7e63656b2a7ad7f0a46c86b5f4a3d2c714d0006277/frozendict-2.4.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9a8a43036754a941601635ea9c788ebd7a7efbed2becba01b54a887b41b175b9", size = 116801, upload-time = "2024-10-13T12:14:26.518Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/45/ae/af06a8bde1947277aad895c2f26c3b8b8b6ee9c0c2ad988fb58a9d1dde3f/frozendict-2.4.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c9905dcf7aa659e6a11b8051114c9fa76dfde3a6e50e6dc129d5aece75b449a2", size = 117329, upload-time = "2024-10-13T12:14:28.485Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d2/df/be3fa0457ff661301228f4c59c630699568c8ed9b5480f113b3eea7d0cb3/frozendict-2.4.6-cp310-cp310-win_amd64.whl", hash = "sha256:323f1b674a2cc18f86ab81698e22aba8145d7a755e0ac2cccf142ee2db58620d", size = 37522, upload-time = "2024-10-13T12:14:30.418Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4a/6f/c22e0266b4c85f58b4613fec024e040e93753880527bf92b0c1bc228c27c/frozendict-2.4.6-cp310-cp310-win_arm64.whl", hash = "sha256:eabd21d8e5db0c58b60d26b4bb9839cac13132e88277e1376970172a85ee04b3", size = 34056, upload-time = "2024-10-13T12:14:31.757Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/04/13/d9839089b900fa7b479cce495d62110cddc4bd5630a04d8469916c0e79c5/frozendict-2.4.6-py311-none-any.whl", hash = "sha256:d065db6a44db2e2375c23eac816f1a022feb2fa98cbb50df44a9e83700accbea", size = 16148, upload-time = "2024-10-13T12:15:26.839Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ba/d0/d482c39cee2ab2978a892558cf130681d4574ea208e162da8958b31e9250/frozendict-2.4.6-py312-none-any.whl", hash = "sha256:49344abe90fb75f0f9fdefe6d4ef6d4894e640fadab71f11009d52ad97f370b9", size = 16146, upload-time = "2024-10-13T12:15:28.16Z" }, +] + +[[package]] +name = "frozenlist" +version = "1.7.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/packages/af/36/0da0a49409f6b47cc2d060dc8c9040b897b5902a8a4e37d9bc1deb11f680/frozenlist-1.7.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cc4df77d638aa2ed703b878dd093725b72a824c3c546c076e8fdf276f78ee84a", size = 81304, upload-time = "2025-06-09T22:59:46.226Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/77/f0/77c11d13d39513b298e267b22eb6cb559c103d56f155aa9a49097221f0b6/frozenlist-1.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:716a9973a2cc963160394f701964fe25012600f3d311f60c790400b00e568b61", size = 47735, upload-time = "2025-06-09T22:59:48.133Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/37/12/9d07fa18971a44150593de56b2f2947c46604819976784bcf6ea0d5db43b/frozenlist-1.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0fd1bad056a3600047fb9462cff4c5322cebc59ebf5d0a3725e0ee78955001d", size = 46775, upload-time = "2025-06-09T22:59:49.564Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/70/34/f73539227e06288fcd1f8a76853e755b2b48bca6747e99e283111c18bcd4/frozenlist-1.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3789ebc19cb811163e70fe2bd354cea097254ce6e707ae42e56f45e31e96cb8e", size = 224644, upload-time = "2025-06-09T22:59:51.35Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fb/68/c1d9c2f4a6e438e14613bad0f2973567586610cc22dcb1e1241da71de9d3/frozenlist-1.7.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af369aa35ee34f132fcfad5be45fbfcde0e3a5f6a1ec0712857f286b7d20cca9", size = 222125, upload-time = "2025-06-09T22:59:52.884Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b9/d0/98e8f9a515228d708344d7c6986752be3e3192d1795f748c24bcf154ad99/frozenlist-1.7.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac64b6478722eeb7a3313d494f8342ef3478dff539d17002f849101b212ef97c", size = 233455, upload-time = "2025-06-09T22:59:54.74Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/79/df/8a11bcec5600557f40338407d3e5bea80376ed1c01a6c0910fcfdc4b8993/frozenlist-1.7.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f89f65d85774f1797239693cef07ad4c97fdd0639544bad9ac4b869782eb1981", size = 227339, upload-time = "2025-06-09T22:59:56.187Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/50/82/41cb97d9c9a5ff94438c63cc343eb7980dac4187eb625a51bdfdb7707314/frozenlist-1.7.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1073557c941395fdfcfac13eb2456cb8aad89f9de27bae29fabca8e563b12615", size = 212969, upload-time = "2025-06-09T22:59:57.604Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/13/47/f9179ee5ee4f55629e4f28c660b3fdf2775c8bfde8f9c53f2de2d93f52a9/frozenlist-1.7.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed8d2fa095aae4bdc7fdd80351009a48d286635edffee66bf865e37a9125c50", size = 222862, upload-time = "2025-06-09T22:59:59.498Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1a/52/df81e41ec6b953902c8b7e3a83bee48b195cb0e5ec2eabae5d8330c78038/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:24c34bea555fe42d9f928ba0a740c553088500377448febecaa82cc3e88aa1fa", size = 222492, upload-time = "2025-06-09T23:00:01.026Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/84/17/30d6ea87fa95a9408245a948604b82c1a4b8b3e153cea596421a2aef2754/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:69cac419ac6a6baad202c85aaf467b65ac860ac2e7f2ac1686dc40dbb52f6577", size = 238250, upload-time = "2025-06-09T23:00:03.401Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8f/00/ecbeb51669e3c3df76cf2ddd66ae3e48345ec213a55e3887d216eb4fbab3/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:960d67d0611f4c87da7e2ae2eacf7ea81a5be967861e0c63cf205215afbfac59", size = 218720, upload-time = "2025-06-09T23:00:05.282Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1a/c0/c224ce0e0eb31cc57f67742071bb470ba8246623c1823a7530be0e76164c/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:41be2964bd4b15bf575e5daee5a5ce7ed3115320fb3c2b71fca05582ffa4dc9e", size = 232585, upload-time = "2025-06-09T23:00:07.962Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/55/3c/34cb694abf532f31f365106deebdeac9e45c19304d83cf7d51ebbb4ca4d1/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:46d84d49e00c9429238a7ce02dc0be8f6d7cd0cd405abd1bebdc991bf27c15bd", size = 234248, upload-time = "2025-06-09T23:00:09.428Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/98/c0/2052d8b6cecda2e70bd81299e3512fa332abb6dcd2969b9c80dfcdddbf75/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:15900082e886edb37480335d9d518cec978afc69ccbc30bd18610b7c1b22a718", size = 221621, upload-time = "2025-06-09T23:00:11.32Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c5/bf/7dcebae315436903b1d98ffb791a09d674c88480c158aa171958a3ac07f0/frozenlist-1.7.0-cp310-cp310-win32.whl", hash = "sha256:400ddd24ab4e55014bba442d917203c73b2846391dd42ca5e38ff52bb18c3c5e", size = 39578, upload-time = "2025-06-09T23:00:13.526Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8f/5f/f69818f017fa9a3d24d1ae39763e29b7f60a59e46d5f91b9c6b21622f4cd/frozenlist-1.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:6eb93efb8101ef39d32d50bce242c84bcbddb4f7e9febfa7b524532a239b4464", size = 43830, upload-time = "2025-06-09T23:00:14.98Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/34/7e/803dde33760128acd393a27eb002f2020ddb8d99d30a44bfbaab31c5f08a/frozenlist-1.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:aa51e147a66b2d74de1e6e2cf5921890de6b0f4820b257465101d7f37b49fb5a", size = 82251, upload-time = "2025-06-09T23:00:16.279Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/75/a9/9c2c5760b6ba45eae11334db454c189d43d34a4c0b489feb2175e5e64277/frozenlist-1.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9b35db7ce1cd71d36ba24f80f0c9e7cff73a28d7a74e91fe83e23d27c7828750", size = 48183, upload-time = "2025-06-09T23:00:17.698Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/47/be/4038e2d869f8a2da165f35a6befb9158c259819be22eeaf9c9a8f6a87771/frozenlist-1.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34a69a85e34ff37791e94542065c8416c1afbf820b68f720452f636d5fb990cd", size = 47107, upload-time = "2025-06-09T23:00:18.952Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/79/26/85314b8a83187c76a37183ceed886381a5f992975786f883472fcb6dc5f2/frozenlist-1.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a646531fa8d82c87fe4bb2e596f23173caec9185bfbca5d583b4ccfb95183e2", size = 237333, upload-time = "2025-06-09T23:00:20.275Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1f/fd/e5b64f7d2c92a41639ffb2ad44a6a82f347787abc0c7df5f49057cf11770/frozenlist-1.7.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:79b2ffbba483f4ed36a0f236ccb85fbb16e670c9238313709638167670ba235f", size = 231724, upload-time = "2025-06-09T23:00:21.705Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/20/fb/03395c0a43a5976af4bf7534759d214405fbbb4c114683f434dfdd3128ef/frozenlist-1.7.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a26f205c9ca5829cbf82bb2a84b5c36f7184c4316617d7ef1b271a56720d6b30", size = 245842, upload-time = "2025-06-09T23:00:23.148Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d0/15/c01c8e1dffdac5d9803507d824f27aed2ba76b6ed0026fab4d9866e82f1f/frozenlist-1.7.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bcacfad3185a623fa11ea0e0634aac7b691aa925d50a440f39b458e41c561d98", size = 239767, upload-time = "2025-06-09T23:00:25.103Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/14/99/3f4c6fe882c1f5514b6848aa0a69b20cb5e5d8e8f51a339d48c0e9305ed0/frozenlist-1.7.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72c1b0fe8fe451b34f12dce46445ddf14bd2a5bcad7e324987194dc8e3a74c86", size = 224130, upload-time = "2025-06-09T23:00:27.061Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4d/83/220a374bd7b2aeba9d0725130665afe11de347d95c3620b9b82cc2fcab97/frozenlist-1.7.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61d1a5baeaac6c0798ff6edfaeaa00e0e412d49946c53fae8d4b8e8b3566c4ae", size = 235301, upload-time = "2025-06-09T23:00:29.02Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/03/3c/3e3390d75334a063181625343e8daab61b77e1b8214802cc4e8a1bb678fc/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7edf5c043c062462f09b6820de9854bf28cc6cc5b6714b383149745e287181a8", size = 234606, upload-time = "2025-06-09T23:00:30.514Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/23/1e/58232c19608b7a549d72d9903005e2d82488f12554a32de2d5fb59b9b1ba/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:d50ac7627b3a1bd2dcef6f9da89a772694ec04d9a61b66cf87f7d9446b4a0c31", size = 248372, upload-time = "2025-06-09T23:00:31.966Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c0/a4/e4a567e01702a88a74ce8a324691e62a629bf47d4f8607f24bf1c7216e7f/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ce48b2fece5aeb45265bb7a58259f45027db0abff478e3077e12b05b17fb9da7", size = 229860, upload-time = "2025-06-09T23:00:33.375Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/73/a6/63b3374f7d22268b41a9db73d68a8233afa30ed164c46107b33c4d18ecdd/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:fe2365ae915a1fafd982c146754e1de6ab3478def8a59c86e1f7242d794f97d5", size = 245893, upload-time = "2025-06-09T23:00:35.002Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6d/eb/d18b3f6e64799a79673c4ba0b45e4cfbe49c240edfd03a68be20002eaeaa/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:45a6f2fdbd10e074e8814eb98b05292f27bad7d1883afbe009d96abdcf3bc898", size = 246323, upload-time = "2025-06-09T23:00:36.468Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5a/f5/720f3812e3d06cd89a1d5db9ff6450088b8f5c449dae8ffb2971a44da506/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:21884e23cffabb157a9dd7e353779077bf5b8f9a58e9b262c6caad2ef5f80a56", size = 233149, upload-time = "2025-06-09T23:00:37.963Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/69/68/03efbf545e217d5db8446acfd4c447c15b7c8cf4dbd4a58403111df9322d/frozenlist-1.7.0-cp311-cp311-win32.whl", hash = "sha256:284d233a8953d7b24f9159b8a3496fc1ddc00f4db99c324bd5fb5f22d8698ea7", size = 39565, upload-time = "2025-06-09T23:00:39.753Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/58/17/fe61124c5c333ae87f09bb67186d65038834a47d974fc10a5fadb4cc5ae1/frozenlist-1.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:387cbfdcde2f2353f19c2f66bbb52406d06ed77519ac7ee21be0232147c2592d", size = 44019, upload-time = "2025-06-09T23:00:40.988Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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 = "2025.3.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/34/f4/5721faf47b8c499e776bc34c6a8fc17efdf7fdef0b00f398128bc5dcb4ac/fsspec-2025.3.0.tar.gz", hash = "sha256:a935fd1ea872591f2b5148907d103488fc523295e6c64b835cfad8c3eca44972", size = 298491, upload-time = "2025-03-07T21:47:56.461Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/56/53/eb690efa8513166adef3e0669afd31e95ffde69fb3c52ec2ac7223ed6018/fsspec-2025.3.0-py3-none-any.whl", hash = "sha256:efb87af3efa9103f94ca91a7f8cb7a4df91af9f74fc106c9c7ea0efd7277c1b3", size = 193615, upload-time = "2025-03-07T21:47:54.809Z" }, +] + +[package.optional-dependencies] +http = [ + { name = "aiohttp" }, +] + +[[package]] +name = "future" +version = "1.0.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a7/b2/4140c69c6a66432916b26158687e821ba631a4c9273c474343badf84d3ba/future-1.0.0.tar.gz", hash = "sha256:bd2968309307861edae1458a4f8a4f3598c03be43b97521076aebf5d94c07b05", size = 1228490, upload-time = "2024-02-21T11:52:38.461Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/da/71/ae30dadffc90b9006d77af76b393cb9dfbfc9629f339fc1574a1c52e6806/future-1.0.0-py3-none-any.whl", hash = "sha256:929292d34f5872e70396626ef385ec22355a1fae8ad29e1a734c3e43f9fbc216", size = 491326, upload-time = "2024-02-21T11:52:35.956Z" }, +] + +[[package]] +name = "gensim" +version = "4.3.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "numpy" }, + { name = "scipy" }, + { name = "smart-open" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ec/bc/36ce4d510085cf150f17d79bb5e88cde942aeba2a894aed5893812ea1e6d/gensim-4.3.3.tar.gz", hash = "sha256:84852076a6a3d88d7dac5be245e24c21c3b819b565e14c1b61fa3e5ee76dcf57", size = 23258708, upload-time = "2024-07-19T14:42:35.418Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/27/12/047dc8b6bed7c4833bcdfbafc10af0f96dc3847ce37be63b14bd6e6c7767/gensim-4.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4e72840adfbea35c5804fd559bc0cb6bc9f439926220a37d852b7ce76eb325c1", size = 24086876, upload-time = "2024-07-19T14:39:26.268Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ff/6e/7c6d7dda41924b83c4b1eb096942b68b85ba305df7f0963ad0642ac0d73f/gensim-4.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4019263c9d9afae7c669f880c17e09461e77a71afce04ed4d79cf71a4cad2848", size = 24041730, upload-time = "2024-07-19T14:39:34.431Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/73/f4/376290613da44ea9d11bdce3a1705ba7cc25f971edb2b460dc192092068c/gensim-4.3.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dea62d3e2ada547687bde6cbba37efa50b534db77e9d44fd5802676bb072c9d9", size = 26398007, upload-time = "2024-07-19T14:39:41.67Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/de/63/776ee55c773f55fa9d4fc1596f2e5e15de109921a6727dfe29cc4f0baeb7/gensim-4.3.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fac93ef5e44982defef9d3c1e4cd00245506b8a29cec19ec5e00f0221b8144c", size = 26506925, upload-time = "2024-07-19T14:39:48.662Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cd/4a/f07e2f255aedd6bb4bd0ae420a465f228a4a91bc78ac359216ea20557be6/gensim-4.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:7c3409f755fb8d62da99cea65e7a40a99d21f8fd86443a3aaf2d90eb68995021", size = 24012924, upload-time = "2024-07-19T14:39:56.224Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7b/f4/f43fd909aa29fd92f0e6d703d90c0e6507a7c6be3d686a025b1e192afa3a/gensim-4.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:99e7b70352aecc6c1674dde82b75f453e7a5d1cc71ac1cfbc460bf1fe20501b7", size = 24082968, upload-time = "2024-07-19T14:40:03.849Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2a/15/aca2fc3b9e97bd0e28be4a4302793c43757b04b828223c6d103c72132f19/gensim-4.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:32a4cac3f3c38af2069eab9524609fc92ebaeb2692b7280cfda365a3517a280a", size = 24036231, upload-time = "2024-07-19T14:40:10.943Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ef/84/e46049a16fa7daa26ac9e83e41b3bc3b30867da832a5d7cb0779da893255/gensim-4.3.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c071b4329ed1be02446eb7ef637b94c68cf0080c15c57fbcde667fce2e49c3fe", size = 26558362, upload-time = "2024-07-19T14:40:17.997Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/78/4f/f6045d5d5f8e7838c42572607ce440f95dbf4de5da41ae664198c2839c05/gensim-4.3.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d662bf96e3d741b6ab61a54be842a7cbf5e45193008b2f4225c758cafd7f9cdc", size = 26662669, upload-time = "2024-07-19T14:40:26.14Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f5/57/f2e6568dbf464a4b270954e5fa3dee4a4054d163a41c0e7bf0a34eb40f0f/gensim-4.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:a54bd53a0e6f991abb837f126663353657270e75be53287e8a568ada0b35b1b0", size = 24010102, upload-time = "2024-07-19T14:40:33.359Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/40/f1/3231b3fd6f7424f28d7d673679c843da0c61659538262a234f9f43ed5b10/gensim-4.3.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9a65ed1a8c1fc83890b4eb2a45ae2b32e82a0209c970c8c74694d0374c2415cb", size = 24079041, upload-time = "2024-07-19T14:40:40.907Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1f/76/616bc781bc19ee76b387a101211f73e00cf59368fcc221e77f88ea907d04/gensim-4.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4db485e08a0287e0fd6a029d89b90913d1df38f1dcd34cd2ab758873ba9255f3", size = 24035496, upload-time = "2024-07-19T14:40:47.667Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e0/b7/a316ba52548ca405413c23967c1c6c77d00f82cf6b0cb63d268001e023aa/gensim-4.3.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7198987116373ab99f034b292a04ac841531d12b56345851c98b40a3fcd93a85", size = 26487104, upload-time = "2024-07-19T14:40:54.867Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1a/07/7a0d5e6cab4da2769c8018f2472690ccb8cab191bf2fe46342dfd627486b/gensim-4.3.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6237a50de4da7a037b19b2b6c430b6537243dcdedebf94afeb089e951953e601", size = 26606101, upload-time = "2024-07-19T14:41:02.539Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/79/7b/747fcb06280764cf20353361162eff68c6b0a3be34c43ead5ae393d3b18e/gensim-4.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:c910c2d5a71f532273166a3a82762959973f0513b221a495fa5a2a07652ee66d", size = 24009244, upload-time = "2024-07-19T14:41:09.732Z" }, +] + +[[package]] +name = "google" +version = "3.0.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "beautifulsoup4" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/89/97/b49c69893cddea912c7a660a4b6102c6b02cd268f8c7162dd70b7c16f753/google-3.0.0.tar.gz", hash = "sha256:143530122ee5130509ad5e989f0512f7cb218b2d4eddbafbad40fd10e8d8ccbe", size = 44978, upload-time = "2020-07-11T14:50:45.678Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ac/35/17c9141c4ae21e9a29a43acdfd848e3e468a810517f862cad07977bf8fe9/google-3.0.0-py2.py3-none-any.whl", hash = "sha256:889cf695f84e4ae2c55fbc0cfdaf4c1e729417fa52ab1db0485202ba173e4935", size = 45258, upload-time = "2020-07-11T14:49:58.287Z" }, +] + +[[package]] +name = "google-ai-generativelanguage" +version = "0.6.15" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "google-api-core", extra = ["grpc"] }, + { name = "google-auth" }, + { name = "proto-plus" }, + { name = "protobuf" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/11/d1/48fe5d7a43d278e9f6b5ada810b0a3530bbeac7ed7fcbcd366f932f05316/google_ai_generativelanguage-0.6.15.tar.gz", hash = "sha256:8f6d9dc4c12b065fe2d0289026171acea5183ebf2d0b11cefe12f3821e159ec3", size = 1375443, upload-time = "2025-01-13T21:50:47.459Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7c/a3/67b8a6ff5001a1d8864922f2d6488dc2a14367ceb651bc3f09a947f2f306/google_ai_generativelanguage-0.6.15-py3-none-any.whl", hash = "sha256:5a03ef86377aa184ffef3662ca28f19eeee158733e45d7947982eb953c6ebb6c", size = 1327356, upload-time = "2025-01-13T21:50:44.174Z" }, +] + +[[package]] +name = "google-api-core" +version = "2.25.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "google-auth" }, + { name = "googleapis-common-protos" }, + { name = "proto-plus" }, + { name = "protobuf" }, + { name = "requests" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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.optional-dependencies] +grpc = [ + { name = "grpcio" }, + { name = "grpcio-status" }, +] + +[[package]] +name = "google-api-python-client" +version = "2.177.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "google-api-core" }, + { name = "google-auth" }, + { name = "google-auth-httplib2" }, + { name = "httplib2" }, + { name = "uritemplate" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7a/75/a89cad519fa8910132e3b08571d0e682ae1163643da6f963f1930f3dc788/google_api_python_client-2.177.0.tar.gz", hash = "sha256:9ffd2b57d68f5afa7e6ac64e2c440534eaa056cbb394812a62ff94723c31b50e", size = 13184405, upload-time = "2025-07-23T16:22:46.321Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/47/f5/121248e18ca605a11720c81ae1b52a5a8cb690af9f01887c56de23cd9a5a/google_api_python_client-2.177.0-py3-none-any.whl", hash = "sha256:f2f50f11105ab883eb9b6cf38ec54ea5fd4b429249f76444bec90deba5be79b3", size = 13709470, upload-time = "2025-07-23T16:22:44.081Z" }, +] + +[[package]] +name = "google-auth" +version = "2.40.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "cachetools" }, + { name = "pyasn1-modules" }, + { name = "rsa" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9e/9b/e92ef23b84fa10a64ce4831390b7a4c2e53c0132568d99d4ae61d04c8855/google_auth-2.40.3.tar.gz", hash = "sha256:500c3a29adedeb36ea9cf24b8d10858e152f2412e3ca37829b3fa18e33d63b77", size = 281029, upload-time = "2025-06-04T18:04:57.577Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/17/63/b19553b658a1692443c62bd07e5868adaa0ad746a0751ba62c59568cd45b/google_auth-2.40.3-py2.py3-none-any.whl", hash = "sha256:1370d4593e86213563547f97a92752fc658456fe4514c809544f330fed45a7ca", size = 216137, upload-time = "2025-06-04T18:04:55.573Z" }, +] + +[[package]] +name = "google-auth-httplib2" +version = "0.2.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "google-auth" }, + { name = "httplib2" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/56/be/217a598a818567b28e859ff087f347475c807a5649296fb5a817c58dacef/google-auth-httplib2-0.2.0.tar.gz", hash = "sha256:38aa7badf48f974f1eb9861794e9c0cb2a0511a4ec0679b1f886d108f5640e05", size = 10842, upload-time = "2023-12-12T17:40:30.722Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/be/8a/fe34d2f3f9470a27b01c9e76226965863f153d5fbe276f83608562e49c04/google_auth_httplib2-0.2.0-py2.py3-none-any.whl", hash = "sha256:b65a0a2123300dd71281a7bf6e64d65a0759287df52729bdd1ae2e47dc311a3d", size = 9253, upload-time = "2023-12-12T17:40:13.055Z" }, +] + +[[package]] +name = "google-cloud-aiplatform" +version = "1.70.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "docstring-parser" }, + { name = "google-api-core", extra = ["grpc"] }, + { name = "google-auth" }, + { name = "google-cloud-bigquery" }, + { name = "google-cloud-resource-manager" }, + { name = "google-cloud-storage" }, + { name = "packaging" }, + { name = "proto-plus" }, + { name = "protobuf" }, + { name = "pydantic" }, + { name = "shapely" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/88/06/bc8028c03d4bedb85114c780a9f749b67ff06ce29d25dc7f1a99622f2692/google-cloud-aiplatform-1.70.0.tar.gz", hash = "sha256:e8edef6dbc7911380d0ea55c47544e799f62b891cb1a83b504ca1c09fff9884b", size = 6311624, upload-time = "2024-10-09T04:28:12.606Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/46/d9/280e5a9b5caf69322f64fa55f62bf447d76c5fe30e8df6e93373f22c4bd7/google_cloud_aiplatform-1.70.0-py2.py3-none-any.whl", hash = "sha256:690e6041f03d3aa85102ac3f316c958d6f43a99aefb7fb3f8938dee56d08abd9", size = 5267225, upload-time = "2024-10-09T04:28:09.271Z" }, +] + +[[package]] +name = "google-cloud-bigquery" +version = "3.35.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "google-api-core", extra = ["grpc"] }, + { name = "google-auth" }, + { name = "google-cloud-core" }, + { name = "google-resumable-media" }, + { name = "packaging" }, + { name = "python-dateutil" }, + { name = "requests" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/44/e4/9cf03fa81fefd1b9811a7cd6e398804ae0de3b6a4edef810e2acd45cabbc/google_cloud_bigquery-3.35.1.tar.gz", hash = "sha256:599f26cacf190acfe88000f6cc5f4bc9e6baac7899e4f406ca054f1906f71960", size = 496433, upload-time = "2025-07-24T15:09:04.108Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/63/50/96fe9bc5b83d3a421e91ed8edc2535de45957e9af398273e3ecb5c3a1094/google_cloud_bigquery-3.35.1-py3-none-any.whl", hash = "sha256:6739a6ba63c6d80735ca2b34b1df2090ff473b80c1a62354caa2debe6dbbd961", size = 256877, upload-time = "2025-07-24T15:09:02.443Z" }, +] + +[[package]] +name = "google-cloud-core" +version = "2.4.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "google-api-core" }, + { name = "google-auth" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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-resource-manager" +version = "1.14.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "google-api-core", extra = ["grpc"] }, + { name = "google-auth" }, + { name = "grpc-google-iam-v1" }, + { name = "proto-plus" }, + { name = "protobuf" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6e/ca/a4648f5038cb94af4b3942815942a03aa9398f9fb0bef55b3f1585b9940d/google_cloud_resource_manager-1.14.2.tar.gz", hash = "sha256:962e2d904c550d7bac48372607904ff7bb3277e3bb4a36d80cc9a37e28e6eb74", size = 446370, upload-time = "2025-03-17T11:35:56.343Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b1/ea/a92631c358da377af34d3a9682c97af83185c2d66363d5939ab4a1169a7f/google_cloud_resource_manager-1.14.2-py3-none-any.whl", hash = "sha256:d0fa954dedd1d2b8e13feae9099c01b8aac515b648e612834f9942d2795a9900", size = 394344, upload-time = "2025-03-17T11:35:54.722Z" }, +] + +[[package]] +name = "google-cloud-storage" +version = "2.19.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/packages/eb/69/b1b05cf415df0d86691d6a8b4b7e60ab3a6fb6efb783ee5cd3ed1382bfd3/google_crc32c-1.7.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:b07d48faf8292b4db7c3d64ab86f950c2e94e93a11fd47271c28ba458e4a0d76", size = 30467, upload-time = "2025-03-26T14:31:11.92Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/44/3d/92f8928ecd671bd5b071756596971c79d252d09b835cdca5a44177fa87aa/google_crc32c-1.7.1-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:7cc81b3a2fbd932a4313eb53cc7d9dde424088ca3a0337160f35d91826880c1d", size = 30311, upload-time = "2025-03-26T14:53:14.161Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/33/42/c2d15a73df79d45ed6b430b9e801d0bd8e28ac139a9012d7d58af50a385d/google_crc32c-1.7.1-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1c67ca0a1f5b56162951a9dae987988679a7db682d6f97ce0f6381ebf0fbea4c", size = 37889, upload-time = "2025-03-26T14:41:27.83Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/57/ea/ac59c86a3c694afd117bb669bde32aaf17d0de4305d01d706495f09cbf19/google_crc32c-1.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc5319db92daa516b653600794d5b9f9439a9a121f3e162f94b0e1891c7933cb", size = 33028, upload-time = "2025-03-26T14:41:29.141Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/60/44/87e77e8476767a4a93f6cf271157c6d948eacec63688c093580af13b04be/google_crc32c-1.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcdf5a64adb747610140572ed18d011896e3b9ae5195f2514b7ff678c80f1603", size = 38026, upload-time = "2025-03-26T14:41:29.921Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c8/bf/21ac7bb305cd7c1a6de9c52f71db0868e104a5b573a4977cd9d0ff830f82/google_crc32c-1.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:754561c6c66e89d55754106739e22fdaa93fafa8da7221b29c8b8e8270c6ec8a", size = 33476, upload-time = "2025-03-26T14:29:09.086Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f7/94/220139ea87822b6fdfdab4fb9ba81b3fff7ea2c82e2af34adc726085bffc/google_crc32c-1.7.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:6fbab4b935989e2c3610371963ba1b86afb09537fd0c633049be82afe153ac06", size = 30468, upload-time = "2025-03-26T14:32:52.215Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/94/97/789b23bdeeb9d15dc2904660463ad539d0318286d7633fe2760c10ed0c1c/google_crc32c-1.7.1-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:ed66cbe1ed9cbaaad9392b5259b3eba4a9e565420d734e6238813c428c3336c9", size = 30313, upload-time = "2025-03-26T14:57:38.758Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/81/b8/976a2b843610c211e7ccb3e248996a61e87dbb2c09b1499847e295080aec/google_crc32c-1.7.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee6547b657621b6cbed3562ea7826c3e11cab01cd33b74e1f677690652883e77", size = 33048, upload-time = "2025-03-26T14:41:30.679Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c9/16/a3842c2cf591093b111d4a5e2bfb478ac6692d02f1b386d2a33283a19dc9/google_crc32c-1.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d68e17bad8f7dd9a49181a1f5a8f4b251c6dbc8cc96fb79f1d321dfd57d66f53", size = 32669, upload-time = "2025-03-26T14:41:31.432Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/04/17/ed9aba495916fcf5fe4ecb2267ceb851fc5f273c4e4625ae453350cfd564/google_crc32c-1.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:6335de12921f06e1f774d0dd1fbea6bf610abe0887a1638f64d694013138be5d", size = 33476, upload-time = "2025-03-26T14:29:10.211Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0b/43/31e57ce04530794917dfe25243860ec141de9fadf4aa9783dffe7dac7c39/google_crc32c-1.7.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a8e9afc74168b0b2232fb32dd202c93e46b7d5e4bf03e66ba5dc273bb3559589", size = 28242, upload-time = "2025-03-26T14:41:42.858Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/eb/f3/8b84cd4e0ad111e63e30eb89453f8dd308e3ad36f42305cf8c202461cdf0/google_crc32c-1.7.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa8136cc14dd27f34a3221c0f16fd42d8a40e4778273e61a3c19aedaa44daf6b", size = 28049, upload-time = "2025-03-26T14:41:44.651Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/16/1b/1693372bf423ada422f80fd88260dbfd140754adb15cbc4d7e9a68b1cb8e/google_crc32c-1.7.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85fef7fae11494e747c9fd1359a527e5970fc9603c90764843caabd3a16a0a48", size = 28241, upload-time = "2025-03-26T14:41:45.898Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fd/3c/2a19a60a473de48717b4efb19398c3f914795b64a96cf3fbe82588044f78/google_crc32c-1.7.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6efb97eb4369d52593ad6f75e7e10d053cf00c48983f7a973105bc70b0ac4d82", size = 28048, upload-time = "2025-03-26T14:41:46.696Z" }, +] + +[[package]] +name = "google-genai" +version = "1.43.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "anyio" }, + { name = "google-auth" }, + { name = "httpx" }, + { name = "pydantic" }, + { name = "requests" }, + { name = "tenacity" }, + { name = "typing-extensions" }, + { name = "websockets" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c1/75/992ca4462682949750709678b8efbc865222c9a16cf34504b69c5459606c/google_genai-1.43.0.tar.gz", hash = "sha256:84eb219d320759c5882bc2cdb4e2ac84544d00f5d12c7892c79fb03d71bfc9a4", size = 236132, upload-time = "2025-10-10T23:16:40.131Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/61/85/e90dda488d5044e6e4cd1b49e7e7f0cc7f4a2a1c8004e88a5122d42ea024/google_genai-1.43.0-py3-none-any.whl", hash = "sha256:be1d4b1acab268125d536fd81b73c38694a70cb08266759089154718924434fd", size = 236733, upload-time = "2025-10-10T23:16:38.809Z" }, +] + +[[package]] +name = "google-generativeai" +version = "0.8.5" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "google-ai-generativelanguage" }, + { name = "google-api-core" }, + { name = "google-api-python-client" }, + { name = "google-auth" }, + { name = "protobuf" }, + { name = "pydantic" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6e/40/c42ff9ded9f09ec9392879a8e6538a00b2dc185e834a3392917626255419/google_generativeai-0.8.5-py3-none-any.whl", hash = "sha256:22b420817fb263f8ed520b33285f45976d5b21e904da32b80d4fd20c055123a2", size = 155427, upload-time = "2025-04-17T00:40:00.67Z" }, +] + +[[package]] +name = "google-resumable-media" +version = "2.7.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "google-crc32c" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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 = "google-search-results" +version = "2.4.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "requests" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/77/30/b3a6f6a2e00f8153549c2fa345c58ae1ce8e5f3153c2fe0484d444c3abcb/google_search_results-2.4.2.tar.gz", hash = "sha256:603a30ecae2af8e600b22635757a6df275dad4b934f975e67878ccd640b78245", size = 18818, upload-time = "2023-03-10T11:13:09.953Z" } + +[[package]] +name = "googleapis-common-protos" +version = "1.70.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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.optional-dependencies] +grpc = [ + { name = "grpcio" }, +] + +[[package]] +name = "gprofiler-official" +version = "1.0.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "requests" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ec/c1/d9252620d09a064247d1623ebc4732d624921a2ed80a677f8b9ce61810dd/gprofiler-official-1.0.0.tar.gz", hash = "sha256:5015b47f10fbdcb59c57e342e815c9c07afbe57cd3984154f75b845ddef2445d", size = 9584, upload-time = "2019-04-02T10:52:19.527Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/df/1b/5a87c1a1da8f601c00a0ce4dedb5aab8a5cad6a0f4a5062c4da22a045072/gprofiler_official-1.0.0-py3-none-any.whl", hash = "sha256:c582baf728e5a6cddac964e4085ca385e082c4ef0279e3af1a16a9af07ab5395", size = 9277, upload-time = "2019-04-02T10:52:17.769Z" }, +] + +[[package]] +name = "graspologic" +version = "3.4.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "anytree" }, + { name = "beartype" }, + { name = "gensim" }, + { name = "graspologic-native" }, + { name = "hyppo" }, + { name = "joblib" }, + { name = "matplotlib" }, + { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version < '3.11'" }, + { name = "networkx", version = "3.5", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy" }, + { name = "pot" }, + { name = "scikit-learn" }, + { name = "scipy" }, + { name = "seaborn" }, + { name = "statsmodels" }, + { name = "typing-extensions" }, + { name = "umap-learn" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/06/de/83d653cc8029dc8c5f75bc5aea68f6b1e834230f05525fb3e7ac4aeae226/graspologic-3.4.1.tar.gz", hash = "sha256:7561f0b852a2bccd351bff77e8db07d9892f9dfa35a420fdec01690e4fdc8075", size = 5134018, upload-time = "2024-05-22T22:54:42.797Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6a/0b/9a167cec9cc4555b59cd282e8669998a50cb3f929a9a503965b24fa58a20/graspologic-3.4.1-py3-none-any.whl", hash = "sha256:c6563e087eda599bad1de831d4b7321c0daa7a82f4e85a7d7737ff67e07cdda2", size = 5200768, upload-time = "2024-05-22T22:54:39.259Z" }, +] + +[[package]] +name = "graspologic-native" +version = "1.2.5" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/25/2d/62b30d89533643ccf4778a18eb023f291b8877b5d85de3342f07b2d363a7/graspologic_native-1.2.5.tar.gz", hash = "sha256:27ea7e01fa44466c0b4cdd678d4561e5d3dc0cb400015683b7ae1386031257a0", size = 2512729, upload-time = "2025-04-02T19:34:22.961Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ae/86/10748f4c474b0c8f6060dd379bb0c4da5d42779244bb13a58656ffb44a03/graspologic_native-1.2.5-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:bf05f2e162ae2a2a8d6e8cfccbe3586d1faa0b808159ff950478348df557c61e", size = 648437, upload-time = "2025-04-02T19:34:16.29Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/42/cc/b75ea35755340bedda29727e5388390c639ea533f55b9249f5ac3003f656/graspologic_native-1.2.5-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a7fff06ed49c3875cf351bb09a92ae7cbc169ce92dcc4c3439e28e801f822ae", size = 352044, upload-time = "2025-04-02T19:34:18.153Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8e/55/15e6e4f18bf249b529ac4cd1522b03f5c9ef9284a2f7bfaa1fd1f96464fe/graspologic_native-1.2.5-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53e7e993e7d70fe0d860773fc62812fbb8cb4ef2d11d8661a1f06f8772593915", size = 364644, upload-time = "2025-04-02T19:34:19.486Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3b/51/21097af79f3d68626539ab829bdbf6cc42933f020e161972927d916e394c/graspologic_native-1.2.5-cp38-abi3-win_amd64.whl", hash = "sha256:c3ef2172d774083d7e2c8e77daccd218571ddeebeb2c1703cebb1a2cc4c56e07", size = 210438, upload-time = "2025-04-02T19:34:21.139Z" }, +] + +[[package]] +name = "greenlet" +version = "3.2.4" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/packages/7d/ed/6bfa4109fcb23a58819600392564fea69cdc6551ffd5e69ccf1d52a40cbc/greenlet-3.2.4-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:8c68325b0d0acf8d91dde4e6f930967dd52a5302cd4062932a6b2e7c2969f47c", size = 271061, upload-time = "2025-08-07T13:17:15.373Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2a/fc/102ec1a2fc015b3a7652abab7acf3541d58c04d3d17a8d3d6a44adae1eb1/greenlet-3.2.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:94385f101946790ae13da500603491f04a76b6e4c059dab271b3ce2e283b2590", size = 629475, upload-time = "2025-08-07T13:42:54.009Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c5/26/80383131d55a4ac0fb08d71660fd77e7660b9db6bdb4e8884f46d9f2cc04/greenlet-3.2.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f10fd42b5ee276335863712fa3da6608e93f70629c631bf77145021600abc23c", size = 640802, upload-time = "2025-08-07T13:45:25.52Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9f/7c/e7833dbcd8f376f3326bd728c845d31dcde4c84268d3921afcae77d90d08/greenlet-3.2.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c8c9e331e58180d0d83c5b7999255721b725913ff6bc6cf39fa2a45841a4fd4b", size = 636703, upload-time = "2025-08-07T13:53:12.622Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e9/49/547b93b7c0428ede7b3f309bc965986874759f7d89e4e04aeddbc9699acb/greenlet-3.2.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:58b97143c9cc7b86fc458f215bd0932f1757ce649e05b640fea2e79b54cedb31", size = 635417, upload-time = "2025-08-07T13:18:25.189Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7f/91/ae2eb6b7979e2f9b035a9f612cf70f1bf54aad4e1d125129bef1eae96f19/greenlet-3.2.4-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c2ca18a03a8cfb5b25bc1cbe20f3d9a4c80d8c3b13ba3df49ac3961af0b1018d", size = 584358, upload-time = "2025-08-07T13:18:23.708Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f7/85/433de0c9c0252b22b16d413c9407e6cb3b41df7389afc366ca204dbc1393/greenlet-3.2.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9fe0a28a7b952a21e2c062cd5756d34354117796c6d9215a87f55e38d15402c5", size = 1113550, upload-time = "2025-08-07T13:42:37.467Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a1/8d/88f3ebd2bc96bf7747093696f4335a0a8a4c5acfcf1b757717c0d2474ba3/greenlet-3.2.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8854167e06950ca75b898b104b63cc646573aa5fef1353d4508ecdd1ee76254f", size = 1137126, upload-time = "2025-08-07T13:18:20.239Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d6/6f/b60b0291d9623c496638c582297ead61f43c4b72eef5e9c926ef4565ec13/greenlet-3.2.4-cp310-cp310-win_amd64.whl", hash = "sha256:73f49b5368b5359d04e18d15828eecc1806033db5233397748f4ca813ff1056c", size = 298654, upload-time = "2025-08-07T13:50:00.469Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a4/de/f28ced0a67749cac23fecb02b694f6473f47686dff6afaa211d186e2ef9c/greenlet-3.2.4-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:96378df1de302bc38e99c3a9aa311967b7dc80ced1dcc6f171e99842987882a2", size = 272305, upload-time = "2025-08-07T13:15:41.288Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/09/16/2c3792cba130000bf2a31c5272999113f4764fd9d874fb257ff588ac779a/greenlet-3.2.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1ee8fae0519a337f2329cb78bd7a8e128ec0f881073d43f023c7b8d4831d5246", size = 632472, upload-time = "2025-08-07T13:42:55.044Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ae/8f/95d48d7e3d433e6dae5b1682e4292242a53f22df82e6d3dda81b1701a960/greenlet-3.2.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:94abf90142c2a18151632371140b3dba4dee031633fe614cb592dbb6c9e17bc3", size = 644646, upload-time = "2025-08-07T13:45:26.523Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d5/5e/405965351aef8c76b8ef7ad370e5da58d57ef6068df197548b015464001a/greenlet-3.2.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:4d1378601b85e2e5171b99be8d2dc85f594c79967599328f95c1dc1a40f1c633", size = 640519, upload-time = "2025-08-07T13:53:13.928Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/25/5d/382753b52006ce0218297ec1b628e048c4e64b155379331f25a7316eb749/greenlet-3.2.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0db5594dce18db94f7d1650d7489909b57afde4c580806b8d9203b6e79cdc079", size = 639707, upload-time = "2025-08-07T13:18:27.146Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1f/8e/abdd3f14d735b2929290a018ecf133c901be4874b858dd1c604b9319f064/greenlet-3.2.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2523e5246274f54fdadbce8494458a2ebdcdbc7b802318466ac5606d3cded1f8", size = 587684, upload-time = "2025-08-07T13:18:25.164Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5d/65/deb2a69c3e5996439b0176f6651e0052542bb6c8f8ec2e3fba97c9768805/greenlet-3.2.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1987de92fec508535687fb807a5cea1560f6196285a4cde35c100b8cd632cc52", size = 1116647, upload-time = "2025-08-07T13:42:38.655Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3f/cc/b07000438a29ac5cfb2194bfc128151d52f333cee74dd7dfe3fb733fc16c/greenlet-3.2.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:55e9c5affaa6775e2c6b67659f3a71684de4c549b3dd9afca3bc773533d284fa", size = 1142073, upload-time = "2025-08-07T13:18:21.737Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d8/0f/30aef242fcab550b0b3520b8e3561156857c94288f0332a79928c31a52cf/greenlet-3.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:9c40adce87eaa9ddb593ccb0fa6a07caf34015a29bf8d344811665b573138db9", size = 299100, upload-time = "2025-08-07T13:44:12.287Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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 = "groq" +version = "0.9.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/48/c8/5ea4aa6b329fd01795acdf4cb0c78d92860253d108eddfc008fccbe56642/groq-0.9.0.tar.gz", hash = "sha256:130ed5e35d3acfaab46b9e7a078eeaebf91052f4a9d71f86f87fb319b5fec332", size = 68728, upload-time = "2024-06-11T20:12:03.864Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/59/7c/81b1302925c2452d540c7d7784b316017b69e1f3f19c2996bcb09360437b/groq-0.9.0-py3-none-any.whl", hash = "sha256:d0e46f4ad645504672bb09c8100af3ced3a7db0d5119dc13e4aca535fc455874", size = 103457, upload-time = "2024-06-11T20:12:02.407Z" }, +] + +[[package]] +name = "grpc-google-iam-v1" +version = "0.14.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "googleapis-common-protos", extra = ["grpc"] }, + { name = "grpcio" }, + { name = "protobuf" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b9/4e/8d0ca3b035e41fe0b3f31ebbb638356af720335e5a11154c330169b40777/grpc_google_iam_v1-0.14.2.tar.gz", hash = "sha256:b3e1fc387a1a329e41672197d0ace9de22c78dd7d215048c4c78712073f7bd20", size = 16259, upload-time = "2025-03-17T11:40:23.586Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/66/6f/dd9b178aee7835b96c2e63715aba6516a9d50f6bebbd1cc1d32c82a2a6c3/grpc_google_iam_v1-0.14.2-py3-none-any.whl", hash = "sha256:a3171468459770907926d56a440b2bb643eec1d7ba215f48f3ecece42b4d8351", size = 19242, upload-time = "2025-03-17T11:40:22.648Z" }, +] + +[[package]] +name = "grpcio" +version = "1.74.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/38/b4/35feb8f7cab7239c5b94bd2db71abb3d6adb5f335ad8f131abb6060840b6/grpcio-1.74.0.tar.gz", hash = "sha256:80d1f4fbb35b0742d3e3d3bb654b7381cd5f015f8497279a1e9c21ba623e01b1", size = 12756048, upload-time = "2025-07-24T18:54:23.039Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/66/54/68e51a90797ad7afc5b0a7881426c337f6a9168ebab73c3210b76aa7c90d/grpcio-1.74.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:85bd5cdf4ed7b2d6438871adf6afff9af7096486fcf51818a81b77ef4dd30907", size = 5481935, upload-time = "2025-07-24T18:52:43.756Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/32/2a/af817c7e9843929e93e54d09c9aee2555c2e8d81b93102a9426b36e91833/grpcio-1.74.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:68c8ebcca945efff9d86d8d6d7bfb0841cf0071024417e2d7f45c5e46b5b08eb", size = 10986796, upload-time = "2025-07-24T18:52:47.219Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d5/94/d67756638d7bb07750b07d0826c68e414124574b53840ba1ff777abcd388/grpcio-1.74.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:e154d230dc1bbbd78ad2fdc3039fa50ad7ffcf438e4eb2fa30bce223a70c7486", size = 5983663, upload-time = "2025-07-24T18:52:49.463Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/35/f5/c5e4853bf42148fea8532d49e919426585b73eafcf379a712934652a8de9/grpcio-1.74.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8978003816c7b9eabe217f88c78bc26adc8f9304bf6a594b02e5a49b2ef9c11", size = 6653765, upload-time = "2025-07-24T18:52:51.094Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fd/75/a1991dd64b331d199935e096cc9daa3415ee5ccbe9f909aa48eded7bba34/grpcio-1.74.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3d7bd6e3929fd2ea7fbc3f562e4987229ead70c9ae5f01501a46701e08f1ad9", size = 6215172, upload-time = "2025-07-24T18:52:53.282Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/01/a4/7cef3dbb3b073d0ce34fd507efc44ac4c9442a0ef9fba4fb3f5c551efef5/grpcio-1.74.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:136b53c91ac1d02c8c24201bfdeb56f8b3ac3278668cbb8e0ba49c88069e1bdc", size = 6329142, upload-time = "2025-07-24T18:52:54.927Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bf/d3/587920f882b46e835ad96014087054655312400e2f1f1446419e5179a383/grpcio-1.74.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fe0f540750a13fd8e5da4b3eaba91a785eea8dca5ccd2bc2ffe978caa403090e", size = 7018632, upload-time = "2025-07-24T18:52:56.523Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1f/95/c70a3b15a0bc83334b507e3d2ae20ee8fa38d419b8758a4d838f5c2a7d32/grpcio-1.74.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4e4181bfc24413d1e3a37a0b7889bea68d973d4b45dd2bc68bb766c140718f82", size = 6509641, upload-time = "2025-07-24T18:52:58.495Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4b/06/2e7042d06247d668ae69ea6998eca33f475fd4e2855f94dcb2aa5daef334/grpcio-1.74.0-cp310-cp310-win32.whl", hash = "sha256:1733969040989f7acc3d94c22f55b4a9501a30f6aaacdbccfaba0a3ffb255ab7", size = 3817478, upload-time = "2025-07-24T18:53:00.128Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/93/20/e02b9dcca3ee91124060b65bbf5b8e1af80b3b76a30f694b44b964ab4d71/grpcio-1.74.0-cp310-cp310-win_amd64.whl", hash = "sha256:9e912d3c993a29df6c627459af58975b2e5c897d93287939b9d5065f000249b5", size = 4493971, upload-time = "2025-07-24T18:53:02.068Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e7/77/b2f06db9f240a5abeddd23a0e49eae2b6ac54d85f0e5267784ce02269c3b/grpcio-1.74.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:69e1a8180868a2576f02356565f16635b99088da7df3d45aaa7e24e73a054e31", size = 5487368, upload-time = "2025-07-24T18:53:03.548Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/48/99/0ac8678a819c28d9a370a663007581744a9f2a844e32f0fa95e1ddda5b9e/grpcio-1.74.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:8efe72fde5500f47aca1ef59495cb59c885afe04ac89dd11d810f2de87d935d4", size = 10999804, upload-time = "2025-07-24T18:53:05.095Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/45/c6/a2d586300d9e14ad72e8dc211c7aecb45fe9846a51e558c5bca0c9102c7f/grpcio-1.74.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:a8f0302f9ac4e9923f98d8e243939a6fb627cd048f5cd38595c97e38020dffce", size = 5987667, upload-time = "2025-07-24T18:53:07.157Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c9/57/5f338bf56a7f22584e68d669632e521f0de460bb3749d54533fc3d0fca4f/grpcio-1.74.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f609a39f62a6f6f05c7512746798282546358a37ea93c1fcbadf8b2fed162e3", size = 6655612, upload-time = "2025-07-24T18:53:09.244Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/82/ea/a4820c4c44c8b35b1903a6c72a5bdccec92d0840cf5c858c498c66786ba5/grpcio-1.74.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c98e0b7434a7fa4e3e63f250456eaef52499fba5ae661c58cc5b5477d11e7182", size = 6219544, upload-time = "2025-07-24T18:53:11.221Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a4/17/0537630a921365928f5abb6d14c79ba4dcb3e662e0dbeede8af4138d9dcf/grpcio-1.74.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:662456c4513e298db6d7bd9c3b8df6f75f8752f0ba01fb653e252ed4a59b5a5d", size = 6334863, upload-time = "2025-07-24T18:53:12.925Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e2/a6/85ca6cb9af3f13e1320d0a806658dca432ff88149d5972df1f7b51e87127/grpcio-1.74.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3d14e3c4d65e19d8430a4e28ceb71ace4728776fd6c3ce34016947474479683f", size = 7019320, upload-time = "2025-07-24T18:53:15.002Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4f/a7/fe2beab970a1e25d2eff108b3cf4f7d9a53c185106377a3d1989216eba45/grpcio-1.74.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1bf949792cee20d2078323a9b02bacbbae002b9e3b9e2433f2741c15bdeba1c4", size = 6514228, upload-time = "2025-07-24T18:53:16.999Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6a/c2/2f9c945c8a248cebc3ccda1b7a1bf1775b9d7d59e444dbb18c0014e23da6/grpcio-1.74.0-cp311-cp311-win32.whl", hash = "sha256:55b453812fa7c7ce2f5c88be3018fb4a490519b6ce80788d5913f3f9d7da8c7b", size = 3817216, upload-time = "2025-07-24T18:53:20.564Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ff/d1/a9cf9c94b55becda2199299a12b9feef0c79946b0d9d34c989de6d12d05d/grpcio-1.74.0-cp311-cp311-win_amd64.whl", hash = "sha256:86ad489db097141a907c559988c29718719aa3e13370d40e20506f11b4de0d11", size = 4495380, upload-time = "2025-07-24T18:53:22.058Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4c/5d/e504d5d5c4469823504f65687d6c8fb97b7f7bf0b34873b7598f1df24630/grpcio-1.74.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:8533e6e9c5bd630ca98062e3a1326249e6ada07d05acf191a77bc33f8948f3d8", size = 5445551, upload-time = "2025-07-24T18:53:23.641Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/43/01/730e37056f96f2f6ce9f17999af1556df62ee8dab7fa48bceeaab5fd3008/grpcio-1.74.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:2918948864fec2a11721d91568effffbe0a02b23ecd57f281391d986847982f6", size = 10979810, upload-time = "2025-07-24T18:53:25.349Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/79/3d/09fd100473ea5c47083889ca47ffd356576173ec134312f6aa0e13111dee/grpcio-1.74.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:60d2d48b0580e70d2e1954d0d19fa3c2e60dd7cbed826aca104fff518310d1c5", size = 5941946, upload-time = "2025-07-24T18:53:27.387Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8a/99/12d2cca0a63c874c6d3d195629dcd85cdf5d6f98a30d8db44271f8a97b93/grpcio-1.74.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3601274bc0523f6dc07666c0e01682c94472402ac2fd1226fd96e079863bfa49", size = 6621763, upload-time = "2025-07-24T18:53:29.193Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9d/2c/930b0e7a2f1029bbc193443c7bc4dc2a46fedb0203c8793dcd97081f1520/grpcio-1.74.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:176d60a5168d7948539def20b2a3adcce67d72454d9ae05969a2e73f3a0feee7", size = 6180664, upload-time = "2025-07-24T18:53:30.823Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/db/d5/ff8a2442180ad0867717e670f5ec42bfd8d38b92158ad6bcd864e6d4b1ed/grpcio-1.74.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e759f9e8bc908aaae0412642afe5416c9f983a80499448fcc7fab8692ae044c3", size = 6301083, upload-time = "2025-07-24T18:53:32.454Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b0/ba/b361d390451a37ca118e4ec7dccec690422e05bc85fba2ec72b06cefec9f/grpcio-1.74.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:9e7c4389771855a92934b2846bd807fc25a3dfa820fd912fe6bd8136026b2707", size = 6994132, upload-time = "2025-07-24T18:53:34.506Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3b/0c/3a5fa47d2437a44ced74141795ac0251bbddeae74bf81df3447edd767d27/grpcio-1.74.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cce634b10aeab37010449124814b05a62fb5f18928ca878f1bf4750d1f0c815b", size = 6489616, upload-time = "2025-07-24T18:53:36.217Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ae/95/ab64703b436d99dc5217228babc76047d60e9ad14df129e307b5fec81fd0/grpcio-1.74.0-cp312-cp312-win32.whl", hash = "sha256:885912559974df35d92219e2dc98f51a16a48395f37b92865ad45186f294096c", size = 3807083, upload-time = "2025-07-24T18:53:37.911Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/84/59/900aa2445891fc47a33f7d2f76e00ca5d6ae6584b20d19af9c06fa09bf9a/grpcio-1.74.0-cp312-cp312-win_amd64.whl", hash = "sha256:42f8fee287427b94be63d916c90399ed310ed10aadbf9e2e5538b3e497d269bc", size = 4490123, upload-time = "2025-07-24T18:53:39.528Z" }, +] + +[[package]] +name = "grpcio-status" +version = "1.71.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "googleapis-common-protos" }, + { name = "grpcio" }, + { name = "protobuf" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fd/d1/b6e9877fedae3add1afdeae1f89d1927d296da9cf977eca0eb08fb8a460e/grpcio_status-1.71.2.tar.gz", hash = "sha256:c7a97e176df71cdc2c179cd1847d7fc86cca5832ad12e9798d7fed6b7a1aab50", size = 13677, upload-time = "2025-06-28T04:24:05.426Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/67/58/317b0134129b556a93a3b0afe00ee675b5657f0155509e22fcb853bafe2d/grpcio_status-1.71.2-py3-none-any.whl", hash = "sha256:803c98cb6a8b7dc6dbb785b1111aed739f241ab5e9da0bba96888aa74704cfd3", size = 14424, upload-time = "2025-06-28T04:23:42.136Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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.2.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "hpack" }, + { name = "hyperframe" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1b/38/d7f80fd13e6582fb8e0df8c9a653dcc02b03ca34f4d72f34869298c5baf8/h2-4.2.0.tar.gz", hash = "sha256:c8a52129695e88b1a0578d8d2cc6842bbd79128ac685463b887ee278126ad01f", size = 2150682, upload-time = "2025-02-02T07:43:51.815Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d0/9e/984486f2d0a0bd2b024bf4bc1c62688fcafa9e61991f041fb0e2def4a982/h2-4.2.0-py3-none-any.whl", hash = "sha256:479a53ad425bb29af087f3458a61d30780bc818e4ebcf01f0b536ba916462ed0", size = 60957, upload-time = "2025-02-01T11:02:26.481Z" }, +] + +[[package]] +name = "hanziconv" +version = "0.3.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/63/71/b89cb63077fd807fe31cf7c016a06e7e579a289d8a37aa24a30282d02dd2/hanziconv-0.3.2.tar.gz", hash = "sha256:208866da6ae305bca19eb98702b65c93bb3a803b496e4287ca740d68892fc4c4", size = 276775, upload-time = "2016-09-01T05:41:15.254Z" } + +[[package]] +name = "hf-transfer" +version = "0.1.9" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1a/eb/8fc64f40388c29ce8ce3b2b180a089d4d6b25b1d0d232d016704cb852104/hf_transfer-0.1.9.tar.gz", hash = "sha256:035572865dab29d17e783fbf1e84cf1cb24f3fcf8f1b17db1cfc7fdf139f02bf", size = 25201, upload-time = "2025-01-07T10:05:12.947Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/81/f5/461d2e5f307e5048289b1168d5c642ae3bb2504e88dff1a38b92ed990a21/hf_transfer-0.1.9-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:e66acf91df4a8b72f60223059df3003062a5ae111757187ed1a06750a30e911b", size = 1393046, upload-time = "2025-01-07T10:04:51.003Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/41/ba/8d9fd9f1083525edfcb389c93738c802f3559cb749324090d7109c8bf4c2/hf_transfer-0.1.9-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:8669dbcc7a3e2e8d61d42cd24da9c50d57770bd74b445c65123291ca842a7e7a", size = 1348126, upload-time = "2025-01-07T10:04:45.712Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8e/a2/cd7885bc9959421065a6fae0fe67b6c55becdeda4e69b873e52976f9a9f0/hf_transfer-0.1.9-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8fd0167c4407a3bc4cdd0307e65ada2294ec04f1813d8a69a5243e379b22e9d8", size = 3728604, upload-time = "2025-01-07T10:04:14.173Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f6/2e/a072cf196edfeda3310c9a5ade0a0fdd785e6154b3ce24fc738c818da2a7/hf_transfer-0.1.9-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ee8b10afedcb75f71091bcc197c526a6ebf5c58bbbadb34fdeee6160f55f619f", size = 3064995, upload-time = "2025-01-07T10:04:18.663Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c2/84/aec9ef4c0fab93c1ea2b1badff38c78b4b2f86f0555b26d2051dbc920cde/hf_transfer-0.1.9-cp38-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5828057e313de59300dd1abb489444bc452efe3f479d3c55b31a8f680936ba42", size = 3580908, upload-time = "2025-01-07T10:04:32.834Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/29/63/b560d39651a56603d64f1a0212d0472a44cbd965db2fa62b99d99cb981bf/hf_transfer-0.1.9-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc6bd19e1cc177c66bdef15ef8636ad3bde79d5a4f608c158021153b4573509d", size = 3400839, upload-time = "2025-01-07T10:04:26.122Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d6/d8/f87ea6f42456254b48915970ed98e993110521e9263472840174d32c880d/hf_transfer-0.1.9-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdca9bfb89e6f8f281890cc61a8aff2d3cecaff7e1a4d275574d96ca70098557", size = 3552664, upload-time = "2025-01-07T10:04:40.123Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d6/56/1267c39b65fc8f4e2113b36297320f102718bf5799b544a6cbe22013aa1d/hf_transfer-0.1.9-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:89a23f58b7b7effbc047b8ca286f131b17728c99a9f972723323003ffd1bb916", size = 4073732, upload-time = "2025-01-07T10:04:55.624Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/82/1a/9c748befbe3decf7cb415e34f8a0c3789a0a9c55910dea73d581e48c0ce5/hf_transfer-0.1.9-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:dc7fff1345980d6c0ebb92c811d24afa4b98b3e07ed070c8e38cc91fd80478c5", size = 3390096, upload-time = "2025-01-07T10:04:59.98Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/72/85/4c03da147b6b4b7cb12e074d3d44eee28604a387ed0eaf7eaaead5069c57/hf_transfer-0.1.9-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:1a6bd16c667ebe89a069ca163060127a794fa3a3525292c900b8c8cc47985b0d", size = 3664743, upload-time = "2025-01-07T10:05:05.416Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e7/6e/e597b04f753f1b09e6893075d53a82a30c13855cbaa791402695b01e369f/hf_transfer-0.1.9-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d2fde99d502093ade3ab1b53f80da18480e9902aa960dab7f74fb1b9e5bc5746", size = 3695243, upload-time = "2025-01-07T10:05:11.411Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/09/89/d4e234727a26b2546c8fb70a276cd924260d60135f2165bf8b9ed67bb9a4/hf_transfer-0.1.9-cp38-abi3-win32.whl", hash = "sha256:435cc3cdc8524ce57b074032b8fd76eed70a4224d2091232fa6a8cef8fd6803e", size = 1086605, upload-time = "2025-01-07T10:05:18.873Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a1/14/f1e15b851d1c2af5b0b1a82bf8eb10bda2da62d98180220ba6fd8879bb5b/hf_transfer-0.1.9-cp38-abi3-win_amd64.whl", hash = "sha256:16f208fc678911c37e11aa7b586bc66a37d02e636208f18b6bc53d29b5df40ad", size = 1160240, upload-time = "2025-01-07T10:05:14.324Z" }, +] + +[[package]] +name = "hpack" +version = "4.1.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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 = "html-text" +version = "0.6.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "lxml" }, + { name = "lxml-html-clean" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5e/35/10ab103fec3b953ed1ba56ff827d00afc170dd204e0785c5fe7d3d6e1ae9/html_text-0.6.2.tar.gz", hash = "sha256:81455b4de5430cf63ce7c45a870fb8629e79ca8518e240f172d62409c2f2ff72", size = 53592, upload-time = "2024-05-01T11:55:13.92Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b8/bd/b28e8456b952268083642d631bec7d74e1564a54bfd2e6f6d13e597bbec0/html_text-0.6.2-py2.py3-none-any.whl", hash = "sha256:d83d619ccd4b4d6172e21084d8a46e29e49ce87a08cc02161e7ca8c2918e7bca", size = 7694, upload-time = "2024-05-01T11:55:12.315Z" }, +] + +[[package]] +name = "html2text" +version = "2024.2.26" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1a/43/e1d53588561e533212117750ee79ad0ba02a41f52a08c1df3396bd466c05/html2text-2024.2.26.tar.gz", hash = "sha256:05f8e367d15aaabc96415376776cdd11afd5127a77fce6e36afc60c563ca2c32", size = 56527, upload-time = "2024-02-27T18:49:24.855Z" } + +[[package]] +name = "html5lib" +version = "1.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "six" }, + { name = "webencodings" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ac/b6/b55c3f49042f1df3dcd422b7f224f939892ee94f22abcf503a9b7339eaf2/html5lib-1.1.tar.gz", hash = "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f", size = 272215, upload-time = "2020-06-22T23:32:38.834Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-py2.py3-none-any.whl", hash = "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d", size = 112173, upload-time = "2020-06-22T23:32:36.781Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httplib2" +version = "0.22.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "pyparsing" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3d/ad/2371116b22d616c194aa25ec410c9c6c37f23599dcd590502b74db197584/httplib2-0.22.0.tar.gz", hash = "sha256:d7a10bc5ef5ab08322488bde8c726eeee5c8618723fdb399597ec58f3d82df81", size = 351116, upload-time = "2023-03-21T22:29:37.214Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a8/6c/d2fbdaaa5959339d53ba38e94c123e4e84b8fbc4b84beb0e70d7c1608486/httplib2-0.22.0-py3-none-any.whl", hash = "sha256:14ae0a53c1ba8f3d37e9e27cf37eabb0fb9980f435ba405d546948b009dd64dc", size = 96854, upload-time = "2023-03-21T22:29:35.683Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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] +socks = [ + { name = "socksio" }, +] + +[[package]] +name = "httpx-sse" +version = "0.4.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6e/fa/66bd985dd0b7c109a3bcb89272ee0bfb7e2b4d06309ad7b38ff866734b2a/httpx_sse-0.4.1.tar.gz", hash = "sha256:8f44d34414bc7b21bf3602713005c5df4917884f76072479b21f68befa4ea26e", size = 12998, upload-time = "2025-06-24T13:21:05.71Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/25/0a/6269e3473b09aed2dab8aa1a600c70f31f00ae1349bee30658f7e358a159/httpx_sse-0.4.1-py3-none-any.whl", hash = "sha256:cba42174344c3a5b06f255ce65b350880f962d99ead85e776f23c6618a377a37", size = 8054, upload-time = "2025-06-24T13:21:04.772Z" }, +] + +[[package]] +name = "huggingface-hub" +version = "0.25.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "filelock" }, + { name = "fsspec" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/df/fd/5f81bae67096c5ab50d29a0230b8374f0245916cca192f8ee2fada51f4f6/huggingface_hub-0.25.2.tar.gz", hash = "sha256:a1014ea111a5f40ccd23f7f7ba8ac46e20fa3b658ced1f86a00c75c06ec6423c", size = 365806, upload-time = "2024-10-09T08:32:41.565Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/64/09/a535946bf2dc88e61341f39dc507530411bb3ea4eac493e5ec833e8f35bd/huggingface_hub-0.25.2-py3-none-any.whl", hash = "sha256:1897caf88ce7f97fe0110603d8f66ac264e3ba6accdf30cd66cc0fed5282ad25", size = 436575, upload-time = "2024-10-09T08:32:39.166Z" }, +] + +[[package]] +name = "humanfriendly" +version = "10.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "pyreadline3", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cc/3f/2c29224acb2e2df4d2046e4c73ee2662023c58ff5b113c4c1adac0886c43/humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc", size = 360702, upload-time = "2021-09-17T21:40:43.31Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f0/0f/310fb31e39e2d734ccaa2c0fb981ee41f7bd5056ce9bc29b2248bd569169/humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477", size = 86794, upload-time = "2021-09-17T21:40:39.897Z" }, +] + +[[package]] +name = "hyperframe" +version = "6.1.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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 = "hypothesis" +version = "6.136.6" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "attrs" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "sortedcontainers" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e2/d6/6c45c56cdc532f25a354cf2c93a3bebc21b91b6a48125e3acfd364161447/hypothesis-6.136.6.tar.gz", hash = "sha256:2ad2e4f2012be4d41c6515b0628d84d48af6e6c38b4db50840bd9ac0899f5856", size = 458049, upload-time = "2025-07-28T09:48:02.495Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e9/e2/7fd1a9b12740b3472349f7e3c216e94b1b2e03d32c9d842284b57eb5a3f8/hypothesis-6.136.6-py3-none-any.whl", hash = "sha256:1d6296dde36d42263bd44a084c74e91467e78186676e410167f920aa0374a9e7", size = 524958, upload-time = "2025-07-28T09:47:58.938Z" }, +] + +[[package]] +name = "hyppo" +version = "0.4.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "autograd" }, + { name = "numba" }, + { name = "numpy" }, + { name = "scikit-learn" }, + { name = "scipy" }, +] +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/34/87/7940713f929d0280cff1bde207479cb588a0d3a4dd49a0e2e69bfff46363/hyppo-0.4.0-py3-none-any.whl", hash = "sha256:4e75565b8deb601485cd7bc1b5c3f44e6ddf329136fc81e65d011f9b4e95132f", size = 146607, upload-time = "2023-05-24T13:50:04.441Z" }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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 = "ijson" +version = "3.4.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a3/4f/1cfeada63f5fce87536651268ddf5cca79b8b4bbb457aee4e45777964a0a/ijson-3.4.0.tar.gz", hash = "sha256:5f74dcbad9d592c428d3ca3957f7115a42689ee7ee941458860900236ae9bb13", size = 65782, upload-time = "2025-05-08T02:37:20.135Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/eb/6b/a247ba44004154aaa71f9e6bd9f05ba412f490cc4043618efb29314f035e/ijson-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e27e50f6dcdee648f704abc5d31b976cd2f90b4642ed447cf03296d138433d09", size = 87609, upload-time = "2025-05-08T02:35:20.535Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3c/1d/8d2009d74373b7dec2a49b1167e396debb896501396c70a674bb9ccc41ff/ijson-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2a753be681ac930740a4af9c93cfb4edc49a167faed48061ea650dc5b0f406f1", size = 59243, upload-time = "2025-05-08T02:35:21.958Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a7/b2/a85a21ebaba81f64a326c303a94625fb94b84890c52d9efdd8acb38b6312/ijson-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a07c47aed534e0ec198e6a2d4360b259d32ac654af59c015afc517ad7973b7fb", size = 59309, upload-time = "2025-05-08T02:35:23.317Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b1/35/273dfa1f27c38eeaba105496ecb54532199f76c0120177b28315daf5aec3/ijson-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c55f48181e11c597cd7146fb31edc8058391201ead69f8f40d2ecbb0b3e4fc6", size = 131213, upload-time = "2025-05-08T02:35:24.735Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4d/37/9d3bb0e200a103ca9f8e9315c4d96ecaca43a3c1957c1ac069ea9dc9c6ba/ijson-3.4.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd5669f96f79d8a2dd5ae81cbd06770a4d42c435fd4a75c74ef28d9913b697d", size = 125456, upload-time = "2025-05-08T02:35:25.896Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/00/54/8f015c4df30200fd14435dec9c67bf675dff0fee44a16c084a8ec0f82922/ijson-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e3ddd46d16b8542c63b1b8af7006c758d4e21cc1b86122c15f8530fae773461", size = 130192, upload-time = "2025-05-08T02:35:27.367Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/88/01/46a0540ad3461332edcc689a8874fa13f0a4c00f60f02d155b70e36f5e0b/ijson-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1504cec7fe04be2bb0cc33b50c9dd3f83f98c0540ad4991d4017373b7853cfe6", size = 132217, upload-time = "2025-05-08T02:35:28.545Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d7/da/8f8df42f3fd7ef279e20eae294738eed62d41ed5b6a4baca5121abc7cf0f/ijson-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:2f2ff456adeb216603e25d7915f10584c1b958b6eafa60038d76d08fc8a5fb06", size = 127118, upload-time = "2025-05-08T02:35:29.726Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/82/0a/a410d9d3b082cc2ec9738d54935a589974cbe54c0f358e4d17465594d660/ijson-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0ab00d75d61613a125fbbb524551658b1ad6919a52271ca16563ca5bc2737bb1", size = 129808, upload-time = "2025-05-08T02:35:31.247Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2e/c6/a3e2a446b8bd2cf91cb4ca7439f128d2b379b5a79794d0ea25e379b0f4f3/ijson-3.4.0-cp310-cp310-win32.whl", hash = "sha256:ada421fd59fe2bfa4cfa64ba39aeba3f0753696cdcd4d50396a85f38b1d12b01", size = 51160, upload-time = "2025-05-08T02:35:32.964Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/18/7c/e6620603df42d2ef8a92076eaa5cd2b905366e86e113adf49e7b79970bd3/ijson-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:8c75e82cec05d00ed3a4af5f4edf08f59d536ed1a86ac7e84044870872d82a33", size = 53710, upload-time = "2025-05-08T02:35:34.033Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1a/0d/3e2998f4d7b7d2db2d511e4f0cf9127b6e2140c325c3cb77be46ae46ff1d/ijson-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9e369bf5a173ca51846c243002ad8025d32032532523b06510881ecc8723ee54", size = 87643, upload-time = "2025-05-08T02:35:35.693Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e9/7b/afef2b08af2fee5ead65fcd972fadc3e31f9ae2b517fe2c378d50a9bf79b/ijson-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:26e7da0a3cd2a56a1fde1b34231867693f21c528b683856f6691e95f9f39caec", size = 59260, upload-time = "2025-05-08T02:35:37.166Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/da/4a/39f583a2a13096f5063028bb767622f09cafc9ec254c193deee6c80af59f/ijson-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1c28c7f604729be22aa453e604e9617b665fa0c24cd25f9f47a970e8130c571a", size = 59311, upload-time = "2025-05-08T02:35:38.538Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3c/58/5b80efd54b093e479c98d14b31d7794267281f6a8729f2c94fbfab661029/ijson-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bed8bcb84d3468940f97869da323ba09ae3e6b950df11dea9b62e2b231ca1e3", size = 136125, upload-time = "2025-05-08T02:35:39.976Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e5/f5/f37659b1647ecc3992216277cd8a45e2194e84e8818178f77c99e1d18463/ijson-3.4.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:296bc824f4088f2af814aaf973b0435bc887ce3d9f517b1577cc4e7d1afb1cb7", size = 130699, upload-time = "2025-05-08T02:35:41.483Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ee/2f/4c580ac4bb5eda059b672ad0a05e4bafdae5182a6ec6ab43546763dafa91/ijson-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8145f8f40617b6a8aa24e28559d0adc8b889e56a203725226a8a60fa3501073f", size = 134963, upload-time = "2025-05-08T02:35:43.017Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6d/9e/64ec39718609faab6ed6e1ceb44f9c35d71210ad9c87fff477c03503e8f8/ijson-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b674a97bd503ea21bc85103e06b6493b1b2a12da3372950f53e1c664566a33a4", size = 137405, upload-time = "2025-05-08T02:35:44.618Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/71/b2/f0bf0e4a0962845597996de6de59c0078bc03a1f899e03908220039f4cf6/ijson-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8bc731cf1c3282b021d3407a601a5a327613da9ad3c4cecb1123232623ae1826", size = 131861, upload-time = "2025-05-08T02:35:46.22Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/17/83/4a2e3611e2b4842b413ec84d2e54adea55ab52e4408ea0f1b1b927e19536/ijson-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:42ace5e940e0cf58c9de72f688d6829ddd815096d07927ee7e77df2648006365", size = 134297, upload-time = "2025-05-08T02:35:47.401Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/38/75/2d332911ac765b44cd7da0cb2b06143521ad5e31dfcc8d8587e6e6168bc8/ijson-3.4.0-cp311-cp311-win32.whl", hash = "sha256:5be39a0df4cd3f02b304382ea8885391900ac62e95888af47525a287c50005e9", size = 51161, upload-time = "2025-05-08T02:35:49.164Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7d/ba/4ad571f9f7fcf5906b26e757b130c1713c5f0198a1e59568f05d53a0816c/ijson-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:0b1be1781792291e70d2e177acf564ec672a7907ba74f313583bdf39fe81f9b7", size = 53710, upload-time = "2025-05-08T02:35:50.323Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f8/ec/317ee5b2d13e50448833ead3aa906659a32b376191f6abc2a7c6112d2b27/ijson-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:956b148f88259a80a9027ffbe2d91705fae0c004fbfba3e5a24028fbe72311a9", size = 87212, upload-time = "2025-05-08T02:35:51.835Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f8/43/b06c96ced30cacecc5d518f89b0fd1c98c294a30ff88848b70ed7b7f72a1/ijson-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:06b89960f5c721106394c7fba5760b3f67c515b8eb7d80f612388f5eca2f4621", size = 59175, upload-time = "2025-05-08T02:35:52.988Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e9/df/b4aeafb7ecde463130840ee9be36130823ec94a00525049bf700883378b8/ijson-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9a0bb591cf250dd7e9dfab69d634745a7f3272d31cfe879f9156e0a081fd97ee", size = 59011, upload-time = "2025-05-08T02:35:54.394Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e3/7c/a80b8e361641609507f62022089626d4b8067f0826f51e1c09e4ba86eba8/ijson-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:72e92de999977f4c6b660ffcf2b8d59604ccd531edcbfde05b642baf283e0de8", size = 146094, upload-time = "2025-05-08T02:35:55.601Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/01/44/fa416347b9a802e3646c6ff377fc3278bd7d6106e17beb339514b6a3184e/ijson-3.4.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e9602157a5b869d44b6896e64f502c712a312fcde044c2e586fccb85d3e316e", size = 137903, upload-time = "2025-05-08T02:35:56.814Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/24/c6/41a9ad4d42df50ff6e70fdce79b034f09b914802737ebbdc141153d8d791/ijson-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e83660edb931a425b7ff662eb49db1f10d30ca6d4d350e5630edbed098bc01", size = 148339, upload-time = "2025-05-08T02:35:58.595Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5f/6f/7d01efda415b8502dce67e067ed9e8a124f53e763002c02207e542e1a2f1/ijson-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:49bf8eac1c7b7913073865a859c215488461f7591b4fa6a33c14b51cb73659d0", size = 149383, upload-time = "2025-05-08T02:36:00.197Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/95/6c/0d67024b9ecb57916c5e5ab0350251c9fe2f86dc9c8ca2b605c194bdad6a/ijson-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:160b09273cb42019f1811469508b0a057d19f26434d44752bde6f281da6d3f32", size = 141580, upload-time = "2025-05-08T02:36:01.998Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/06/43/e10edcc1c6a3b619294de835e7678bfb3a1b8a75955f3689fd66a1e9e7b4/ijson-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2019ff4e6f354aa00c76c8591bd450899111c61f2354ad55cc127e2ce2492c44", size = 150280, upload-time = "2025-05-08T02:36:03.926Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/07/84/1cbeee8e8190a1ebe6926569a92cf1fa80ddb380c129beb6f86559e1bb24/ijson-3.4.0-cp312-cp312-win32.whl", hash = "sha256:931c007bf6bb8330705429989b2deed6838c22b63358a330bf362b6e458ba0bf", size = 51512, upload-time = "2025-05-08T02:36:05.595Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/66/13/530802bc391c95be6fe9f96e9aa427d94067e7c0b7da7a9092344dc44c4b/ijson-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:71523f2b64cb856a820223e94d23e88369f193017ecc789bb4de198cc9d349eb", size = 54081, upload-time = "2025-05-08T02:36:07.099Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a7/22/da919f16ca9254f8a9ea0ba482d2c1d012ce6e4c712dcafd8adb16b16c63/ijson-3.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:54e989c35dba9cf163d532c14bcf0c260897d5f465643f0cd1fba9c908bed7ef", size = 56480, upload-time = "2025-05-08T02:36:54.942Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6d/54/c2afd289e034d11c4909f4ea90c9dae55053bed358064f310c3dd5033657/ijson-3.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:494eeb8e87afef22fbb969a4cb81ac2c535f30406f334fb6136e9117b0bb5380", size = 55956, upload-time = "2025-05-08T02:36:56.178Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/43/d6/18799b0fca9ecb8a47e22527eedcea3267e95d4567b564ef21d0299e2d12/ijson-3.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81603de95de1688958af65cd2294881a4790edae7de540b70c65c8253c5dc44a", size = 69394, upload-time = "2025-05-08T02:36:57.699Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c2/d6/c58032c69e9e977bf6d954f22cad0cd52092db89c454ea98926744523665/ijson-3.4.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8524be12c1773e1be466034cc49c1ecbe3d5b47bb86217bd2a57f73f970a6c19", size = 70378, upload-time = "2025-05-08T02:36:58.98Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/da/03/07c6840454d5d228bb5b4509c9a7ac5b9c0b8258e2b317a53f97372be1eb/ijson-3.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17994696ec895d05e0cfa21b11c68c920c82634b4a3d8b8a1455d6fe9fdee8f7", size = 67770, upload-time = "2025-05-08T02:37:00.162Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/32/c7/da58a9840380308df574dfdb0276c9d802b12f6125f999e92bcef36db552/ijson-3.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0b67727aaee55d43b2e82b6a866c3cbcb2b66a5e9894212190cbd8773d0d9857", size = 53858, upload-time = "2025-05-08T02:37:01.691Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a3/9b/0bc0594d357600c03c3b5a3a34043d764fc3ad3f0757d2f3aae5b28f6c1c/ijson-3.4.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cdc8c5ca0eec789ed99db29c68012dda05027af0860bb360afd28d825238d69d", size = 56483, upload-time = "2025-05-08T02:37:03.274Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/00/1f/506cf2574673da1adcc8a794ebb85bf857cabe6294523978637e646814de/ijson-3.4.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:8e6b44b6ec45d5b1a0ee9d97e0e65ab7f62258727004cbbe202bf5f198bc21f7", size = 55957, upload-time = "2025-05-08T02:37:04.865Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/dc/3d/a7cd8d8a6de0f3084fe4d457a8f76176e11b013867d1cad16c67d25e8bec/ijson-3.4.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b51e239e4cb537929796e840d349fc731fdc0d58b1a0683ce5465ad725321e0f", size = 69394, upload-time = "2025-05-08T02:37:06.142Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/32/51/aa30abc02aabfc41c95887acf5f1f88da569642d7197fbe5aa105545226d/ijson-3.4.0-pp311-pypy311_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed05d43ec02be8ddb1ab59579761f6656b25d241a77fd74f4f0f7ec09074318a", size = 70377, upload-time = "2025-05-08T02:37:07.353Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c7/37/7773659b8d8d98b34234e1237352f6b446a3c12941619686c7d4a8a5c69c/ijson-3.4.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cfeca1aaa59d93fd0a3718cbe5f7ef0effff85cf837e0bceb71831a47f39cc14", size = 67767, upload-time = "2025-05-08T02:37:08.587Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cd/1f/dd52a84ed140e31a5d226cd47d98d21aa559aead35ef7bae479eab4c494c/ijson-3.4.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:7ca72ca12e9a1dd4252c97d952be34282907f263f7e28fcdff3a01b83981e837", size = 53864, upload-time = "2025-05-08T02:37:10.044Z" }, +] + +[[package]] +name = "imagesize" +version = "1.4.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a7/84/62473fb57d61e31fef6e36d64a179c8781605429fd927b5dd608c997be31/imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a", size = 1280026, upload-time = "2022-07-01T12:21:05.687Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769, upload-time = "2022-07-01T12:21:02.467Z" }, +] + +[[package]] +name = "imapclient" +version = "2.3.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/19/d8/a4a0337d5e39a0569d89793d5053d7535eefd9b8756df4e10dc114caf3c2/IMAPClient-2.3.1.zip", hash = "sha256:26ea995664fae3a88b878ebce2aff7402931697b86658b7882043ddb01b0e6ba" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/13/9c/b2890e73bc9eee53fe63218e3f3cb774a6beefdb7b5c47928a81cc3b3c13/IMAPClient-2.3.1-py2.py3-none-any.whl", hash = "sha256:057f28025d2987c63e065afb0e4370b0b850b539b0e1494cea0427e88130108c" }, +] + +[[package]] +name = "importlib-metadata" +version = "8.7.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "zipp" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641, upload-time = "2025-04-27T15:29:01.736Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656, upload-time = "2025-04-27T15:29:00.214Z" }, +] + +[[package]] +name = "infinity-emb" +version = "0.0.66" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "hf-transfer" }, + { name = "huggingface-hub" }, + { name = "numpy" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/25/f7/aa95638ba1aa5cc4ecdc9ac62a8db8715bf7975e17b4471cb86c7c7c1f56/infinity_emb-0.0.66.tar.gz", hash = "sha256:9c9a361ccebf8e8f626c1f685286518d03d0c35e7d14179ae7c2500b4fc68b98", size = 73314, upload-time = "2024-10-19T08:28:41.714Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6b/3a/a16d04f2af44295d60d4263455a136682816d03acd85a2f190051cc70288/infinity_emb-0.0.66-py3-none-any.whl", hash = "sha256:1dc6ed9fa48e6cbe83650a7583dbbb4bc393900c39c326bb0aff2ddc090ac018", size = 89176, upload-time = "2024-10-19T08:28:40.079Z" }, +] + +[[package]] +name = "infinity-sdk" +version = "0.6.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "numpy" }, + { name = "openpyxl" }, + { name = "pandas" }, + { name = "polars-lts-cpu" }, + { name = "pyarrow" }, + { name = "pydantic" }, + { name = "pytest" }, + { name = "readerwriterlock" }, + { name = "requests" }, + { name = "setuptools" }, + { name = "sqlglot", extra = ["rs"] }, + { name = "thrift" }, +] +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/44/0e/7a596a41a79d15bb6c87e76862aa287bb98243a40fa7a31096b57df01613/infinity_sdk-0.6.1-py3-none-any.whl", hash = "sha256:b9cb1f7fee28569de8b763c353aa299fa141af70e67ceadc70562c84237229e4", size = 75260, upload-time = "2025-10-21T13:11:06.265Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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 = "inscriptis" +version = "2.6.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "lxml" }, + { name = "requests" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/23/f0/77b6eeaa0cde44b8ef0d5d0c2c0ee7ddeb6e6e4aebec33e15b8d0abbf3ed/inscriptis-2.6.0.tar.gz", hash = "sha256:6f164bf45ea6972d61fd048a8e074d5125d215eaa837f8e70c158c97c31c3181", size = 41620, upload-time = "2025-03-22T18:23:26.246Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5e/c4/e1a68d42fa4609231da5f14e159c3d6256fb9e2951928592cebf7c899379/inscriptis-2.6.0-py3-none-any.whl", hash = "sha256:654dbcd0551c2f6004f8069a05cafff3eed2d327d5057adc6e657ba2610f52af", size = 45120, upload-time = "2025-03-22T18:23:24.404Z" }, +] + +[[package]] +name = "ir-datasets" +version = "0.5.11" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "ijson" }, + { name = "inscriptis" }, + { name = "lxml" }, + { name = "lz4" }, + { name = "numpy" }, + { name = "pyarrow" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "tqdm" }, + { name = "trec-car-tools" }, + { name = "unlzw3" }, + { name = "warc3-wet" }, + { name = "warc3-wet-clueweb09" }, + { name = "zlib-state" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/80/c6/f02811c51fec845ee87a10bb3675516a2d71935b203e5ddb80b7eb59b1da/ir_datasets-0.5.11.tar.gz", hash = "sha256:06c90af634ae5063c813286b35065debca1a974d26e136403d899f3ecd7ad463", size = 758463, upload-time = "2025-06-24T07:58:31.375Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/81/38/73fa582d6997362d9b4901b2a8ba1177b2d2896aa59ab8d069a3884e2e0d/ir_datasets-0.5.11-py3-none-any.whl", hash = "sha256:ae78549e5a7fa45e50462b7acb9f0765fc344fec6054108bf3dd063050555206", size = 866095, upload-time = "2025-06-24T07:58:29.958Z" }, +] + +[[package]] +name = "isodate" +version = "0.7.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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.1.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7f/a1/d3fb83e7a61fa0c0d3d08ad0a94ddbeff3731c05212617dff3a94e097f08/itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a", size = 56143, upload-time = "2022-03-24T15:12:15.102Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/68/5f/447e04e828f47465eeab35b5d408b7ebaaaee207f48b7136c5a7267a30ae/itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44", size = 15749, upload-time = "2022-03-24T15:12:13.2Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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.10.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ee/9d/ae7ddb4b8ab3fb1b51faf4deb36cb48a4fbbd7cb36bad6a5fca4741306f7/jiter-0.10.0.tar.gz", hash = "sha256:07a7142c38aacc85194391108dc91b5b57093c978a9932bd86a36862759d9500", size = 162759, upload-time = "2025-05-18T19:04:59.73Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/be/7e/4011b5c77bec97cb2b572f566220364e3e21b51c48c5bd9c4a9c26b41b67/jiter-0.10.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:cd2fb72b02478f06a900a5782de2ef47e0396b3e1f7d5aba30daeb1fce66f303", size = 317215, upload-time = "2025-05-18T19:03:04.303Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8a/4f/144c1b57c39692efc7ea7d8e247acf28e47d0912800b34d0ad815f6b2824/jiter-0.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:32bb468e3af278f095d3fa5b90314728a6916d89ba3d0ffb726dd9bf7367285e", size = 322814, upload-time = "2025-05-18T19:03:06.433Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/63/1f/db977336d332a9406c0b1f0b82be6f71f72526a806cbb2281baf201d38e3/jiter-0.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa8b3e0068c26ddedc7abc6fac37da2d0af16b921e288a5a613f4b86f050354f", size = 345237, upload-time = "2025-05-18T19:03:07.833Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d7/1c/aa30a4a775e8a672ad7f21532bdbfb269f0706b39c6ff14e1f86bdd9e5ff/jiter-0.10.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:286299b74cc49e25cd42eea19b72aa82c515d2f2ee12d11392c56d8701f52224", size = 370999, upload-time = "2025-05-18T19:03:09.338Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/35/df/f8257abc4207830cb18880781b5f5b716bad5b2a22fb4330cfd357407c5b/jiter-0.10.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6ed5649ceeaeffc28d87fb012d25a4cd356dcd53eff5acff1f0466b831dda2a7", size = 491109, upload-time = "2025-05-18T19:03:11.13Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/06/76/9e1516fd7b4278aa13a2cc7f159e56befbea9aa65c71586305e7afa8b0b3/jiter-0.10.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2ab0051160cb758a70716448908ef14ad476c3774bd03ddce075f3c1f90a3d6", size = 388608, upload-time = "2025-05-18T19:03:12.911Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6d/64/67750672b4354ca20ca18d3d1ccf2c62a072e8a2d452ac3cf8ced73571ef/jiter-0.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03997d2f37f6b67d2f5c475da4412be584e1cec273c1cfc03d642c46db43f8cf", size = 352454, upload-time = "2025-05-18T19:03:14.741Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/96/4d/5c4e36d48f169a54b53a305114be3efa2bbffd33b648cd1478a688f639c1/jiter-0.10.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c404a99352d839fed80d6afd6c1d66071f3bacaaa5c4268983fc10f769112e90", size = 391833, upload-time = "2025-05-18T19:03:16.426Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0b/de/ce4a6166a78810bd83763d2fa13f85f73cbd3743a325469a4a9289af6dae/jiter-0.10.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:66e989410b6666d3ddb27a74c7e50d0829704ede652fd4c858e91f8d64b403d0", size = 523646, upload-time = "2025-05-18T19:03:17.704Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a2/a6/3bc9acce53466972964cf4ad85efecb94f9244539ab6da1107f7aed82934/jiter-0.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b532d3af9ef4f6374609a3bcb5e05a1951d3bf6190dc6b176fdb277c9bbf15ee", size = 514735, upload-time = "2025-05-18T19:03:19.44Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b4/d8/243c2ab8426a2a4dea85ba2a2ba43df379ccece2145320dfd4799b9633c5/jiter-0.10.0-cp310-cp310-win32.whl", hash = "sha256:da9be20b333970e28b72edc4dff63d4fec3398e05770fb3205f7fb460eb48dd4", size = 210747, upload-time = "2025-05-18T19:03:21.184Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/37/7a/8021bd615ef7788b98fc76ff533eaac846322c170e93cbffa01979197a45/jiter-0.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:f59e533afed0c5b0ac3eba20d2548c4a550336d8282ee69eb07b37ea526ee4e5", size = 207484, upload-time = "2025-05-18T19:03:23.046Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1b/dd/6cefc6bd68b1c3c979cecfa7029ab582b57690a31cd2f346c4d0ce7951b6/jiter-0.10.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:3bebe0c558e19902c96e99217e0b8e8b17d570906e72ed8a87170bc290b1e978", size = 317473, upload-time = "2025-05-18T19:03:25.942Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/be/cf/fc33f5159ce132be1d8dd57251a1ec7a631c7df4bd11e1cd198308c6ae32/jiter-0.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:558cc7e44fd8e507a236bee6a02fa17199ba752874400a0ca6cd6e2196cdb7dc", size = 321971, upload-time = "2025-05-18T19:03:27.255Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/68/a4/da3f150cf1d51f6c472616fb7650429c7ce053e0c962b41b68557fdf6379/jiter-0.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d613e4b379a07d7c8453c5712ce7014e86c6ac93d990a0b8e7377e18505e98d", size = 345574, upload-time = "2025-05-18T19:03:28.63Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/84/34/6e8d412e60ff06b186040e77da5f83bc158e9735759fcae65b37d681f28b/jiter-0.10.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f62cf8ba0618eda841b9bf61797f21c5ebd15a7a1e19daab76e4e4b498d515b2", size = 371028, upload-time = "2025-05-18T19:03:30.292Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fb/d9/9ee86173aae4576c35a2f50ae930d2ccb4c4c236f6cb9353267aa1d626b7/jiter-0.10.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:919d139cdfa8ae8945112398511cb7fca58a77382617d279556b344867a37e61", size = 491083, upload-time = "2025-05-18T19:03:31.654Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d9/2c/f955de55e74771493ac9e188b0f731524c6a995dffdcb8c255b89c6fb74b/jiter-0.10.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13ddbc6ae311175a3b03bd8994881bc4635c923754932918e18da841632349db", size = 388821, upload-time = "2025-05-18T19:03:33.184Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/81/5a/0e73541b6edd3f4aada586c24e50626c7815c561a7ba337d6a7eb0a915b4/jiter-0.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c440ea003ad10927a30521a9062ce10b5479592e8a70da27f21eeb457b4a9c5", size = 352174, upload-time = "2025-05-18T19:03:34.965Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1c/c0/61eeec33b8c75b31cae42be14d44f9e6fe3ac15a4e58010256ac3abf3638/jiter-0.10.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dc347c87944983481e138dea467c0551080c86b9d21de6ea9306efb12ca8f606", size = 391869, upload-time = "2025-05-18T19:03:36.436Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/41/22/5beb5ee4ad4ef7d86f5ea5b4509f680a20706c4a7659e74344777efb7739/jiter-0.10.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:13252b58c1f4d8c5b63ab103c03d909e8e1e7842d302473f482915d95fefd605", size = 523741, upload-time = "2025-05-18T19:03:38.168Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ea/10/768e8818538e5817c637b0df52e54366ec4cebc3346108a4457ea7a98f32/jiter-0.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7d1bbf3c465de4a24ab12fb7766a0003f6f9bce48b8b6a886158c4d569452dc5", size = 514527, upload-time = "2025-05-18T19:03:39.577Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/73/6d/29b7c2dc76ce93cbedabfd842fc9096d01a0550c52692dfc33d3cc889815/jiter-0.10.0-cp311-cp311-win32.whl", hash = "sha256:db16e4848b7e826edca4ccdd5b145939758dadf0dc06e7007ad0e9cfb5928ae7", size = 210765, upload-time = "2025-05-18T19:03:41.271Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c2/c9/d394706deb4c660137caf13e33d05a031d734eb99c051142e039d8ceb794/jiter-0.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:9c9c1d5f10e18909e993f9641f12fe1c77b3e9b533ee94ffa970acc14ded3812", size = 209234, upload-time = "2025-05-18T19:03:42.918Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6d/b5/348b3313c58f5fbfb2194eb4d07e46a35748ba6e5b3b3046143f3040bafa/jiter-0.10.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1e274728e4a5345a6dde2d343c8da018b9d4bd4350f5a472fa91f66fda44911b", size = 312262, upload-time = "2025-05-18T19:03:44.637Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9c/4a/6a2397096162b21645162825f058d1709a02965606e537e3304b02742e9b/jiter-0.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7202ae396446c988cb2a5feb33a543ab2165b786ac97f53b59aafb803fef0744", size = 320124, upload-time = "2025-05-18T19:03:46.341Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2a/85/1ce02cade7516b726dd88f59a4ee46914bf79d1676d1228ef2002ed2f1c9/jiter-0.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23ba7722d6748b6920ed02a8f1726fb4b33e0fd2f3f621816a8b486c66410ab2", size = 345330, upload-time = "2025-05-18T19:03:47.596Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/75/d0/bb6b4f209a77190ce10ea8d7e50bf3725fc16d3372d0a9f11985a2b23eff/jiter-0.10.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:371eab43c0a288537d30e1f0b193bc4eca90439fc08a022dd83e5e07500ed026", size = 369670, upload-time = "2025-05-18T19:03:49.334Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a0/f5/a61787da9b8847a601e6827fbc42ecb12be2c925ced3252c8ffcb56afcaf/jiter-0.10.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c675736059020365cebc845a820214765162728b51ab1e03a1b7b3abb70f74c", size = 489057, upload-time = "2025-05-18T19:03:50.66Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/12/e4/6f906272810a7b21406c760a53aadbe52e99ee070fc5c0cb191e316de30b/jiter-0.10.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0c5867d40ab716e4684858e4887489685968a47e3ba222e44cde6e4a2154f959", size = 389372, upload-time = "2025-05-18T19:03:51.98Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e2/ba/77013b0b8ba904bf3762f11e0129b8928bff7f978a81838dfcc958ad5728/jiter-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395bb9a26111b60141757d874d27fdea01b17e8fac958b91c20128ba8f4acc8a", size = 352038, upload-time = "2025-05-18T19:03:53.703Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/67/27/c62568e3ccb03368dbcc44a1ef3a423cb86778a4389e995125d3d1aaa0a4/jiter-0.10.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6842184aed5cdb07e0c7e20e5bdcfafe33515ee1741a6835353bb45fe5d1bd95", size = 391538, upload-time = "2025-05-18T19:03:55.046Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c0/72/0d6b7e31fc17a8fdce76164884edef0698ba556b8eb0af9546ae1a06b91d/jiter-0.10.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:62755d1bcea9876770d4df713d82606c8c1a3dca88ff39046b85a048566d56ea", size = 523557, upload-time = "2025-05-18T19:03:56.386Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2f/09/bc1661fbbcbeb6244bd2904ff3a06f340aa77a2b94e5a7373fd165960ea3/jiter-0.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:533efbce2cacec78d5ba73a41756beff8431dfa1694b6346ce7af3a12c42202b", size = 514202, upload-time = "2025-05-18T19:03:57.675Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1b/84/5a5d5400e9d4d54b8004c9673bbe4403928a00d28529ff35b19e9d176b19/jiter-0.10.0-cp312-cp312-win32.whl", hash = "sha256:8be921f0cadd245e981b964dfbcd6fd4bc4e254cdc069490416dd7a2632ecc01", size = 211781, upload-time = "2025-05-18T19:03:59.025Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9b/52/7ec47455e26f2d6e5f2ea4951a0652c06e5b995c291f723973ae9e724a65/jiter-0.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:a7c7d785ae9dda68c2678532a5a1581347e9c15362ae9f6e68f3fdbfb64f2e49", size = 206176, upload-time = "2025-05-18T19:04:00.305Z" }, +] + +[[package]] +name = "jmespath" +version = "1.0.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/dc/fe/0f5a938c54105553436dbff7a61dc4fed4b1b2c98852f8833beaf4d5968f/joblib-1.5.1.tar.gz", hash = "sha256:f4f86e351f39fe3d0d32a9f2c3d8af1ee4cec285aafcb27003dda5205576b444", size = 330475, upload-time = "2025-05-23T12:04:37.097Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7d/4f/1195bbac8e0c2acc5f740661631d8d750dc38d4a32b23ee5df3cde6f4e0d/joblib-1.5.1-py3-none-any.whl", hash = "sha256:4719a31f054c7d766948dcd83e9613686b27114f190f717cec7eaa2084f8a74a", size = 307746, upload-time = "2025-05-23T12:04:35.124Z" }, +] + +[[package]] +name = "json-repair" +version = "0.35.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/95/9c/5ef83a13541c3444e0b949e88b3aa0f4e364e37acf4ffa9de476d36a3de0/json_repair-0.35.0.tar.gz", hash = "sha256:e70f834865a4ae5fe64352c23c1c16d3b70c5dd62dc544a169d8b0932bdbdcaa", size = 29053, upload-time = "2024-12-31T12:03:52.239Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4e/c7/9a63ff79f2f6350c0f5e504bad940386015381b4d171bd73077465a7dbbc/json_repair-0.35.0-py3-none-any.whl", hash = "sha256:1d429407158474d28a996e745b8f8f7dc78957cb2cfbc92120b9f580b5230a9e", size = 19908, upload-time = "2024-12-31T12:03:51.234Z" }, +] + +[[package]] +name = "jsonpath" +version = "0.82.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cf/a1/693351acd0a9edca4de9153372a65e75398898ea7f8a5c722ab00f464929/jsonpath-0.82.2.tar.gz", hash = "sha256:d87ef2bcbcded68ee96bc34c1809b69457ecec9b0c4dd471658a12bd391002d1" } + +[[package]] +name = "jsonschema" +version = "4.25.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d5/00/a297a868e9d0784450faa7365c2172a7d6110c763e30ba861867c32ae6a9/jsonschema-4.25.0.tar.gz", hash = "sha256:e63acf5c11762c0e6672ffb61482bdf57f0876684d8d249c0fe2d730d48bc55f", size = 356830, upload-time = "2025-07-18T15:39:45.11Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fe/54/c86cd8e011fe98803d7e382fd67c0df5ceab8d2b7ad8c5a81524f791551c/jsonschema-4.25.0-py3-none-any.whl", hash = "sha256:24c2e8da302de79c8b9382fee3e76b355e44d2a4364bb207159ce10b517bd716", size = 89184, upload-time = "2025-07-18T15:39:42.956Z" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.4.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bf/ce/46fbd9c8119cfc3581ee5643ea49464d168028cfb5caff5fc0596d0cf914/jsonschema_specifications-2025.4.1.tar.gz", hash = "sha256:630159c9f4dbea161a6a2205c3011cc4f18ff381b189fff48bb39b9bf26ae608", size = 15513, upload-time = "2025-04-23T12:34:07.418Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/01/0e/b27cdbaccf30b890c40ed1da9fd4a3593a5cf94dae54fb34f8a4b74fcd3f/jsonschema_specifications-2025.4.1-py3-none-any.whl", hash = "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af", size = 18437, upload-time = "2025-04-23T12:34:05.422Z" }, +] + +[[package]] +name = "kaitaistruct" +version = "0.10" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/54/04/dd60b9cb65d580ef6cb6eaee975ad1bdd22d46a3f51b07a1e0606710ea88/kaitaistruct-0.10.tar.gz", hash = "sha256:a044dee29173d6afbacf27bcac39daf89b654dd418cfa009ab82d9178a9ae52a", size = 7061, upload-time = "2022-07-09T00:34:06.729Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4e/bf/88ad23efc08708bda9a2647169828e3553bb2093a473801db61f75356395/kaitaistruct-0.10-py2.py3-none-any.whl", hash = "sha256:a97350919adbf37fda881f75e9365e2fb88d04832b7a4e57106ec70119efb235", size = 7013, upload-time = "2022-07-09T00:34:03.905Z" }, +] + +[[package]] +name = "kiwisolver" +version = "1.4.8" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/82/59/7c91426a8ac292e1cdd53a63b6d9439abd573c875c3f92c146767dd33faf/kiwisolver-1.4.8.tar.gz", hash = "sha256:23d5f023bdc8c7e54eb65f03ca5d5bb25b601eac4d7f1a042888a1f45237987e", size = 97538, upload-time = "2024-12-24T18:30:51.519Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/47/5f/4d8e9e852d98ecd26cdf8eaf7ed8bc33174033bba5e07001b289f07308fd/kiwisolver-1.4.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88c6f252f6816a73b1f8c904f7bbe02fd67c09a69f7cb8a0eecdbf5ce78e63db", size = 124623, upload-time = "2024-12-24T18:28:17.687Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1d/70/7f5af2a18a76fe92ea14675f8bd88ce53ee79e37900fa5f1a1d8e0b42998/kiwisolver-1.4.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c72941acb7b67138f35b879bbe85be0f6c6a70cab78fe3ef6db9c024d9223e5b", size = 66720, upload-time = "2024-12-24T18:28:19.158Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c6/13/e15f804a142353aefd089fadc8f1d985561a15358c97aca27b0979cb0785/kiwisolver-1.4.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce2cf1e5688edcb727fdf7cd1bbd0b6416758996826a8be1d958f91880d0809d", size = 65413, upload-time = "2024-12-24T18:28:20.064Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ce/6d/67d36c4d2054e83fb875c6b59d0809d5c530de8148846b1370475eeeece9/kiwisolver-1.4.8-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c8bf637892dc6e6aad2bc6d4d69d08764166e5e3f69d469e55427b6ac001b19d", size = 1650826, upload-time = "2024-12-24T18:28:21.203Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/de/c6/7b9bb8044e150d4d1558423a1568e4f227193662a02231064e3824f37e0a/kiwisolver-1.4.8-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:034d2c891f76bd3edbdb3ea11140d8510dca675443da7304205a2eaa45d8334c", size = 1628231, upload-time = "2024-12-24T18:28:23.851Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b6/38/ad10d437563063eaaedbe2c3540a71101fc7fb07a7e71f855e93ea4de605/kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d47b28d1dfe0793d5e96bce90835e17edf9a499b53969b03c6c47ea5985844c3", size = 1408938, upload-time = "2024-12-24T18:28:26.687Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/52/ce/c0106b3bd7f9e665c5f5bc1e07cc95b5dabd4e08e3dad42dbe2faad467e7/kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb158fe28ca0c29f2260cca8c43005329ad58452c36f0edf298204de32a9a3ed", size = 1422799, upload-time = "2024-12-24T18:28:30.538Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d0/87/efb704b1d75dc9758087ba374c0f23d3254505edaedd09cf9d247f7878b9/kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5536185fce131780ebd809f8e623bf4030ce1b161353166c49a3c74c287897f", size = 1354362, upload-time = "2024-12-24T18:28:32.943Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/eb/b3/fd760dc214ec9a8f208b99e42e8f0130ff4b384eca8b29dd0efc62052176/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:369b75d40abedc1da2c1f4de13f3482cb99e3237b38726710f4a793432b1c5ff", size = 2222695, upload-time = "2024-12-24T18:28:35.641Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a2/09/a27fb36cca3fc01700687cc45dae7a6a5f8eeb5f657b9f710f788748e10d/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:641f2ddf9358c80faa22e22eb4c9f54bd3f0e442e038728f500e3b978d00aa7d", size = 2370802, upload-time = "2024-12-24T18:28:38.357Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3d/c3/ba0a0346db35fe4dc1f2f2cf8b99362fbb922d7562e5f911f7ce7a7b60fa/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d561d2d8883e0819445cfe58d7ddd673e4015c3c57261d7bdcd3710d0d14005c", size = 2334646, upload-time = "2024-12-24T18:28:40.941Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/41/52/942cf69e562f5ed253ac67d5c92a693745f0bed3c81f49fc0cbebe4d6b00/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1732e065704b47c9afca7ffa272f845300a4eb959276bf6970dc07265e73b605", size = 2467260, upload-time = "2024-12-24T18:28:42.273Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/32/26/2d9668f30d8a494b0411d4d7d4ea1345ba12deb6a75274d58dd6ea01e951/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bcb1ebc3547619c3b58a39e2448af089ea2ef44b37988caf432447374941574e", size = 2288633, upload-time = "2024-12-24T18:28:44.87Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/98/99/0dd05071654aa44fe5d5e350729961e7bb535372935a45ac89a8924316e6/kiwisolver-1.4.8-cp310-cp310-win_amd64.whl", hash = "sha256:89c107041f7b27844179ea9c85d6da275aa55ecf28413e87624d033cf1f6b751", size = 71885, upload-time = "2024-12-24T18:28:47.346Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6c/fc/822e532262a97442989335394d441cd1d0448c2e46d26d3e04efca84df22/kiwisolver-1.4.8-cp310-cp310-win_arm64.whl", hash = "sha256:b5773efa2be9eb9fcf5415ea3ab70fc785d598729fd6057bea38d539ead28271", size = 65175, upload-time = "2024-12-24T18:28:49.651Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/da/ed/c913ee28936c371418cb167b128066ffb20bbf37771eecc2c97edf8a6e4c/kiwisolver-1.4.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a4d3601908c560bdf880f07d94f31d734afd1bb71e96585cace0e38ef44c6d84", size = 124635, upload-time = "2024-12-24T18:28:51.826Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4c/45/4a7f896f7467aaf5f56ef093d1f329346f3b594e77c6a3c327b2d415f521/kiwisolver-1.4.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:856b269c4d28a5c0d5e6c1955ec36ebfd1651ac00e1ce0afa3e28da95293b561", size = 66717, upload-time = "2024-12-24T18:28:54.256Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5f/b4/c12b3ac0852a3a68f94598d4c8d569f55361beef6159dce4e7b624160da2/kiwisolver-1.4.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c2b9a96e0f326205af81a15718a9073328df1173a2619a68553decb7097fd5d7", size = 65413, upload-time = "2024-12-24T18:28:55.184Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a9/98/1df4089b1ed23d83d410adfdc5947245c753bddfbe06541c4aae330e9e70/kiwisolver-1.4.8-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5020c83e8553f770cb3b5fc13faac40f17e0b205bd237aebd21d53d733adb03", size = 1343994, upload-time = "2024-12-24T18:28:57.493Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8d/bf/b4b169b050c8421a7c53ea1ea74e4ef9c335ee9013216c558a047f162d20/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dace81d28c787956bfbfbbfd72fdcef014f37d9b48830829e488fdb32b49d954", size = 1434804, upload-time = "2024-12-24T18:29:00.077Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/66/5a/e13bd341fbcf73325ea60fdc8af752addf75c5079867af2e04cc41f34434/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:11e1022b524bd48ae56c9b4f9296bce77e15a2e42a502cceba602f804b32bb79", size = 1450690, upload-time = "2024-12-24T18:29:01.401Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9b/4f/5955dcb376ba4a830384cc6fab7d7547bd6759fe75a09564910e9e3bb8ea/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b9b4d2892fefc886f30301cdd80debd8bb01ecdf165a449eb6e78f79f0fabd6", size = 1376839, upload-time = "2024-12-24T18:29:02.685Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3a/97/5edbed69a9d0caa2e4aa616ae7df8127e10f6586940aa683a496c2c280b9/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a96c0e790ee875d65e340ab383700e2b4891677b7fcd30a699146f9384a2bb0", size = 1435109, upload-time = "2024-12-24T18:29:04.113Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/13/fc/e756382cb64e556af6c1809a1bbb22c141bbc2445049f2da06b420fe52bf/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:23454ff084b07ac54ca8be535f4174170c1094a4cff78fbae4f73a4bcc0d4dab", size = 2245269, upload-time = "2024-12-24T18:29:05.488Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/76/15/e59e45829d7f41c776d138245cabae6515cb4eb44b418f6d4109c478b481/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:87b287251ad6488e95b4f0b4a79a6d04d3ea35fde6340eb38fbd1ca9cd35bbbc", size = 2393468, upload-time = "2024-12-24T18:29:06.79Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e9/39/483558c2a913ab8384d6e4b66a932406f87c95a6080112433da5ed668559/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b21dbe165081142b1232a240fc6383fd32cdd877ca6cc89eab93e5f5883e1c25", size = 2355394, upload-time = "2024-12-24T18:29:08.24Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/01/aa/efad1fbca6570a161d29224f14b082960c7e08268a133fe5dc0f6906820e/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:768cade2c2df13db52475bd28d3a3fac8c9eff04b0e9e2fda0f3760f20b3f7fc", size = 2490901, upload-time = "2024-12-24T18:29:09.653Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c9/4f/15988966ba46bcd5ab9d0c8296914436720dd67fca689ae1a75b4ec1c72f/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d47cfb2650f0e103d4bf68b0b5804c68da97272c84bb12850d877a95c056bd67", size = 2312306, upload-time = "2024-12-24T18:29:12.644Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2d/27/bdf1c769c83f74d98cbc34483a972f221440703054894a37d174fba8aa68/kiwisolver-1.4.8-cp311-cp311-win_amd64.whl", hash = "sha256:ed33ca2002a779a2e20eeb06aea7721b6e47f2d4b8a8ece979d8ba9e2a167e34", size = 71966, upload-time = "2024-12-24T18:29:14.089Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4a/c9/9642ea855604aeb2968a8e145fc662edf61db7632ad2e4fb92424be6b6c0/kiwisolver-1.4.8-cp311-cp311-win_arm64.whl", hash = "sha256:16523b40aab60426ffdebe33ac374457cf62863e330a90a0383639ce14bf44b2", size = 65311, upload-time = "2024-12-24T18:29:15.892Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fc/aa/cea685c4ab647f349c3bc92d2daf7ae34c8e8cf405a6dcd3a497f58a2ac3/kiwisolver-1.4.8-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d6af5e8815fd02997cb6ad9bbed0ee1e60014438ee1a5c2444c96f87b8843502", size = 124152, upload-time = "2024-12-24T18:29:16.85Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c5/0b/8db6d2e2452d60d5ebc4ce4b204feeb16176a851fd42462f66ade6808084/kiwisolver-1.4.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bade438f86e21d91e0cf5dd7c0ed00cda0f77c8c1616bd83f9fc157fa6760d31", size = 66555, upload-time = "2024-12-24T18:29:19.146Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/60/26/d6a0db6785dd35d3ba5bf2b2df0aedc5af089962c6eb2cbf67a15b81369e/kiwisolver-1.4.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b83dc6769ddbc57613280118fb4ce3cd08899cc3369f7d0e0fab518a7cf37fdb", size = 65067, upload-time = "2024-12-24T18:29:20.096Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c9/ed/1d97f7e3561e09757a196231edccc1bcf59d55ddccefa2afc9c615abd8e0/kiwisolver-1.4.8-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:111793b232842991be367ed828076b03d96202c19221b5ebab421ce8bcad016f", size = 1378443, upload-time = "2024-12-24T18:29:22.843Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/29/61/39d30b99954e6b46f760e6289c12fede2ab96a254c443639052d1b573fbc/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:257af1622860e51b1a9d0ce387bf5c2c4f36a90594cb9514f55b074bcc787cfc", size = 1472728, upload-time = "2024-12-24T18:29:24.463Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0c/3e/804163b932f7603ef256e4a715e5843a9600802bb23a68b4e08c8c0ff61d/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69b5637c3f316cab1ec1c9a12b8c5f4750a4c4b71af9157645bf32830e39c03a", size = 1478388, upload-time = "2024-12-24T18:29:25.776Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8a/9e/60eaa75169a154700be74f875a4d9961b11ba048bef315fbe89cb6999056/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:782bb86f245ec18009890e7cb8d13a5ef54dcf2ebe18ed65f795e635a96a1c6a", size = 1413849, upload-time = "2024-12-24T18:29:27.202Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bc/b3/9458adb9472e61a998c8c4d95cfdfec91c73c53a375b30b1428310f923e4/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc978a80a0db3a66d25767b03688f1147a69e6237175c0f4ffffaaedf744055a", size = 1475533, upload-time = "2024-12-24T18:29:28.638Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e4/7a/0a42d9571e35798de80aef4bb43a9b672aa7f8e58643d7bd1950398ffb0a/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:36dbbfd34838500a31f52c9786990d00150860e46cd5041386f217101350f0d3", size = 2268898, upload-time = "2024-12-24T18:29:30.368Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d9/07/1255dc8d80271400126ed8db35a1795b1a2c098ac3a72645075d06fe5c5d/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:eaa973f1e05131de5ff3569bbba7f5fd07ea0595d3870ed4a526d486fe57fa1b", size = 2425605, upload-time = "2024-12-24T18:29:33.151Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/84/df/5a3b4cf13780ef6f6942df67b138b03b7e79e9f1f08f57c49957d5867f6e/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a66f60f8d0c87ab7f59b6fb80e642ebb29fec354a4dfad687ca4092ae69d04f4", size = 2375801, upload-time = "2024-12-24T18:29:34.584Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8f/10/2348d068e8b0f635c8c86892788dac7a6b5c0cb12356620ab575775aad89/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858416b7fb777a53f0c59ca08190ce24e9abbd3cffa18886a5781b8e3e26f65d", size = 2520077, upload-time = "2024-12-24T18:29:36.138Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/32/d8/014b89fee5d4dce157d814303b0fce4d31385a2af4c41fed194b173b81ac/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:085940635c62697391baafaaeabdf3dd7a6c3643577dde337f4d66eba021b2b8", size = 2338410, upload-time = "2024-12-24T18:29:39.991Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bd/72/dfff0cc97f2a0776e1c9eb5bef1ddfd45f46246c6533b0191887a427bca5/kiwisolver-1.4.8-cp312-cp312-win_amd64.whl", hash = "sha256:01c3d31902c7db5fb6182832713d3b4122ad9317c2c5877d0539227d96bb2e50", size = 71853, upload-time = "2024-12-24T18:29:42.006Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/dc/85/220d13d914485c0948a00f0b9eb419efaf6da81b7d72e88ce2391f7aed8d/kiwisolver-1.4.8-cp312-cp312-win_arm64.whl", hash = "sha256:a3c44cb68861de93f0c4a8175fbaa691f0aa22550c331fefef02b618a9dcb476", size = 65424, upload-time = "2024-12-24T18:29:44.38Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1f/f9/ae81c47a43e33b93b0a9819cac6723257f5da2a5a60daf46aa5c7226ea85/kiwisolver-1.4.8-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e7a019419b7b510f0f7c9dceff8c5eae2392037eae483a7f9162625233802b0a", size = 60403, upload-time = "2024-12-24T18:30:41.372Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/58/ca/f92b5cb6f4ce0c1ebfcfe3e2e42b96917e16f7090e45b21102941924f18f/kiwisolver-1.4.8-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:286b18e86682fd2217a48fc6be6b0f20c1d0ed10958d8dc53453ad58d7be0bf8", size = 58657, upload-time = "2024-12-24T18:30:42.392Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/80/28/ae0240f732f0484d3a4dc885d055653c47144bdf59b670aae0ec3c65a7c8/kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4191ee8dfd0be1c3666ccbac178c5a05d5f8d689bbe3fc92f3c4abec817f8fe0", size = 84948, upload-time = "2024-12-24T18:30:44.703Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5d/eb/78d50346c51db22c7203c1611f9b513075f35c4e0e4877c5dde378d66043/kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cd2785b9391f2873ad46088ed7599a6a71e762e1ea33e87514b1a441ed1da1c", size = 81186, upload-time = "2024-12-24T18:30:45.654Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/43/f8/7259f18c77adca88d5f64f9a522792e178b2691f3748817a8750c2d216ef/kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c07b29089b7ba090b6f1a669f1411f27221c3662b3a1b7010e67b59bb5a6f10b", size = 80279, upload-time = "2024-12-24T18:30:47.951Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3a/1d/50ad811d1c5dae091e4cf046beba925bcae0a610e79ae4c538f996f63ed5/kiwisolver-1.4.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:65ea09a5a3faadd59c2ce96dc7bf0f364986a315949dc6374f04396b0d60e09b", size = 71762, upload-time = "2024-12-24T18:30:48.903Z" }, +] + +[[package]] +name = "langfuse" +version = "3.2.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "backoff" }, + { name = "httpx" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-otlp" }, + { name = "opentelemetry-sdk" }, + { name = "packaging" }, + { name = "pydantic" }, + { name = "requests" }, + { name = "wrapt" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/61/0d/8fc51099cf337fb3b56cb7d305074bc0223c62e1ccabf80cc6285ccf5b31/langfuse-3.2.1.tar.gz", hash = "sha256:f79b0380dfcf52c7525bb5d7f8e9d8786a6fc8b37867def047bb388930a7beb3", size = 153369, upload-time = "2025-07-16T09:50:28.434Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/92/b0/8f08df3f0fa584c4132937690c6dd33e0a116f963ecf2b35567f614e0ca7/langfuse-3.2.1-py3-none-any.whl", hash = "sha256:07a84e8c1eed6ac8e149bdda1431fd866e4aee741b66124316336fb2bc7e6a32", size = 299315, upload-time = "2025-07-16T09:50:26.582Z" }, +] + +[[package]] +name = "lark" +version = "1.2.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/af/60/bc7622aefb2aee1c0b4ba23c1446d3e30225c8770b38d7aedbfb65ca9d5a/lark-1.2.2.tar.gz", hash = "sha256:ca807d0162cd16cef15a8feecb862d7319e7a09bdb13aef927968e45040fed80", size = 252132, upload-time = "2024-08-13T19:49:00.652Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2d/00/d90b10b962b4277f5e64a78b6609968859ff86889f5b898c1a778c06ec00/lark-1.2.2-py3-none-any.whl", hash = "sha256:c2276486b02f0f1b90be155f2c8ba4a8e194d42775786db622faccd652d8e80c", size = 111036, upload-time = "2024-08-13T19:48:58.603Z" }, +] + +[[package]] +name = "lark-parser" +version = "0.12.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5a/ee/fd1192d7724419ddfe15b6f17d1c8742800d4de917c0adac3b6aaf22e921/lark-parser-0.12.0.tar.gz", hash = "sha256:15967db1f1214013dca65b1180745047b9be457d73da224fcda3d9dd4e96a138", size = 235029, upload-time = "2021-08-30T09:14:44.484Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/76/00/90f05db333fe1aa6b6ffea83a35425b7d53ea95c8bba0b1597f226cf1d5f/lark_parser-0.12.0-py2.py3-none-any.whl", hash = "sha256:0eaf30cb5ba787fe404d73a7d6e61df97b21d5a63ac26c5008c78a494373c675", size = 103498, upload-time = "2021-08-30T13:01:01.603Z" }, +] + +[[package]] +name = "litellm" +version = "1.75.5.post1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "click" }, + { name = "httpx" }, + { name = "importlib-metadata" }, + { name = "jinja2" }, + { name = "jsonschema" }, + { name = "openai" }, + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "tiktoken" }, + { name = "tokenizers" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/10/97/6091a020895102a20f1da204ebe68c1293123555476b38e749f95ba5981c/litellm-1.75.5.post1.tar.gz", hash = "sha256:e40a0e4b25032755dc5df7f02742abe9e3b8836236363f605f3bdd363cb5a0d0", size = 10127846, upload-time = "2025-08-10T16:30:23.788Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8f/76/780f68a3b26227136a5147c76860aacedcae9bf1b7afc1c991ec9cad11bc/litellm-1.75.5.post1-py3-none-any.whl", hash = "sha256:1c72809a9c8f6e132ad06eb7e628f674c775b0ce6bfb58cbd37e8b585d929cb7", size = 8895997, upload-time = "2025-08-10T16:30:21.325Z" }, +] + +[[package]] +name = "llvmlite" +version = "0.44.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/89/6a/95a3d3610d5c75293d5dbbb2a76480d5d4eeba641557b69fe90af6c5b84e/llvmlite-0.44.0.tar.gz", hash = "sha256:07667d66a5d150abed9157ab6c0b9393c9356f229784a4385c02f99e94fc94d4", size = 171880, upload-time = "2025-01-20T11:14:41.342Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/41/75/d4863ddfd8ab5f6e70f4504cf8cc37f4e986ec6910f4ef8502bb7d3c1c71/llvmlite-0.44.0-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:9fbadbfba8422123bab5535b293da1cf72f9f478a65645ecd73e781f962ca614", size = 28132306, upload-time = "2025-01-20T11:12:18.634Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/37/d9/6e8943e1515d2f1003e8278819ec03e4e653e2eeb71e4d00de6cfe59424e/llvmlite-0.44.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cccf8eb28f24840f2689fb1a45f9c0f7e582dd24e088dcf96e424834af11f791", size = 26201096, upload-time = "2025-01-20T11:12:24.544Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/aa/46/8ffbc114def88cc698906bf5acab54ca9fdf9214fe04aed0e71731fb3688/llvmlite-0.44.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7202b678cdf904823c764ee0fe2dfe38a76981f4c1e51715b4cb5abb6cf1d9e8", size = 42361859, upload-time = "2025-01-20T11:12:31.839Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/30/1c/9366b29ab050a726af13ebaae8d0dff00c3c58562261c79c635ad4f5eb71/llvmlite-0.44.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40526fb5e313d7b96bda4cbb2c85cd5374e04d80732dd36a282d72a560bb6408", size = 41184199, upload-time = "2025-01-20T11:12:40.049Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/69/07/35e7c594b021ecb1938540f5bce543ddd8713cff97f71d81f021221edc1b/llvmlite-0.44.0-cp310-cp310-win_amd64.whl", hash = "sha256:41e3839150db4330e1b2716c0be3b5c4672525b4c9005e17c7597f835f351ce2", size = 30332381, upload-time = "2025-01-20T11:12:47.054Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b5/e2/86b245397052386595ad726f9742e5223d7aea999b18c518a50e96c3aca4/llvmlite-0.44.0-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:eed7d5f29136bda63b6d7804c279e2b72e08c952b7c5df61f45db408e0ee52f3", size = 28132305, upload-time = "2025-01-20T11:12:53.936Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ff/ec/506902dc6870249fbe2466d9cf66d531265d0f3a1157213c8f986250c033/llvmlite-0.44.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ace564d9fa44bb91eb6e6d8e7754977783c68e90a471ea7ce913bff30bd62427", size = 26201090, upload-time = "2025-01-20T11:12:59.847Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/99/fe/d030f1849ebb1f394bb3f7adad5e729b634fb100515594aca25c354ffc62/llvmlite-0.44.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5d22c3bfc842668168a786af4205ec8e3ad29fb1bc03fd11fd48460d0df64c1", size = 42361858, upload-time = "2025-01-20T11:13:07.623Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d7/7a/ce6174664b9077fc673d172e4c888cb0b128e707e306bc33fff8c2035f0d/llvmlite-0.44.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f01a394e9c9b7b1d4e63c327b096d10f6f0ed149ef53d38a09b3749dcf8c9610", size = 41184200, upload-time = "2025-01-20T11:13:20.058Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5f/c6/258801143975a6d09a373f2641237992496e15567b907a4d401839d671b8/llvmlite-0.44.0-cp311-cp311-win_amd64.whl", hash = "sha256:d8489634d43c20cd0ad71330dde1d5bc7b9966937a263ff1ec1cebb90dc50955", size = 30331193, upload-time = "2025-01-20T11:13:26.976Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/15/86/e3c3195b92e6e492458f16d233e58a1a812aa2bfbef9bdd0fbafcec85c60/llvmlite-0.44.0-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:1d671a56acf725bf1b531d5ef76b86660a5ab8ef19bb6a46064a705c6ca80aad", size = 28132297, upload-time = "2025-01-20T11:13:32.57Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d6/53/373b6b8be67b9221d12b24125fd0ec56b1078b660eeae266ec388a6ac9a0/llvmlite-0.44.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f79a728e0435493611c9f405168682bb75ffd1fbe6fc360733b850c80a026db", size = 26201105, upload-time = "2025-01-20T11:13:38.744Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cb/da/8341fd3056419441286c8e26bf436923021005ece0bff5f41906476ae514/llvmlite-0.44.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0143a5ef336da14deaa8ec26c5449ad5b6a2b564df82fcef4be040b9cacfea9", size = 42361901, upload-time = "2025-01-20T11:13:46.711Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/53/ad/d79349dc07b8a395a99153d7ce8b01d6fcdc9f8231355a5df55ded649b61/llvmlite-0.44.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d752f89e31b66db6f8da06df8b39f9b91e78c5feea1bf9e8c1fba1d1c24c065d", size = 41184247, upload-time = "2025-01-20T11:13:56.159Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e2/3b/a9a17366af80127bd09decbe2a54d8974b6d8b274b39bf47fbaedeec6307/llvmlite-0.44.0-cp312-cp312-win_amd64.whl", hash = "sha256:eae7e2d4ca8f88f89d315b48c6b741dcb925d6a1042da694aa16ab3dd4cbd3a1", size = 30332380, upload-time = "2025-01-20T11:14:02.442Z" }, +] + +[[package]] +name = "loguru" +version = "0.7.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "win32-setctime", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3a/05/a1dae3dffd1116099471c643b8924f5aa6524411dc6c63fdae648c4f1aca/loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6", size = 63559, upload-time = "2024-12-06T11:20:56.608Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595, upload-time = "2024-12-06T11:20:54.538Z" }, +] + +[[package]] +name = "lxml" +version = "5.3.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e7/6b/20c3a4b24751377aaa6307eb230b66701024012c29dd374999cc92983269/lxml-5.3.0.tar.gz", hash = "sha256:4e109ca30d1edec1ac60cdbe341905dc3b8f55b16855e03a54aaf59e51ec8c6f", size = 3679318, upload-time = "2024-08-10T18:17:29.668Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a1/ce/2789e39eddf2b13fac29878bfa465f0910eb6b0096e29090e5176bc8cf43/lxml-5.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:dd36439be765e2dde7660212b5275641edbc813e7b24668831a5c8ac91180656", size = 8124570, upload-time = "2024-08-10T18:09:04.096Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/24/a8/f4010166a25d41715527129af2675981a50d3bbf7df09c5d9ab8ca24fbf9/lxml-5.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ae5fe5c4b525aa82b8076c1a59d642c17b6e8739ecf852522c6321852178119d", size = 4413042, upload-time = "2024-08-10T18:09:08.841Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/41/a4/7e45756cecdd7577ddf67a68b69c1db0f5ddbf0c9f65021ee769165ffc5a/lxml-5.3.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:501d0d7e26b4d261fca8132854d845e4988097611ba2531408ec91cf3fd9d20a", size = 5139213, upload-time = "2024-08-10T18:09:12.622Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/02/e2/ecf845b12323c92748077e1818b64e8b4dba509a4cb12920b3762ebe7552/lxml-5.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb66442c2546446944437df74379e9cf9e9db353e61301d1a0e26482f43f0dd8", size = 4838814, upload-time = "2024-08-10T18:09:16.222Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/12/91/619f9fb72cf75e9ceb8700706f7276f23995f6ad757e6d400fbe35ca4990/lxml-5.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e41506fec7a7f9405b14aa2d5c8abbb4dbbd09d88f9496958b6d00cb4d45330", size = 5425084, upload-time = "2024-08-10T18:09:19.795Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/25/3b/162a85a8f0fd2a3032ec3f936636911c6e9523a8e263fffcfd581ce98b54/lxml-5.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f7d4a670107d75dfe5ad080bed6c341d18c4442f9378c9f58e5851e86eb79965", size = 4875993, upload-time = "2024-08-10T18:09:23.776Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/43/af/dd3f58cc7d946da6ae42909629a2b1d5dd2d1b583334d4af9396697d6863/lxml-5.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41ce1f1e2c7755abfc7e759dc34d7d05fd221723ff822947132dc934d122fe22", size = 5012462, upload-time = "2024-08-10T18:09:27.642Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/69/c1/5ea46b2d4c98f5bf5c83fffab8a0ad293c9bc74df9ecfbafef10f77f7201/lxml-5.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:44264ecae91b30e5633013fb66f6ddd05c006d3e0e884f75ce0b4755b3e3847b", size = 4815288, upload-time = "2024-08-10T18:09:31.633Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1d/51/a0acca077ad35da458f4d3f729ef98effd2b90f003440d35fc36323f8ae6/lxml-5.3.0-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:3c174dc350d3ec52deb77f2faf05c439331d6ed5e702fc247ccb4e6b62d884b7", size = 5472435, upload-time = "2024-08-10T18:09:35.758Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4d/6b/0989c9368986961a6b0f55b46c80404c4b758417acdb6d87bfc3bd5f4967/lxml-5.3.0-cp310-cp310-manylinux_2_28_s390x.whl", hash = "sha256:2dfab5fa6a28a0b60a20638dc48e6343c02ea9933e3279ccb132f555a62323d8", size = 4976354, upload-time = "2024-08-10T18:09:39.51Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/05/9e/87492d03ff604fbf656ed2bf3e2e8d28f5d58ea1f00ff27ac27b06509079/lxml-5.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b1c8c20847b9f34e98080da785bb2336ea982e7f913eed5809e5a3c872900f32", size = 5029973, upload-time = "2024-08-10T18:09:42.978Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f9/cc/9ae1baf5472af88e19e2c454b3710c1be9ecafb20eb474eeabcd88a055d2/lxml-5.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2c86bf781b12ba417f64f3422cfc302523ac9cd1d8ae8c0f92a1c66e56ef2e86", size = 4888837, upload-time = "2024-08-10T18:09:46.185Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d2/10/5594ffaec8c120d75b17e3ad23439b740a51549a9b5fd7484b2179adfe8f/lxml-5.3.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:c162b216070f280fa7da844531169be0baf9ccb17263cf5a8bf876fcd3117fa5", size = 5530555, upload-time = "2024-08-10T18:09:50.366Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ea/9b/de17f05377c8833343b629905571fb06cff2028f15a6f58ae2267662e341/lxml-5.3.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:36aef61a1678cb778097b4a6eeae96a69875d51d1e8f4d4b491ab3cfb54b5a03", size = 5405314, upload-time = "2024-08-10T18:09:54.58Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8a/b4/227be0f1f3cca8255925985164c3838b8b36e441ff0cc10c1d3c6bdba031/lxml-5.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f65e5120863c2b266dbcc927b306c5b78e502c71edf3295dfcb9501ec96e5fc7", size = 5079303, upload-time = "2024-08-10T18:09:58.032Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5c/ee/19abcebb7fc40319bb71cd6adefa1ad94d09b5660228715854d6cc420713/lxml-5.3.0-cp310-cp310-win32.whl", hash = "sha256:ef0c1fe22171dd7c7c27147f2e9c3e86f8bdf473fed75f16b0c2e84a5030ce80", size = 3475126, upload-time = "2024-08-10T18:10:01.43Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a1/35/183d32551447e280032b2331738cd850da435a42f850b71ebeaab42c1313/lxml-5.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:052d99051e77a4f3e8482c65014cf6372e61b0a6f4fe9edb98503bb5364cfee3", size = 3805065, upload-time = "2024-08-10T18:10:05.189Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5c/a8/449faa2a3cbe6a99f8d38dcd51a3ee8844c17862841a6f769ea7c2a9cd0f/lxml-5.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:74bcb423462233bc5d6066e4e98b0264e7c1bed7541fff2f4e34fe6b21563c8b", size = 8141056, upload-time = "2024-08-10T18:10:09.455Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ac/8a/ae6325e994e2052de92f894363b038351c50ee38749d30cc6b6d96aaf90f/lxml-5.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a3d819eb6f9b8677f57f9664265d0a10dd6551d227afb4af2b9cd7bdc2ccbf18", size = 4425238, upload-time = "2024-08-10T18:10:13.348Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f8/fb/128dddb7f9086236bce0eeae2bfb316d138b49b159f50bc681d56c1bdd19/lxml-5.3.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b8f5db71b28b8c404956ddf79575ea77aa8b1538e8b2ef9ec877945b3f46442", size = 5095197, upload-time = "2024-08-10T18:10:16.825Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b4/f9/a181a8ef106e41e3086629c8bdb2d21a942f14c84a0e77452c22d6b22091/lxml-5.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c3406b63232fc7e9b8783ab0b765d7c59e7c59ff96759d8ef9632fca27c7ee4", size = 4809809, upload-time = "2024-08-10T18:10:20.046Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/25/2f/b20565e808f7f6868aacea48ddcdd7e9e9fb4c799287f21f1a6c7c2e8b71/lxml-5.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ecdd78ab768f844c7a1d4a03595038c166b609f6395e25af9b0f3f26ae1230f", size = 5407593, upload-time = "2024-08-10T18:10:23.641Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/23/0e/caac672ec246d3189a16c4d364ed4f7d6bf856c080215382c06764058c08/lxml-5.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:168f2dfcfdedf611eb285efac1516c8454c8c99caf271dccda8943576b67552e", size = 4866657, upload-time = "2024-08-10T18:10:26.528Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/67/a4/1f5fbd3f58d4069000522196b0b776a014f3feec1796da03e495cf23532d/lxml-5.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa617107a410245b8660028a7483b68e7914304a6d4882b5ff3d2d3eb5948d8c", size = 4967017, upload-time = "2024-08-10T18:10:29.639Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ee/73/623ecea6ca3c530dd0a4ed0d00d9702e0e85cd5624e2d5b93b005fe00abd/lxml-5.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:69959bd3167b993e6e710b99051265654133a98f20cec1d9b493b931942e9c16", size = 4810730, upload-time = "2024-08-10T18:10:33.387Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1d/ce/fb84fb8e3c298f3a245ae3ea6221c2426f1bbaa82d10a88787412a498145/lxml-5.3.0-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:bd96517ef76c8654446fc3db9242d019a1bb5fe8b751ba414765d59f99210b79", size = 5455154, upload-time = "2024-08-10T18:10:36.897Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b1/72/4d1ad363748a72c7c0411c28be2b0dc7150d91e823eadad3b91a4514cbea/lxml-5.3.0-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:ab6dd83b970dc97c2d10bc71aa925b84788c7c05de30241b9e96f9b6d9ea3080", size = 4969416, upload-time = "2024-08-10T18:10:40.331Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/42/07/b29571a58a3a80681722ea8ed0ba569211d9bb8531ad49b5cacf6d409185/lxml-5.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:eec1bb8cdbba2925bedc887bc0609a80e599c75b12d87ae42ac23fd199445654", size = 5013672, upload-time = "2024-08-10T18:10:43.768Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b9/93/bde740d5a58cf04cbd38e3dd93ad1e36c2f95553bbf7d57807bc6815d926/lxml-5.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6a7095eeec6f89111d03dabfe5883a1fd54da319c94e0fb104ee8f23616b572d", size = 4878644, upload-time = "2024-08-10T18:10:47.901Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/56/b5/645c8c02721d49927c93181de4017164ec0e141413577687c3df8ff0800f/lxml-5.3.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6f651ebd0b21ec65dfca93aa629610a0dbc13dbc13554f19b0113da2e61a4763", size = 5511531, upload-time = "2024-08-10T18:10:51.581Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/85/3f/6a99a12d9438316f4fc86ef88c5d4c8fb674247b17f3173ecadd8346b671/lxml-5.3.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f422a209d2455c56849442ae42f25dbaaba1c6c3f501d58761c619c7836642ec", size = 5402065, upload-time = "2024-08-10T18:10:54.841Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/80/8a/df47bff6ad5ac57335bf552babfb2408f9eb680c074ec1ba412a1a6af2c5/lxml-5.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:62f7fdb0d1ed2065451f086519865b4c90aa19aed51081979ecd05a21eb4d1be", size = 5069775, upload-time = "2024-08-10T18:10:57.808Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/08/ae/e7ad0f0fbe4b6368c5ee1e3ef0c3365098d806d42379c46c1ba2802a52f7/lxml-5.3.0-cp311-cp311-win32.whl", hash = "sha256:c6379f35350b655fd817cd0d6cbeef7f265f3ae5fedb1caae2eb442bbeae9ab9", size = 3474226, upload-time = "2024-08-10T18:11:00.73Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c3/b5/91c2249bfac02ee514ab135e9304b89d55967be7e53e94a879b74eec7a5c/lxml-5.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:9c52100e2c2dbb0649b90467935c4b0de5528833c76a35ea1a2691ec9f1ee7a1", size = 3814971, upload-time = "2024-08-10T18:11:03.743Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/eb/6d/d1f1c5e40c64bf62afd7a3f9b34ce18a586a1cccbf71e783cd0a6d8e8971/lxml-5.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e99f5507401436fdcc85036a2e7dc2e28d962550afe1cbfc07c40e454256a859", size = 8171753, upload-time = "2024-08-10T18:11:07.859Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bd/83/26b1864921869784355459f374896dcf8b44d4af3b15d7697e9156cb2de9/lxml-5.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:384aacddf2e5813a36495233b64cb96b1949da72bef933918ba5c84e06af8f0e", size = 4441955, upload-time = "2024-08-10T18:11:12.251Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e0/d2/e9bff9fb359226c25cda3538f664f54f2804f4b37b0d7c944639e1a51f69/lxml-5.3.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:874a216bf6afaf97c263b56371434e47e2c652d215788396f60477540298218f", size = 5050778, upload-time = "2024-08-10T18:11:16.233Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/88/69/6972bfafa8cd3ddc8562b126dd607011e218e17be313a8b1b9cc5a0ee876/lxml-5.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65ab5685d56914b9a2a34d67dd5488b83213d680b0c5d10b47f81da5a16b0b0e", size = 4748628, upload-time = "2024-08-10T18:11:19.507Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5d/ea/a6523c7c7f6dc755a6eed3d2f6d6646617cad4d3d6d8ce4ed71bfd2362c8/lxml-5.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aac0bbd3e8dd2d9c45ceb82249e8bdd3ac99131a32b4d35c8af3cc9db1657179", size = 5322215, upload-time = "2024-08-10T18:11:23.708Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/99/37/396fbd24a70f62b31d988e4500f2068c7f3fd399d2fd45257d13eab51a6f/lxml-5.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b369d3db3c22ed14c75ccd5af429086f166a19627e84a8fdade3f8f31426e52a", size = 4813963, upload-time = "2024-08-10T18:11:26.997Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/09/91/e6136f17459a11ce1757df864b213efbeab7adcb2efa63efb1b846ab6723/lxml-5.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c24037349665434f375645fa9d1f5304800cec574d0310f618490c871fd902b3", size = 4923353, upload-time = "2024-08-10T18:11:30.478Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1d/7c/2eeecf87c9a1fca4f84f991067c693e67340f2b7127fc3eca8fa29d75ee3/lxml-5.3.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:62d172f358f33a26d6b41b28c170c63886742f5b6772a42b59b4f0fa10526cb1", size = 4740541, upload-time = "2024-08-10T18:11:34.344Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3b/ed/4c38ba58defca84f5f0d0ac2480fdcd99fc7ae4b28fc417c93640a6949ae/lxml-5.3.0-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:c1f794c02903c2824fccce5b20c339a1a14b114e83b306ff11b597c5f71a1c8d", size = 5346504, upload-time = "2024-08-10T18:11:37.595Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a5/22/bbd3995437e5745cb4c2b5d89088d70ab19d4feabf8a27a24cecb9745464/lxml-5.3.0-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:5d6a6972b93c426ace71e0be9a6f4b2cfae9b1baed2eed2006076a746692288c", size = 4898077, upload-time = "2024-08-10T18:11:40.867Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0a/6e/94537acfb5b8f18235d13186d247bca478fea5e87d224644e0fe907df976/lxml-5.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:3879cc6ce938ff4eb4900d901ed63555c778731a96365e53fadb36437a131a99", size = 4946543, upload-time = "2024-08-10T18:11:44.954Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8d/e8/4b15df533fe8e8d53363b23a41df9be907330e1fa28c7ca36893fad338ee/lxml-5.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:74068c601baff6ff021c70f0935b0c7bc528baa8ea210c202e03757c68c5a4ff", size = 4816841, upload-time = "2024-08-10T18:11:49.046Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1a/e7/03f390ea37d1acda50bc538feb5b2bda6745b25731e4e76ab48fae7106bf/lxml-5.3.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ecd4ad8453ac17bc7ba3868371bffb46f628161ad0eefbd0a855d2c8c32dd81a", size = 5417341, upload-time = "2024-08-10T18:11:52.295Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ea/99/d1133ab4c250da85a883c3b60249d3d3e7c64f24faff494cf0fd23f91e80/lxml-5.3.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7e2f58095acc211eb9d8b5771bf04df9ff37d6b87618d1cbf85f92399c98dae8", size = 5327539, upload-time = "2024-08-10T18:11:55.98Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7d/ed/e6276c8d9668028213df01f598f385b05b55a4e1b4662ee12ef05dab35aa/lxml-5.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e63601ad5cd8f860aa99d109889b5ac34de571c7ee902d6812d5d9ddcc77fa7d", size = 5012542, upload-time = "2024-08-10T18:11:59.351Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/36/88/684d4e800f5aa28df2a991a6a622783fb73cf0e46235cfa690f9776f032e/lxml-5.3.0-cp312-cp312-win32.whl", hash = "sha256:17e8d968d04a37c50ad9c456a286b525d78c4a1c15dd53aa46c1d8e06bf6fa30", size = 3486454, upload-time = "2024-08-10T18:12:02.696Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fc/82/ace5a5676051e60355bd8fb945df7b1ba4f4fb8447f2010fb816bfd57724/lxml-5.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:c1a69e58a6bb2de65902051d57fde951febad631a20a64572677a1052690482f", size = 3816857, upload-time = "2024-08-10T18:12:06.456Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/99/f7/b73a431c8500565aa500e99e60b448d305eaf7c0b4c893c7c5a8a69cc595/lxml-5.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7b1cd427cb0d5f7393c31b7496419da594fe600e6fdc4b105a54f82405e6626c", size = 3925431, upload-time = "2024-08-10T18:15:59.002Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/db/48/4a206623c0d093d0e3b15f415ffb4345b0bdf661a3d0b15a112948c033c7/lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51806cfe0279e06ed8500ce19479d757db42a30fd509940b1701be9c86a5ff9a", size = 4216683, upload-time = "2024-08-10T18:16:03.004Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/54/47/577820c45dd954523ae8453b632d91e76da94ca6d9ee40d8c98dd86f916b/lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee70d08fd60c9565ba8190f41a46a54096afa0eeb8f76bd66f2c25d3b1b83005", size = 4326732, upload-time = "2024-08-10T18:16:06.973Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/68/de/96cb6d3269bc994b4f5ede8ca7bf0840f5de0a278bc6e50cb317ff71cafa/lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:8dc2c0395bea8254d8daebc76dcf8eb3a95ec2a46fa6fae5eaccee366bfe02ce", size = 4218377, upload-time = "2024-08-10T18:16:10.836Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a5/43/19b1ef6cbffa4244a217f95cc5f41a6cb4720fed33510a49670b03c5f1a0/lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6ba0d3dcac281aad8a0e5b14c7ed6f9fa89c8612b47939fc94f80b16e2e9bc83", size = 4351237, upload-time = "2024-08-10T18:16:14.652Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ba/b2/6a22fb5c0885da3b00e116aee81f0b829ec9ac8f736cd414b4a09413fc7d/lxml-5.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:6e91cf736959057f7aac7adfc83481e03615a8e8dd5758aa1d95ea69e8931dba", size = 3487557, upload-time = "2024-08-10T18:16:18.255Z" }, +] + +[[package]] +name = "lxml-html-clean" +version = "0.4.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "lxml" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/79/b6/466e71db127950fb8d172026a8f0a9f0dc6f64c8e78e2ca79f252e5790b8/lxml_html_clean-0.4.2.tar.gz", hash = "sha256:91291e7b5db95430abf461bc53440964d58e06cc468950f9e47db64976cebcb3", size = 21622, upload-time = "2025-04-09T11:33:59.432Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4e/0b/942cb7278d6caad79343ad2ddd636ed204a47909b969d19114a3097f5aa3/lxml_html_clean-0.4.2-py3-none-any.whl", hash = "sha256:74ccfba277adcfea87a1e9294f47dd86b05d65b4da7c5b07966e3d5f3be8a505", size = 14184, upload-time = "2025-04-09T11:33:57.988Z" }, +] + +[[package]] +name = "lz4" +version = "4.4.4" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c6/5a/945f5086326d569f14c84ac6f7fcc3229f0b9b1e8cc536b951fd53dfb9e1/lz4-4.4.4.tar.gz", hash = "sha256:070fd0627ec4393011251a094e08ed9fdcc78cb4e7ab28f507638eee4e39abda", size = 171884, upload-time = "2025-04-01T22:55:58.62Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b0/80/4054e99cda2e003097f59aeb3ad470128f3298db5065174a84564d2d6983/lz4-4.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f170abb8416c4efca48e76cac2c86c3185efdf841aecbe5c190121c42828ced0", size = 220896, upload-time = "2025-04-01T22:55:13.577Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/dd/4e/f92424d5734e772b05ddbeec739e2566e2a2336995b36a180e1dd9411e9a/lz4-4.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d33a5105cd96ebd32c3e78d7ece6123a9d2fb7c18b84dec61f27837d9e0c496c", size = 189679, upload-time = "2025-04-01T22:55:15.471Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a2/70/71ffd496067cba6ba352e10b89c0e9cee3e4bc4717ba866b6aa350f4c7ac/lz4-4.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30ebbc5b76b4f0018988825a7e9ce153be4f0d4eba34e6c1f2fcded120573e88", size = 1237940, upload-time = "2025-04-01T22:55:16.498Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6e/59/cf34d1e232b11e1ae7122300be00529f369a7cd80f74ac351d58c4c4eedf/lz4-4.4.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc64d6dfa7a89397529b22638939e70d85eaedc1bd68e30a29c78bfb65d4f715", size = 1264105, upload-time = "2025-04-01T22:55:17.606Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f9/f6/3a00a98ff5b872d572cc6e9c88e0f6275bea0f3ed1dc1b8f8b736c85784c/lz4-4.4.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a355223a284f42a723c120ce68827de66d5cb872a38732b3d5abbf544fa2fe26", size = 1184179, upload-time = "2025-04-01T22:55:19.206Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bc/de/6aeb602786174bad290609c0c988afb1077b74a80eaea23ebc3b5de6e2fa/lz4-4.4.4-cp310-cp310-win32.whl", hash = "sha256:b28228197775b7b5096898851d59ef43ccaf151136f81d9c436bc9ba560bc2ba", size = 88265, upload-time = "2025-04-01T22:55:20.215Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e4/b5/1f52c8b17d02ae637f85911c0135ca08be1c9bbdfb3e7de1c4ae7af0bac6/lz4-4.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:45e7c954546de4f85d895aa735989d77f87dd649f503ce1c8a71a151b092ed36", size = 99916, upload-time = "2025-04-01T22:55:21.332Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/01/e7/123587e7dae6cdba48393e4fdad2b9412f43f51346afe9ca6f697029de11/lz4-4.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:e3fc90f766401684740978cd781d73b9685bd81b5dbf7257542ef9de4612e4d2", size = 89746, upload-time = "2025-04-01T22:55:22.205Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/28/e8/63843dc5ecb1529eb38e1761ceed04a0ad52a9ad8929ab8b7930ea2e4976/lz4-4.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ddfc7194cd206496c445e9e5b0c47f970ce982c725c87bd22de028884125b68f", size = 220898, upload-time = "2025-04-01T22:55:23.085Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e4/94/c53de5f07c7dc11cf459aab2a1d754f5df5f693bfacbbe1e4914bfd02f1e/lz4-4.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:714f9298c86f8e7278f1c6af23e509044782fa8220eb0260f8f8f1632f820550", size = 189685, upload-time = "2025-04-01T22:55:24.413Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fe/59/c22d516dd0352f2a3415d1f665ccef2f3e74ecec3ca6a8f061a38f97d50d/lz4-4.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a8474c91de47733856c6686df3c4aca33753741da7e757979369c2c0d32918ba", size = 1239225, upload-time = "2025-04-01T22:55:25.737Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/81/af/665685072e71f3f0e626221b7922867ec249cd8376aca761078c8f11f5da/lz4-4.4.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80dd27d7d680ea02c261c226acf1d41de2fd77af4fb2da62b278a9376e380de0", size = 1265881, upload-time = "2025-04-01T22:55:26.817Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/90/04/b4557ae381d3aa451388a29755cc410066f5e2f78c847f66f154f4520a68/lz4-4.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9b7d6dddfd01b49aedb940fdcaf32f41dc58c926ba35f4e31866aeec2f32f4f4", size = 1185593, upload-time = "2025-04-01T22:55:27.896Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7b/e4/03636979f4e8bf92c557f998ca98ee4e6ef92e92eaf0ed6d3c7f2524e790/lz4-4.4.4-cp311-cp311-win32.whl", hash = "sha256:4134b9fd70ac41954c080b772816bb1afe0c8354ee993015a83430031d686a4c", size = 88259, upload-time = "2025-04-01T22:55:29.03Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/07/f0/9efe53b4945441a5d2790d455134843ad86739855b7e6199977bf6dc8898/lz4-4.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:f5024d3ca2383470f7c4ef4d0ed8eabad0b22b23eeefde1c192cf1a38d5e9f78", size = 99916, upload-time = "2025-04-01T22:55:29.933Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/87/c8/1675527549ee174b9e1db089f7ddfbb962a97314657269b1e0344a5eaf56/lz4-4.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:6ea715bb3357ea1665f77874cf8f55385ff112553db06f3742d3cdcec08633f7", size = 89741, upload-time = "2025-04-01T22:55:31.184Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f7/2d/5523b4fabe11cd98f040f715728d1932eb7e696bfe94391872a823332b94/lz4-4.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:23ae267494fdd80f0d2a131beff890cf857f1b812ee72dbb96c3204aab725553", size = 220669, upload-time = "2025-04-01T22:55:32.032Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/91/06/1a5bbcacbfb48d8ee5b6eb3fca6aa84143a81d92946bdb5cd6b005f1863e/lz4-4.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fff9f3a1ed63d45cb6514bfb8293005dc4141341ce3500abdfeb76124c0b9b2e", size = 189661, upload-time = "2025-04-01T22:55:33.413Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fa/08/39eb7ac907f73e11a69a11576a75a9e36406b3241c0ba41453a7eb842abb/lz4-4.4.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ea7f07329f85a8eda4d8cf937b87f27f0ac392c6400f18bea2c667c8b7f8ecc", size = 1238775, upload-time = "2025-04-01T22:55:34.835Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e9/26/05840fbd4233e8d23e88411a066ab19f1e9de332edddb8df2b6a95c7fddc/lz4-4.4.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ccab8f7f7b82f9fa9fc3b0ba584d353bd5aa818d5821d77d5b9447faad2aaad", size = 1265143, upload-time = "2025-04-01T22:55:35.933Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b7/5d/5f2db18c298a419932f3ab2023deb689863cf8fd7ed875b1c43492479af2/lz4-4.4.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e43e9d48b2daf80e486213128b0763deed35bbb7a59b66d1681e205e1702d735", size = 1185032, upload-time = "2025-04-01T22:55:37.454Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c4/e6/736ab5f128694b0f6aac58343bcf37163437ac95997276cd0be3ea4c3342/lz4-4.4.4-cp312-cp312-win32.whl", hash = "sha256:33e01e18e4561b0381b2c33d58e77ceee850a5067f0ece945064cbaac2176962", size = 88284, upload-time = "2025-04-01T22:55:38.536Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/40/b8/243430cb62319175070e06e3a94c4c7bd186a812e474e22148ae1290d47d/lz4-4.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:d21d1a2892a2dcc193163dd13eaadabb2c1b803807a5117d8f8588b22eaf9f12", size = 99918, upload-time = "2025-04-01T22:55:39.628Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6c/e1/0686c91738f3e6c2e1a243e0fdd4371667c4d2e5009b0a3605806c2aa020/lz4-4.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:2f4f2965c98ab254feddf6b5072854a6935adab7bc81412ec4fe238f07b85f62", size = 89736, upload-time = "2025-04-01T22:55:40.5Z" }, +] + +[[package]] +name = "mammoth" +version = "1.11.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "cobble" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ed/3c/a58418d2af00f2da60d4a51e18cd0311307b72d48d2fffec36a97b4a5e44/mammoth-1.11.0.tar.gz", hash = "sha256:a0f59e442f34d5b6447f4b0999306cbf3e67aaabfa8cb516f878fb1456744637", size = 53142, upload-time = "2025-09-19T10:35:20.373Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ca/54/2e39566a131b13f6d8d193f974cb6a34e81bb7cc2fa6f7e03de067b36588/mammoth-1.11.0-py2.py3-none-any.whl", hash = "sha256:c077ab0d450bd7c0c6ecd529a23bf7e0fa8190c929e28998308ff4eada3f063b", size = 54752, upload-time = "2025-09-19T10:35:18.699Z" }, +] + +[[package]] +name = "markdown" +version = "3.6" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/22/02/4785861427848cc11e452cc62bb541006a1087cf04a1de83aedd5530b948/Markdown-3.6.tar.gz", hash = "sha256:ed4f41f6daecbeeb96e576ce414c41d2d876daa9a16cb35fa8ed8c2ddfad0224", size = 354715, upload-time = "2024-03-14T15:37:59.775Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fc/b3/0c0c994fe49cd661084f8d5dc06562af53818cc0abefaca35bdc894577c3/Markdown-3.6-py3-none-any.whl", hash = "sha256:48f276f4d8cfb8ce6527c8f79e2ee29708508bf4d40aa410fbc3b4ee832c850f", size = 105381, upload-time = "2024-03-14T15:37:57.457Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, +] + +[[package]] +name = "markdown-to-json" +version = "2.1.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b5/1a/d235321eac5ba6de9f83dd172b9549eb03fd149ecda4c8c25cdc9a5224bc/markdown_to_json-2.1.1.tar.gz", hash = "sha256:27642c42acd9130d1449f791f57fd0c4bbf58c7a76cfb5af6d42010ca97b1107", size = 51343, upload-time = "2024-05-09T19:08:44.729Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/47/2b/dac4143951a16c0c03e8fe217c9fa784838d02a29c52ef0e8b265befea8f/markdown_to_json-2.1.1-py3-none-any.whl", hash = "sha256:c73b8a3ac7fbde65463dbaeba8bb925d1d54377cbb01a064cd65e1f3e394bd62", size = 52647, upload-time = "2024-05-09T19:08:42.959Z" }, +] + +[[package]] +name = "markdownify" +version = "1.2.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "six" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/83/1b/6f2697b51eaca81f08852fd2734745af15718fea10222a1d40f8a239c4ea/markdownify-1.2.0.tar.gz", hash = "sha256:f6c367c54eb24ee953921804dfe6d6575c5e5b42c643955e7242034435de634c", size = 18771, upload-time = "2025-08-09T17:44:15.302Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6a/e2/7af643acb4cae0741dffffaa7f3f7c9e7ab4046724543ba1777c401d821c/markdownify-1.2.0-py3-none-any.whl", hash = "sha256:48e150a1c4993d4d50f282f725c0111bd9eb25645d41fa2f543708fd44161351", size = 15561, upload-time = "2025-08-09T17:44:14.074Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/04/90/d08277ce111dd22f77149fd1a5d4653eeb3b3eaacbdfcbae5afb2600eebd/MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", size = 14357, upload-time = "2024-10-18T15:20:51.44Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/04/e1/6e2194baeae0bca1fae6629dc0cbbb968d4d941469cbab11a3872edff374/MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", size = 12393, upload-time = "2024-10-18T15:20:52.426Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1d/69/35fa85a8ece0a437493dc61ce0bb6d459dcba482c34197e3efc829aa357f/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", size = 21732, upload-time = "2024-10-18T15:20:53.578Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/22/35/137da042dfb4720b638d2937c38a9c2df83fe32d20e8c8f3185dbfef05f7/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", size = 20866, upload-time = "2024-10-18T15:20:55.06Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/29/28/6d029a903727a1b62edb51863232152fd335d602def598dade38996887f0/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", size = 20964, upload-time = "2024-10-18T15:20:55.906Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cc/cd/07438f95f83e8bc028279909d9c9bd39e24149b0d60053a97b2bc4f8aa51/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", size = 21977, upload-time = "2024-10-18T15:20:57.189Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/29/01/84b57395b4cc062f9c4c55ce0df7d3108ca32397299d9df00fedd9117d3d/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", size = 21366, upload-time = "2024-10-18T15:20:58.235Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bd/6e/61ebf08d8940553afff20d1fb1ba7294b6f8d279df9fd0c0db911b4bbcfd/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", size = 21091, upload-time = "2024-10-18T15:20:59.235Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/11/23/ffbf53694e8c94ebd1e7e491de185124277964344733c45481f32ede2499/MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50", size = 15065, upload-time = "2024-10-18T15:21:00.307Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/44/06/e7175d06dd6e9172d4a69a72592cb3f7a996a9c396eee29082826449bbc3/MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", size = 15514, upload-time = "2024-10-18T15:21:01.122Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353, upload-time = "2024-10-18T15:21:02.187Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392, upload-time = "2024-10-18T15:21:02.941Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984, upload-time = "2024-10-18T15:21:03.953Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120, upload-time = "2024-10-18T15:21:06.495Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032, upload-time = "2024-10-18T15:21:07.295Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057, upload-time = "2024-10-18T15:21:08.073Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359, upload-time = "2024-10-18T15:21:09.318Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306, upload-time = "2024-10-18T15:21:10.185Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094, upload-time = "2024-10-18T15:21:11.005Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521, upload-time = "2024-10-18T15:21:12.911Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload-time = "2024-10-18T15:21:13.777Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload-time = "2024-10-18T15:21:14.822Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload-time = "2024-10-18T15:21:15.642Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload-time = "2024-10-18T15:21:17.133Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload-time = "2024-10-18T15:21:18.064Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload-time = "2024-10-18T15:21:18.859Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload-time = "2024-10-18T15:21:19.671Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload-time = "2024-10-18T15:21:20.971Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload-time = "2024-10-18T15:21:22.646Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" }, +] + +[[package]] +name = "matplotlib" +version = "3.10.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "contourpy", version = "1.3.2", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version < '3.11'" }, + { name = "contourpy", version = "1.3.3", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "cycler" }, + { name = "fonttools" }, + { name = "kiwisolver" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "pyparsing" }, + { name = "python-dateutil" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/26/91/d49359a21893183ed2a5b6c76bec40e0b1dcbf8ca148f864d134897cfc75/matplotlib-3.10.3.tar.gz", hash = "sha256:2f82d2c5bb7ae93aaaa4cd42aca65d76ce6376f83304fa3a630b569aca274df0", size = 34799811, upload-time = "2025-05-08T19:10:54.39Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d0/ea/2bba25d289d389c7451f331ecd593944b3705f06ddf593fa7be75037d308/matplotlib-3.10.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:213fadd6348d106ca7db99e113f1bea1e65e383c3ba76e8556ba4a3054b65ae7", size = 8167862, upload-time = "2025-05-08T19:09:39.563Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/41/81/cc70b5138c926604e8c9ed810ed4c79e8116ba72e02230852f5c12c87ba2/matplotlib-3.10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d3bec61cb8221f0ca6313889308326e7bb303d0d302c5cc9e523b2f2e6c73deb", size = 8042149, upload-time = "2025-05-08T19:09:42.413Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4a/9a/0ff45b6bfa42bb16de597e6058edf2361c298ad5ef93b327728145161bbf/matplotlib-3.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c21ae75651c0231b3ba014b6d5e08fb969c40cdb5a011e33e99ed0c9ea86ecb", size = 8453719, upload-time = "2025-05-08T19:09:44.901Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/85/c7/1866e972fed6d71ef136efbc980d4d1854ab7ef1ea8152bbd995ca231c81/matplotlib-3.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a49e39755580b08e30e3620efc659330eac5d6534ab7eae50fa5e31f53ee4e30", size = 8590801, upload-time = "2025-05-08T19:09:47.404Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5d/b9/748f6626d534ab7e255bdc39dc22634d337cf3ce200f261b5d65742044a1/matplotlib-3.10.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cf4636203e1190871d3a73664dea03d26fb019b66692cbfd642faafdad6208e8", size = 9402111, upload-time = "2025-05-08T19:09:49.474Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1f/78/8bf07bd8fb67ea5665a6af188e70b57fcb2ab67057daa06b85a08e59160a/matplotlib-3.10.3-cp310-cp310-win_amd64.whl", hash = "sha256:fd5641a9bb9d55f4dd2afe897a53b537c834b9012684c8444cc105895c8c16fd", size = 8057213, upload-time = "2025-05-08T19:09:51.489Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f5/bd/af9f655456f60fe1d575f54fb14704ee299b16e999704817a7645dfce6b0/matplotlib-3.10.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0ef061f74cd488586f552d0c336b2f078d43bc00dc473d2c3e7bfee2272f3fa8", size = 8178873, upload-time = "2025-05-08T19:09:53.857Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c2/86/e1c86690610661cd716eda5f9d0b35eaf606ae6c9b6736687cfc8f2d0cd8/matplotlib-3.10.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d96985d14dc5f4a736bbea4b9de9afaa735f8a0fc2ca75be2fa9e96b2097369d", size = 8052205, upload-time = "2025-05-08T19:09:55.684Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/54/51/a9f8e49af3883dacddb2da1af5fca1f7468677f1188936452dd9aaaeb9ed/matplotlib-3.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c5f0283da91e9522bdba4d6583ed9d5521566f63729ffb68334f86d0bb98049", size = 8465823, upload-time = "2025-05-08T19:09:57.442Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e7/e3/c82963a3b86d6e6d5874cbeaa390166458a7f1961bab9feb14d3d1a10f02/matplotlib-3.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdfa07c0ec58035242bc8b2c8aae37037c9a886370eef6850703d7583e19964b", size = 8606464, upload-time = "2025-05-08T19:09:59.471Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0e/34/24da1027e7fcdd9e82da3194c470143c551852757a4b473a09a012f5b945/matplotlib-3.10.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c0b9849a17bce080a16ebcb80a7b714b5677d0ec32161a2cc0a8e5a6030ae220", size = 9413103, upload-time = "2025-05-08T19:10:03.208Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a6/da/948a017c3ea13fd4a97afad5fdebe2f5bbc4d28c0654510ce6fd6b06b7bd/matplotlib-3.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:eef6ed6c03717083bc6d69c2d7ee8624205c29a8e6ea5a31cd3492ecdbaee1e1", size = 8065492, upload-time = "2025-05-08T19:10:05.271Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/eb/43/6b80eb47d1071f234ef0c96ca370c2ca621f91c12045f1401b5c9b28a639/matplotlib-3.10.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0ab1affc11d1f495ab9e6362b8174a25afc19c081ba5b0775ef00533a4236eea", size = 8179689, upload-time = "2025-05-08T19:10:07.602Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0f/70/d61a591958325c357204870b5e7b164f93f2a8cca1dc6ce940f563909a13/matplotlib-3.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2a818d8bdcafa7ed2eed74487fdb071c09c1ae24152d403952adad11fa3c65b4", size = 8050466, upload-time = "2025-05-08T19:10:09.383Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e7/75/70c9d2306203148cc7902a961240c5927dd8728afedf35e6a77e105a2985/matplotlib-3.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:748ebc3470c253e770b17d8b0557f0aa85cf8c63fd52f1a61af5b27ec0b7ffee", size = 8456252, upload-time = "2025-05-08T19:10:11.958Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c4/91/ba0ae1ff4b3f30972ad01cd4a8029e70a0ec3b8ea5be04764b128b66f763/matplotlib-3.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed70453fd99733293ace1aec568255bc51c6361cb0da94fa5ebf0649fdb2150a", size = 8601321, upload-time = "2025-05-08T19:10:14.47Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d2/88/d636041eb54a84b889e11872d91f7cbf036b3b0e194a70fa064eb8b04f7a/matplotlib-3.10.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dbed9917b44070e55640bd13419de83b4c918e52d97561544814ba463811cbc7", size = 9406972, upload-time = "2025-05-08T19:10:16.569Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b1/79/0d1c165eac44405a86478082e225fce87874f7198300bbebc55faaf6d28d/matplotlib-3.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:cf37d8c6ef1a48829443e8ba5227b44236d7fcaf7647caa3178a4ff9f7a5be05", size = 8067954, upload-time = "2025-05-08T19:10:18.663Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3d/d1/f54d43e95384b312ffa4a74a4326c722f3b8187aaaa12e9a84cdf3037131/matplotlib-3.10.3-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:86ab63d66bbc83fdb6733471d3bff40897c1e9921cba112accd748eee4bce5e4", size = 8162896, upload-time = "2025-05-08T19:10:46.432Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/24/a4/fbfc00c2346177c95b353dcf9b5a004106abe8730a62cb6f27e79df0a698/matplotlib-3.10.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:a48f9c08bf7444b5d2391a83e75edb464ccda3c380384b36532a0962593a1751", size = 8039702, upload-time = "2025-05-08T19:10:49.634Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6a/b9/59e120d24a2ec5fc2d30646adb2efb4621aab3c6d83d66fb2a7a182db032/matplotlib-3.10.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb73d8aa75a237457988f9765e4dfe1c0d2453c5ca4eabc897d4309672c8e014", size = 8594298, upload-time = "2025-05-08T19:10:51.738Z" }, +] + +[[package]] +name = "mcp" +version = "1.12.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "anyio" }, + { name = "httpx" }, + { name = "httpx-sse" }, + { name = "jsonschema" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "python-multipart" }, + { name = "pywin32", marker = "sys_platform == 'win32'" }, + { name = "sse-starlette" }, + { name = "starlette" }, + { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/66/85/f36d538b1286b7758f35c1b69d93f2719d2df90c01bd074eadd35f6afc35/mcp-1.12.2.tar.gz", hash = "sha256:a4b7c742c50ce6ed6d6a6c096cca0e3893f5aecc89a59ed06d47c4e6ba41edcc", size = 426202, upload-time = "2025-07-24T18:29:05.175Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2f/cf/3fd38cfe43962452e4bfadc6966b2ea0afaf8e0286cb3991c247c8c33ebd/mcp-1.12.2-py3-none-any.whl", hash = "sha256:b86d584bb60193a42bd78aef01882c5c42d614e416cbf0480149839377ab5a5f", size = 158473, upload-time = "2025-07-24T18:29:03.419Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "mini-racer" +version = "0.12.4" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8c/2d/e051f58e17117b1b8b11a7d17622c1528fa9002c553943c6b677c1b412da/mini_racer-0.12.4.tar.gz", hash = "sha256:84c67553ce9f3736d4c617d8a3f882949d37a46cfb47fe11dab33dd6704e62a4", size = 447529, upload-time = "2024-06-20T14:44:39.992Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/71/fe/1452b6c74cae9e8cd7b6a16d8b1ef08bba4dd0ed373a95f3b401c2e712ea/mini_racer-0.12.4-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:bce8a3cee946575a352f5e65335903bc148da42c036d0c738ac67e931600e455", size = 15701219, upload-time = "2024-06-20T14:44:21.96Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/99/ae/c22478eff26e6136341e6b40d34f8d285f910ca4d2e2a0ca4703ef87be79/mini_racer-0.12.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:56c832e6ac2db6a304d1e8e80030615297aafbc6940f64f3479af4ba16abccd5", size = 14566436, upload-time = "2024-06-20T14:44:24.496Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/44/89/f062aa116b14fcace91f0af86a37605f0ba7c07a01c8101b5ea104d489b1/mini_racer-0.12.4-py3-none-manylinux_2_31_aarch64.whl", hash = "sha256:b82c4bd2976e280ed0a72c9c2de01b13f18ccfbe6f4892cbc22aae04410fac3c", size = 14931664, upload-time = "2024-06-20T14:44:27.385Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9c/a1/09122c88a0dd0a2141b0ea068d70f5d31acd0015d6f3157b8efd3ff7e026/mini_racer-0.12.4-py3-none-manylinux_2_31_x86_64.whl", hash = "sha256:69a1c44d02a9069b881684cef15a2d747fe0743df29eadc881fda7002aae5fd2", size = 14955238, upload-time = "2024-06-20T14:44:30.217Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6c/3b/826e41f92631560e5c6ca2aa4ef9005bdccf9290c1e7ddebe05e0a3b8c7c/mini_racer-0.12.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:499dbc267dfe60e954bc1b6c3787f7b10fc41fe1975853c9a6ddb55eb83dc4d9", size = 15211136, upload-time = "2024-06-20T14:44:33.509Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e5/37/15b30316630d1f63b025f058dc92efa75931a37315c34ca07f80be2cc405/mini_racer-0.12.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:231f949f5787d18351939f1fe59e5a6fe134bccb5ecf8f836b9beab69d91c8d9", size = 15128684, upload-time = "2024-06-20T14:44:35.644Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5c/0e/a9943f90b4a8a6d3849b81a00a00d2db128d876365385af382a0e2caf191/mini_racer-0.12.4-py3-none-win_amd64.whl", hash = "sha256:9446e3bd6a4eb9fbedf1861326f7476080995a31c9b69308acef17e5b7ecaa1b", size = 13674040, upload-time = "2024-06-20T14:44:37.851Z" }, +] + +[[package]] +name = "minio" +version = "7.2.4" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "argon2-cffi" }, + { name = "certifi" }, + { name = "pycryptodome" }, + { name = "typing-extensions" }, + { name = "urllib3" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3a/2e/7bd24eb2e02a19a03bd0e73e59c051c62c62cabdd305ccbc59a90143752c/minio-7.2.4.tar.gz", hash = "sha256:d504d8464e5198fb74dd9b572cc88b185ae7997c17705e8c09f3fef2f439d984", size = 134100, upload-time = "2024-02-11T00:41:07.19Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a8/29/17ec9cecedad692cf18abd0b5e57d7008d1dda8929915e7cfee76ea0e849/minio-7.2.4-py3-none-any.whl", hash = "sha256:91b51c21d25e3ee6d51f52eab126d6c974371add0d77951e42c322a59c5533e7", size = 92644, upload-time = "2024-02-11T00:41:04.907Z" }, +] + +[[package]] +name = "mistralai" +version = "0.4.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "httpx" }, + { name = "orjson" }, + { name = "pydantic" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fa/20/4204f461588310b3a7ffbbbb7fa573493dc1c8185d376ee72516c04575bf/mistralai-0.4.2.tar.gz", hash = "sha256:5eb656710517168ae053f9847b0bb7f617eda07f1f93f946ad6c91a4d407fd93", size = 14234, upload-time = "2024-07-04T09:22:43.992Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4f/fe/79dad76b8d94b62d9e2aab8446183190e1dc384c617d06c3c93307850e11/mistralai-0.4.2-py3-none-any.whl", hash = "sha256:63c98eea139585f0a3b2c4c6c09c453738bac3958055e6f2362d3866e96b0168", size = 20334, upload-time = "2024-07-04T09:22:42.211Z" }, +] + +[[package]] +name = "mistune" +version = "3.1.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c4/79/bda47f7dd7c3c55770478d6d02c9960c430b0cf1773b72366ff89126ea31/mistune-3.1.3.tar.gz", hash = "sha256:a7035c21782b2becb6be62f8f25d3df81ccb4d6fa477a6525b15af06539f02a0", size = 94347, upload-time = "2025-03-19T14:27:24.955Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/01/4d/23c4e4f09da849e127e9f123241946c23c1e30f45a88366879e064211815/mistune-3.1.3-py3-none-any.whl", hash = "sha256:1a32314113cff28aa6432e99e522677c8587fd83e3d51c29b82a52409c842bd9", size = 53410, upload-time = "2025-03-19T14:27:23.451Z" }, +] + +[[package]] +name = "mmh3" +version = "4.1.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/63/96/aa247e82878b123468f0079ce2ac77e948315bab91ce45d2934a62e0af95/mmh3-4.1.0.tar.gz", hash = "sha256:a1cf25348b9acd229dda464a094d6170f47d2850a1fcb762a3b6172d2ce6ca4a", size = 26357, upload-time = "2024-01-09T06:46:04.536Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ef/5a/8609dc74421858f7e94a89dc69221ab9b2c14d0d63a139b46ec190eedc44/mmh3-4.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:be5ac76a8b0cd8095784e51e4c1c9c318c19edcd1709a06eb14979c8d850c31a", size = 39433, upload-time = "2024-01-09T06:44:25.903Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/93/6c/e7a0f07c7082c76964b1ff46aa852f36e2ec6a9c3530dec0afa0b3162fc2/mmh3-4.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:98a49121afdfab67cd80e912b36404139d7deceb6773a83620137aaa0da5714c", size = 29280, upload-time = "2024-01-09T06:44:27.035Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/76/84/60ca728ec7d7e1779a98000d64941c6221786124b4f07bf105a627055890/mmh3-4.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5259ac0535874366e7d1a5423ef746e0d36a9e3c14509ce6511614bdc5a7ef5b", size = 30130, upload-time = "2024-01-09T06:44:28.502Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2a/22/f2ec190b491f712d9ef5ea6252204b6f05255ac9af54a7b505adc3128aed/mmh3-4.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5950827ca0453a2be357696da509ab39646044e3fa15cad364eb65d78797437", size = 68837, upload-time = "2024-01-09T06:44:29.959Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ae/b9/c1e8065671e1d2f4e280c9c57389e74964f4a5792cac26717ad592002c7d/mmh3-4.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1dd0f652ae99585b9dd26de458e5f08571522f0402155809fd1dc8852a613a39", size = 72275, upload-time = "2024-01-09T06:44:31.02Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6b/18/92bbdb102ab2b4e80084e927187d871758280eb067c649693e42bfc6d0d1/mmh3-4.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99d25548070942fab1e4a6f04d1626d67e66d0b81ed6571ecfca511f3edf07e6", size = 70919, upload-time = "2024-01-09T06:44:32.581Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e2/cd/391ce1d1bb559871a5d3a6bbb30b82bf51d3e3b42c4e8589cccb201953da/mmh3-4.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53db8d9bad3cb66c8f35cbc894f336273f63489ce4ac416634932e3cbe79eb5b", size = 65885, upload-time = "2024-01-09T06:44:34.462Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/03/87/4b01a43336bd506478850d1bc3d180648b2d26b4acf1fc4bf1df72bf562f/mmh3-4.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75da0f615eb55295a437264cc0b736753f830b09d102aa4c2a7d719bc445ec05", size = 67610, upload-time = "2024-01-09T06:44:35.589Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e8/12/b464149a1b7181c7ce431ebf3d24fa994863f2f1abc75b78d202dde966e0/mmh3-4.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b926b07fd678ea84b3a2afc1fa22ce50aeb627839c44382f3d0291e945621e1a", size = 74888, upload-time = "2024-01-09T06:44:36.532Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fc/3e/f4eb45a23fc17b970394c1fe74eba157514577ae2d63757684241651d754/mmh3-4.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c5b053334f9b0af8559d6da9dc72cef0a65b325ebb3e630c680012323c950bb6", size = 72969, upload-time = "2024-01-09T06:44:37.473Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c0/3b/83934fd9494371357da0ca026d55ad427c199d611b97b6ffeecacfd8e720/mmh3-4.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:5bf33dc43cd6de2cb86e0aa73a1cc6530f557854bbbe5d59f41ef6de2e353d7b", size = 80338, upload-time = "2024-01-09T06:44:38.523Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b6/c4/5bcd709ea7269173d7e925402f05e05cf12194ef53cc9912a5ad166f8ded/mmh3-4.1.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:fa7eacd2b830727ba3dd65a365bed8a5c992ecd0c8348cf39a05cc77d22f4970", size = 76580, upload-time = "2024-01-09T06:44:39.505Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/da/6a/4c0680d64475e551d7f4cc78bf0fd247c711ed2717f6bb311934993d1e69/mmh3-4.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:42dfd6742b9e3eec599f85270617debfa0bbb913c545bb980c8a4fa7b2d047da", size = 75325, upload-time = "2024-01-09T06:44:40.532Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/70/bc/e2ed99e580b3dd121f6462147bd5f521c57b3c81c692aa2d416b0678c89f/mmh3-4.1.0-cp310-cp310-win32.whl", hash = "sha256:2974ad343f0d39dcc88e93ee6afa96cedc35a9883bc067febd7ff736e207fa47", size = 31235, upload-time = "2024-01-09T06:44:41.467Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/73/2b/3aec865da7feb52830782d9fb7c54115cc18815680c244301adf9080622f/mmh3-4.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:74699a8984ded645c1a24d6078351a056f5a5f1fe5838870412a68ac5e28d865", size = 31271, upload-time = "2024-01-09T06:44:42.881Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/17/2a/925439189ccf562bdcb839aed6263d718359f0c376d673beb3b83d3864ac/mmh3-4.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:f0dc874cedc23d46fc488a987faa6ad08ffa79e44fb08e3cd4d4cf2877c00a00", size = 30147, upload-time = "2024-01-09T06:44:44.173Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2e/d6/86beea107e7e9700df9522466346c23a2f54faa81337c86fd17002aa95a6/mmh3-4.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3280a463855b0eae64b681cd5b9ddd9464b73f81151e87bb7c91a811d25619e6", size = 39427, upload-time = "2024-01-09T06:44:45.686Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1c/08/65fa5489044e2afc304e8540c6c607d5d7b136ddc5cd8315c13de0adc34c/mmh3-4.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:97ac57c6c3301769e757d444fa7c973ceb002cb66534b39cbab5e38de61cd896", size = 29281, upload-time = "2024-01-09T06:44:46.554Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b3/aa/98511d3ea3f6ba958136d913be3be3c1009be935a20ecc7b2763f0a605b6/mmh3-4.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a7b6502cdb4dbd880244818ab363c8770a48cdccecf6d729ade0241b736b5ec0", size = 30130, upload-time = "2024-01-09T06:44:47.463Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3c/b7/1a93f81643435b0e57f1046c4ffe46f0214693eaede0d9b0a1a236776e70/mmh3-4.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52ba2da04671a9621580ddabf72f06f0e72c1c9c3b7b608849b58b11080d8f14", size = 69072, upload-time = "2024-01-09T06:44:48.385Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/45/9e/2ff70246aefd9cf146bc6a420c28ed475a0d1a325f31ee203be02f9215d4/mmh3-4.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a5fef4c4ecc782e6e43fbeab09cff1bac82c998a1773d3a5ee6a3605cde343e", size = 72470, upload-time = "2024-01-09T06:44:49.291Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/dc/cb/57bc1fdbdbe6837aebfca982494e23e2498ee2a89585c9054713b22e4167/mmh3-4.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5135358a7e00991f73b88cdc8eda5203bf9de22120d10a834c5761dbeb07dd13", size = 71251, upload-time = "2024-01-09T06:44:50.839Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4d/c2/46d7d2721b69fbdfd30231309e6395f62ff6744e5c00dd8113b9faa06fba/mmh3-4.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cff9ae76a54f7c6fe0167c9c4028c12c1f6de52d68a31d11b6790bb2ae685560", size = 66035, upload-time = "2024-01-09T06:44:52.407Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6f/a4/7ba4bcc838818bcf018e26d118d5ddb605c23c4fad040dc4d811f1cfcb04/mmh3-4.1.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6f02576a4d106d7830ca90278868bf0983554dd69183b7bbe09f2fcd51cf54f", size = 67844, upload-time = "2024-01-09T06:44:53.566Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/71/ed/8e80d1038e7bb15eaf739711d1fc36f2341acb6b1b95fa77003f2799c91e/mmh3-4.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:073d57425a23721730d3ff5485e2da489dd3c90b04e86243dd7211f889898106", size = 76724, upload-time = "2024-01-09T06:44:54.51Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1c/22/a6a70ca81f0ce8fe2f3a68d89c1184c2d2d0fbe0ee305da50e972c5ff9fa/mmh3-4.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:71e32ddec7f573a1a0feb8d2cf2af474c50ec21e7a8263026e8d3b4b629805db", size = 75004, upload-time = "2024-01-09T06:44:55.517Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/73/20/abe50b605760f1f5b6e0b436c650649e69ca478d0f41b154f300367c09e4/mmh3-4.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7cbb20b29d57e76a58b40fd8b13a9130db495a12d678d651b459bf61c0714cea", size = 82230, upload-time = "2024-01-09T06:44:56.538Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/45/80/a1fc99d3ee50b573df0bfbb1ad518463af78d2ebca44bfca3b3f9473d651/mmh3-4.1.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:a42ad267e131d7847076bb7e31050f6c4378cd38e8f1bf7a0edd32f30224d5c9", size = 78679, upload-time = "2024-01-09T06:44:57.477Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9e/51/6c9ee2ddf3b386f45ff83b6926a5e826635757d91dab04cbf16eee05f9a7/mmh3-4.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4a013979fc9390abadc445ea2527426a0e7a4495c19b74589204f9b71bcaafeb", size = 77382, upload-time = "2024-01-09T06:44:59.02Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ee/fa/4b377f244c27fac5f0343cc4dc0d2eb0a08049afc8d5322d07be7461a768/mmh3-4.1.0-cp311-cp311-win32.whl", hash = "sha256:1d3b1cdad7c71b7b88966301789a478af142bddcb3a2bee563f7a7d40519a00f", size = 31232, upload-time = "2024-01-09T06:45:01.285Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d1/b0/500ef56c29b276d796bfdb47c16d34fa18a68945e4d730a6fa7d483583ed/mmh3-4.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:0dc6dc32eb03727467da8e17deffe004fbb65e8b5ee2b502d36250d7a3f4e2ec", size = 31276, upload-time = "2024-01-09T06:45:03.417Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cc/84/94795e6e710c3861f8f355a12be9c9f4b8433a538c983e75bd4c00496a8a/mmh3-4.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:9ae3a5c1b32dda121c7dc26f9597ef7b01b4c56a98319a7fe86c35b8bc459ae6", size = 30142, upload-time = "2024-01-09T06:45:05.347Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/18/45/b4d41e86b00eed8c500adbe0007129861710e181c7f49c507ef6beae9496/mmh3-4.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0033d60c7939168ef65ddc396611077a7268bde024f2c23bdc283a19123f9e9c", size = 39495, upload-time = "2024-01-09T06:45:07.01Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a6/d4/f041b8704cb8d1aad3717105daa582e29818b78a540622dfed84cd00d88f/mmh3-4.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d6af3e2287644b2b08b5924ed3a88c97b87b44ad08e79ca9f93d3470a54a41c5", size = 29334, upload-time = "2024-01-09T06:45:08.022Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cb/bb/8f75378e1a83b323f9ed06248333c383e7dac614c2f95e1419965cb91693/mmh3-4.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d82eb4defa245e02bb0b0dc4f1e7ee284f8d212633389c91f7fba99ba993f0a2", size = 30144, upload-time = "2024-01-09T06:45:09.437Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3e/50/5e36c1945bd83e780a37361fc1999fc4c5a59ecc10a373557fdf0e58eb1f/mmh3-4.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba245e94b8d54765e14c2d7b6214e832557e7856d5183bc522e17884cab2f45d", size = 69094, upload-time = "2024-01-09T06:45:10.531Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/70/c7/6ae37e7519a938226469476b84bcea2650e2a2cc7a848e6a206ea98ecee3/mmh3-4.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb04e2feeabaad6231e89cd43b3d01a4403579aa792c9ab6fdeef45cc58d4ec0", size = 72611, upload-time = "2024-01-09T06:45:12.27Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5e/47/6613f69f57f1e5045e66b22fae9c2fb39ef754c455805d3917f6073e316e/mmh3-4.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e3b1a27def545ce11e36158ba5d5390cdbc300cfe456a942cc89d649cf7e3b2", size = 71462, upload-time = "2024-01-09T06:45:13.274Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e0/0a/e423db18ce7b479c4b96381a112b443f0985c611de420f95c58a9f934080/mmh3-4.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce0ab79ff736d7044e5e9b3bfe73958a55f79a4ae672e6213e92492ad5e734d5", size = 66165, upload-time = "2024-01-09T06:45:15.003Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4c/7b/bfeb68bee5bddc8baf7ef630b93edc0a533202d84eb076dbb6c77e7e5fd5/mmh3-4.1.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b02268be6e0a8eeb8a924d7db85f28e47344f35c438c1e149878bb1c47b1cd3", size = 68088, upload-time = "2024-01-09T06:45:16.192Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d4/a6/b82e30143997c05776887f5177f724e3b714aa7e7346fbe2ec70f52abcd0/mmh3-4.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:deb887f5fcdaf57cf646b1e062d56b06ef2f23421c80885fce18b37143cba828", size = 76241, upload-time = "2024-01-09T06:45:17.191Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6c/60/a3d5872cf7610fcb13e36c472476020c5cf217b23c092bad452eb7784407/mmh3-4.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:99dd564e9e2b512eb117bd0cbf0f79a50c45d961c2a02402787d581cec5448d5", size = 74538, upload-time = "2024-01-09T06:45:18.999Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f6/d5/742173a94c78f4edab71c04097f6f9150c47f8fd034d592f5f34a9444719/mmh3-4.1.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:08373082dfaa38fe97aa78753d1efd21a1969e51079056ff552e687764eafdfe", size = 81793, upload-time = "2024-01-09T06:45:20.534Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d0/7a/a1db0efe7c67b761d83be3d50e35ef26628ef56b3b8bc776d07412ee8b16/mmh3-4.1.0-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:54b9c6a2ea571b714e4fe28d3e4e2db37abfd03c787a58074ea21ee9a8fd1740", size = 78217, upload-time = "2024-01-09T06:45:21.761Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b3/78/1ff8da7c859cd09704e2f500588d171eda9688fcf6f29e028ef261262a16/mmh3-4.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a7b1edf24c69e3513f879722b97ca85e52f9032f24a52284746877f6a7304086", size = 77052, upload-time = "2024-01-09T06:45:22.824Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ed/c7/cf16ace81fc9fbe54a75c914306252af26c6ea485366bb3b579bf6e3dbb8/mmh3-4.1.0-cp312-cp312-win32.whl", hash = "sha256:411da64b951f635e1e2284b71d81a5a83580cea24994b328f8910d40bed67276", size = 31277, upload-time = "2024-01-09T06:45:24.009Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d2/0b/b3b1637dca9414451edf287fd91e667e7231d5ffd7498137fe011951fc0a/mmh3-4.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:bebc3ecb6ba18292e3d40c8712482b4477abd6981c2ebf0e60869bd90f8ac3a9", size = 31318, upload-time = "2024-01-09T06:45:25.169Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/dd/6c/c0f06040c58112ccbd0df989055ede98f7c1a1f392dc6a3fc63ec6c124ec/mmh3-4.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:168473dd608ade6a8d2ba069600b35199a9af837d96177d3088ca91f2b3798e3", size = 30147, upload-time = "2024-01-09T06:45:26.214Z" }, +] + +[[package]] +name = "mpmath" +version = "1.3.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" }, +] + +[[package]] +name = "msal" +version = "1.33.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "pyjwt", extra = ["crypto"] }, + { name = "requests" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d5/da/81acbe0c1fd7e9e4ec35f55dadeba9833a847b9a6ba2e2d1e4432da901dd/msal-1.33.0.tar.gz", hash = "sha256:836ad80faa3e25a7d71015c990ce61f704a87328b1e73bcbb0623a18cbf17510", size = 153801, upload-time = "2025-07-22T19:36:33.693Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/86/5b/fbc73e91f7727ae1e79b21ed833308e99dc11cc1cd3d4717f579775de5e9/msal-1.33.0-py3-none-any.whl", hash = "sha256:c0cd41cecf8eaed733ee7e3be9e040291eba53b0f262d3ae9c58f38b04244273", size = 116853, upload-time = "2025-07-22T19:36:32.403Z" }, +] + +[[package]] +name = "msal-extensions" +version = "1.3.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "msal" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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 = "msgspec" +version = "0.19.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cf/9b/95d8ce458462b8b71b8a70fa94563b2498b89933689f3a7b8911edfae3d7/msgspec-0.19.0.tar.gz", hash = "sha256:604037e7cd475345848116e89c553aa9a233259733ab51986ac924ab1b976f8e", size = 216934, upload-time = "2024-12-27T17:40:28.597Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/13/40/817282b42f58399762267b30deb8ac011d8db373f8da0c212c85fbe62b8f/msgspec-0.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d8dd848ee7ca7c8153462557655570156c2be94e79acec3561cf379581343259", size = 190019, upload-time = "2024-12-27T17:39:13.803Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/92/99/bd7ed738c00f223a8119928661167a89124140792af18af513e6519b0d54/msgspec-0.19.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0553bbc77662e5708fe66aa75e7bd3e4b0f209709c48b299afd791d711a93c36", size = 183680, upload-time = "2024-12-27T17:39:17.847Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e5/27/322badde18eb234e36d4a14122b89edd4e2973cdbc3da61ca7edf40a1ccd/msgspec-0.19.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe2c4bf29bf4e89790b3117470dea2c20b59932772483082c468b990d45fb947", size = 209334, upload-time = "2024-12-27T17:39:19.065Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c6/65/080509c5774a1592b2779d902a70b5fe008532759927e011f068145a16cb/msgspec-0.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e87ecfa9795ee5214861eab8326b0e75475c2e68a384002aa135ea2a27d909", size = 211551, upload-time = "2024-12-27T17:39:21.767Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6f/2e/1c23c6b4ca6f4285c30a39def1054e2bee281389e4b681b5e3711bd5a8c9/msgspec-0.19.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3c4ec642689da44618f68c90855a10edbc6ac3ff7c1d94395446c65a776e712a", size = 215099, upload-time = "2024-12-27T17:39:24.71Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/83/fe/95f9654518879f3359d1e76bc41189113aa9102452170ab7c9a9a4ee52f6/msgspec-0.19.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2719647625320b60e2d8af06b35f5b12d4f4d281db30a15a1df22adb2295f633", size = 218211, upload-time = "2024-12-27T17:39:27.396Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/79/f6/71ca7e87a1fb34dfe5efea8156c9ef59dd55613aeda2ca562f122cd22012/msgspec-0.19.0-cp310-cp310-win_amd64.whl", hash = "sha256:695b832d0091edd86eeb535cd39e45f3919f48d997685f7ac31acb15e0a2ed90", size = 186174, upload-time = "2024-12-27T17:39:29.647Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/24/d4/2ec2567ac30dab072cce3e91fb17803c52f0a37aab6b0c24375d2b20a581/msgspec-0.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa77046904db764b0462036bc63ef71f02b75b8f72e9c9dd4c447d6da1ed8f8e", size = 187939, upload-time = "2024-12-27T17:39:32.347Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2b/c0/18226e4328897f4f19875cb62bb9259fe47e901eade9d9376ab5f251a929/msgspec-0.19.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:047cfa8675eb3bad68722cfe95c60e7afabf84d1bd8938979dd2b92e9e4a9551", size = 182202, upload-time = "2024-12-27T17:39:33.633Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/81/25/3a4b24d468203d8af90d1d351b77ea3cffb96b29492855cf83078f16bfe4/msgspec-0.19.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e78f46ff39a427e10b4a61614a2777ad69559cc8d603a7c05681f5a595ea98f7", size = 209029, upload-time = "2024-12-27T17:39:35.023Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/85/2e/db7e189b57901955239f7689b5dcd6ae9458637a9c66747326726c650523/msgspec-0.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c7adf191e4bd3be0e9231c3b6dc20cf1199ada2af523885efc2ed218eafd011", size = 210682, upload-time = "2024-12-27T17:39:36.384Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/03/97/7c8895c9074a97052d7e4a1cc1230b7b6e2ca2486714eb12c3f08bb9d284/msgspec-0.19.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f04cad4385e20be7c7176bb8ae3dca54a08e9756cfc97bcdb4f18560c3042063", size = 214003, upload-time = "2024-12-27T17:39:39.097Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/61/61/e892997bcaa289559b4d5869f066a8021b79f4bf8e955f831b095f47a4cd/msgspec-0.19.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:45c8fb410670b3b7eb884d44a75589377c341ec1392b778311acdbfa55187716", size = 216833, upload-time = "2024-12-27T17:39:41.203Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ce/3d/71b2dffd3a1c743ffe13296ff701ee503feaebc3f04d0e75613b6563c374/msgspec-0.19.0-cp311-cp311-win_amd64.whl", hash = "sha256:70eaef4934b87193a27d802534dc466778ad8d536e296ae2f9334e182ac27b6c", size = 186184, upload-time = "2024-12-27T17:39:43.702Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b2/5f/a70c24f075e3e7af2fae5414c7048b0e11389685b7f717bb55ba282a34a7/msgspec-0.19.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f98bd8962ad549c27d63845b50af3f53ec468b6318400c9f1adfe8b092d7b62f", size = 190485, upload-time = "2024-12-27T17:39:44.974Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/89/b0/1b9763938cfae12acf14b682fcf05c92855974d921a5a985ecc197d1c672/msgspec-0.19.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:43bbb237feab761b815ed9df43b266114203f53596f9b6e6f00ebd79d178cdf2", size = 183910, upload-time = "2024-12-27T17:39:46.401Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/87/81/0c8c93f0b92c97e326b279795f9c5b956c5a97af28ca0fbb9fd86c83737a/msgspec-0.19.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cfc033c02c3e0aec52b71710d7f84cb3ca5eb407ab2ad23d75631153fdb1f12", size = 210633, upload-time = "2024-12-27T17:39:49.099Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d0/ef/c5422ce8af73928d194a6606f8ae36e93a52fd5e8df5abd366903a5ca8da/msgspec-0.19.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d911c442571605e17658ca2b416fd8579c5050ac9adc5e00c2cb3126c97f73bc", size = 213594, upload-time = "2024-12-27T17:39:51.204Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/19/2b/4137bc2ed45660444842d042be2cf5b18aa06efd2cda107cff18253b9653/msgspec-0.19.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:757b501fa57e24896cf40a831442b19a864f56d253679f34f260dcb002524a6c", size = 214053, upload-time = "2024-12-27T17:39:52.866Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9d/e6/8ad51bdc806aac1dc501e8fe43f759f9ed7284043d722b53323ea421c360/msgspec-0.19.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5f0f65f29b45e2816d8bded36e6b837a4bf5fb60ec4bc3c625fa2c6da4124537", size = 219081, upload-time = "2024-12-27T17:39:55.142Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b1/ef/27dd35a7049c9a4f4211c6cd6a8c9db0a50647546f003a5867827ec45391/msgspec-0.19.0-cp312-cp312-win_amd64.whl", hash = "sha256:067f0de1c33cfa0b6a8206562efdf6be5985b988b53dd244a8e06f993f27c8c0", size = 187467, upload-time = "2024-12-27T17:39:56.531Z" }, +] + +[[package]] +name = "msoffcrypto-tool" +version = "5.4.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "olefile" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d2/b7/0fd6573157e0ec60c0c470e732ab3322fba4d2834fd24e1088d670522a01/msoffcrypto_tool-5.4.2.tar.gz", hash = "sha256:44b545adba0407564a0cc3d6dde6ca36b7c0fdf352b85bca51618fa1d4817370", size = 41183, upload-time = "2024-08-08T15:50:28.462Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/03/54/7f6d3d9acad083dae8c22d9ab483b657359a1bf56fee1d7af88794677707/msoffcrypto_tool-5.4.2-py3-none-any.whl", hash = "sha256:274fe2181702d1e5a107ec1b68a4c9fea997a44972ae1cc9ae0cb4f6a50fef0e", size = 48713, upload-time = "2024-08-08T15:50:27.093Z" }, +] + +[[package]] +name = "multidict" +version = "6.6.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3d/2c/5dad12e82fbdf7470f29bff2171484bf07cb3b16ada60a6589af8f376440/multidict-6.6.3.tar.gz", hash = "sha256:798a9eb12dab0a6c2e29c1de6f3468af5cb2da6053a20dfa3344907eed0937cc", size = 101006, upload-time = "2025-06-30T15:53:46.929Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0b/67/414933982bce2efce7cbcb3169eaaf901e0f25baec69432b4874dfb1f297/multidict-6.6.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a2be5b7b35271f7fff1397204ba6708365e3d773579fe2a30625e16c4b4ce817", size = 77017, upload-time = "2025-06-30T15:50:58.931Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8a/fe/d8a3ee1fad37dc2ef4f75488b0d9d4f25bf204aad8306cbab63d97bff64a/multidict-6.6.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:12f4581d2930840295c461764b9a65732ec01250b46c6b2c510d7ee68872b140", size = 44897, upload-time = "2025-06-30T15:51:00.999Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1f/e0/265d89af8c98240265d82b8cbcf35897f83b76cd59ee3ab3879050fd8c45/multidict-6.6.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dd7793bab517e706c9ed9d7310b06c8672fd0aeee5781bfad612f56b8e0f7d14", size = 44574, upload-time = "2025-06-30T15:51:02.449Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e6/05/6b759379f7e8e04ccc97cfb2a5dcc5cdbd44a97f072b2272dc51281e6a40/multidict-6.6.3-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:72d8815f2cd3cf3df0f83cac3f3ef801d908b2d90409ae28102e0553af85545a", size = 225729, upload-time = "2025-06-30T15:51:03.794Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4e/f5/8d5a15488edd9a91fa4aad97228d785df208ed6298580883aa3d9def1959/multidict-6.6.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:531e331a2ee53543ab32b16334e2deb26f4e6b9b28e41f8e0c87e99a6c8e2d69", size = 242515, upload-time = "2025-06-30T15:51:05.002Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6e/b5/a8f317d47d0ac5bb746d6d8325885c8967c2a8ce0bb57be5399e3642cccb/multidict-6.6.3-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:42ca5aa9329a63be8dc49040f63817d1ac980e02eeddba763a9ae5b4027b9c9c", size = 222224, upload-time = "2025-06-30T15:51:06.148Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/76/88/18b2a0d5e80515fa22716556061189c2853ecf2aa2133081ebbe85ebea38/multidict-6.6.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:208b9b9757060b9faa6f11ab4bc52846e4f3c2fb8b14d5680c8aac80af3dc751", size = 253124, upload-time = "2025-06-30T15:51:07.375Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/62/bf/ebfcfd6b55a1b05ef16d0775ae34c0fe15e8dab570d69ca9941073b969e7/multidict-6.6.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:acf6b97bd0884891af6a8b43d0f586ab2fcf8e717cbd47ab4bdddc09e20652d8", size = 251529, upload-time = "2025-06-30T15:51:08.691Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/44/11/780615a98fd3775fc309d0234d563941af69ade2df0bb82c91dda6ddaea1/multidict-6.6.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:68e9e12ed00e2089725669bdc88602b0b6f8d23c0c95e52b95f0bc69f7fe9b55", size = 241627, upload-time = "2025-06-30T15:51:10.605Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/28/3d/35f33045e21034b388686213752cabc3a1b9d03e20969e6fa8f1b1d82db1/multidict-6.6.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:05db2f66c9addb10cfa226e1acb363450fab2ff8a6df73c622fefe2f5af6d4e7", size = 239351, upload-time = "2025-06-30T15:51:12.18Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6e/cc/ff84c03b95b430015d2166d9aae775a3985d757b94f6635010d0038d9241/multidict-6.6.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:0db58da8eafb514db832a1b44f8fa7906fdd102f7d982025f816a93ba45e3dcb", size = 233429, upload-time = "2025-06-30T15:51:13.533Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2e/f0/8cd49a0b37bdea673a4b793c2093f2f4ba8e7c9d6d7c9bd672fd6d38cd11/multidict-6.6.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:14117a41c8fdb3ee19c743b1c027da0736fdb79584d61a766da53d399b71176c", size = 243094, upload-time = "2025-06-30T15:51:14.815Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/96/19/5d9a0cfdafe65d82b616a45ae950975820289069f885328e8185e64283c2/multidict-6.6.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:877443eaaabcd0b74ff32ebeed6f6176c71850feb7d6a1d2db65945256ea535c", size = 248957, upload-time = "2025-06-30T15:51:16.076Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e6/dc/c90066151da87d1e489f147b9b4327927241e65f1876702fafec6729c014/multidict-6.6.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:70b72e749a4f6e7ed8fb334fa8d8496384840319512746a5f42fa0aec79f4d61", size = 243590, upload-time = "2025-06-30T15:51:17.413Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ec/39/458afb0cccbb0ee9164365273be3e039efddcfcb94ef35924b7dbdb05db0/multidict-6.6.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:43571f785b86afd02b3855c5ac8e86ec921b760298d6f82ff2a61daf5a35330b", size = 237487, upload-time = "2025-06-30T15:51:19.039Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/35/38/0016adac3990426610a081787011177e661875546b434f50a26319dc8372/multidict-6.6.3-cp310-cp310-win32.whl", hash = "sha256:20c5a0c3c13a15fd5ea86c42311859f970070e4e24de5a550e99d7c271d76318", size = 41390, upload-time = "2025-06-30T15:51:20.362Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f3/d2/17897a8f3f2c5363d969b4c635aa40375fe1f09168dc09a7826780bfb2a4/multidict-6.6.3-cp310-cp310-win_amd64.whl", hash = "sha256:ab0a34a007704c625e25a9116c6770b4d3617a071c8a7c30cd338dfbadfe6485", size = 45954, upload-time = "2025-06-30T15:51:21.383Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2d/5f/d4a717c1e457fe44072e33fa400d2b93eb0f2819c4d669381f925b7cba1f/multidict-6.6.3-cp310-cp310-win_arm64.whl", hash = "sha256:769841d70ca8bdd140a715746199fc6473414bd02efd678d75681d2d6a8986c5", size = 42981, upload-time = "2025-06-30T15:51:22.809Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/08/f0/1a39863ced51f639c81a5463fbfa9eb4df59c20d1a8769ab9ef4ca57ae04/multidict-6.6.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:18f4eba0cbac3546b8ae31e0bbc55b02c801ae3cbaf80c247fcdd89b456ff58c", size = 76445, upload-time = "2025-06-30T15:51:24.01Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c9/0e/a7cfa451c7b0365cd844e90b41e21fab32edaa1e42fc0c9f68461ce44ed7/multidict-6.6.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef43b5dd842382329e4797c46f10748d8c2b6e0614f46b4afe4aee9ac33159df", size = 44610, upload-time = "2025-06-30T15:51:25.158Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c6/bb/a14a4efc5ee748cc1904b0748be278c31b9295ce5f4d2ef66526f410b94d/multidict-6.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf9bd1fd5eec01494e0f2e8e446a74a85d5e49afb63d75a9934e4a5423dba21d", size = 44267, upload-time = "2025-06-30T15:51:26.326Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c2/f8/410677d563c2d55e063ef74fe578f9d53fe6b0a51649597a5861f83ffa15/multidict-6.6.3-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:5bd8d6f793a787153956cd35e24f60485bf0651c238e207b9a54f7458b16d539", size = 230004, upload-time = "2025-06-30T15:51:27.491Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fd/df/2b787f80059314a98e1ec6a4cc7576244986df3e56b3c755e6fc7c99e038/multidict-6.6.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bf99b4daf908c73856bd87ee0a2499c3c9a3d19bb04b9c6025e66af3fd07462", size = 247196, upload-time = "2025-06-30T15:51:28.762Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/05/f2/f9117089151b9a8ab39f9019620d10d9718eec2ac89e7ca9d30f3ec78e96/multidict-6.6.3-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b9e59946b49dafaf990fd9c17ceafa62976e8471a14952163d10a7a630413a9", size = 225337, upload-time = "2025-06-30T15:51:30.025Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/93/2d/7115300ec5b699faa152c56799b089a53ed69e399c3c2d528251f0aeda1a/multidict-6.6.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e2db616467070d0533832d204c54eea6836a5e628f2cb1e6dfd8cd6ba7277cb7", size = 257079, upload-time = "2025-06-30T15:51:31.716Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/15/ea/ff4bab367623e39c20d3b07637225c7688d79e4f3cc1f3b9f89867677f9a/multidict-6.6.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7394888236621f61dcdd25189b2768ae5cc280f041029a5bcf1122ac63df79f9", size = 255461, upload-time = "2025-06-30T15:51:33.029Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/74/07/2c9246cda322dfe08be85f1b8739646f2c4c5113a1422d7a407763422ec4/multidict-6.6.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f114d8478733ca7388e7c7e0ab34b72547476b97009d643644ac33d4d3fe1821", size = 246611, upload-time = "2025-06-30T15:51:34.47Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a8/62/279c13d584207d5697a752a66ffc9bb19355a95f7659140cb1b3cf82180e/multidict-6.6.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cdf22e4db76d323bcdc733514bf732e9fb349707c98d341d40ebcc6e9318ef3d", size = 243102, upload-time = "2025-06-30T15:51:36.525Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/69/cc/e06636f48c6d51e724a8bc8d9e1db5f136fe1df066d7cafe37ef4000f86a/multidict-6.6.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e995a34c3d44ab511bfc11aa26869b9d66c2d8c799fa0e74b28a473a692532d6", size = 238693, upload-time = "2025-06-30T15:51:38.278Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/89/a4/66c9d8fb9acf3b226cdd468ed009537ac65b520aebdc1703dd6908b19d33/multidict-6.6.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:766a4a5996f54361d8d5a9050140aa5362fe48ce51c755a50c0bc3706460c430", size = 246582, upload-time = "2025-06-30T15:51:39.709Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cf/01/c69e0317be556e46257826d5449feb4e6aa0d18573e567a48a2c14156f1f/multidict-6.6.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:3893a0d7d28a7fe6ca7a1f760593bc13038d1d35daf52199d431b61d2660602b", size = 253355, upload-time = "2025-06-30T15:51:41.013Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c0/da/9cc1da0299762d20e626fe0042e71b5694f9f72d7d3f9678397cbaa71b2b/multidict-6.6.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:934796c81ea996e61914ba58064920d6cad5d99140ac3167901eb932150e2e56", size = 247774, upload-time = "2025-06-30T15:51:42.291Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e6/91/b22756afec99cc31105ddd4a52f95ab32b1a4a58f4d417979c570c4a922e/multidict-6.6.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9ed948328aec2072bc00f05d961ceadfd3e9bfc2966c1319aeaf7b7c21219183", size = 242275, upload-time = "2025-06-30T15:51:43.642Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/be/f1/adcc185b878036a20399d5be5228f3cbe7f823d78985d101d425af35c800/multidict-6.6.3-cp311-cp311-win32.whl", hash = "sha256:9f5b28c074c76afc3e4c610c488e3493976fe0e596dd3db6c8ddfbb0134dcac5", size = 41290, upload-time = "2025-06-30T15:51:45.264Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e0/d4/27652c1c6526ea6b4f5ddd397e93f4232ff5de42bea71d339bc6a6cc497f/multidict-6.6.3-cp311-cp311-win_amd64.whl", hash = "sha256:bc7f6fbc61b1c16050a389c630da0b32fc6d4a3d191394ab78972bf5edc568c2", size = 45942, upload-time = "2025-06-30T15:51:46.377Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/16/18/23f4932019804e56d3c2413e237f866444b774b0263bcb81df2fdecaf593/multidict-6.6.3-cp311-cp311-win_arm64.whl", hash = "sha256:d4e47d8faffaae822fb5cba20937c048d4f734f43572e7079298a6c39fb172cb", size = 42880, upload-time = "2025-06-30T15:51:47.561Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0e/a0/6b57988ea102da0623ea814160ed78d45a2645e4bbb499c2896d12833a70/multidict-6.6.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:056bebbeda16b2e38642d75e9e5310c484b7c24e3841dc0fb943206a72ec89d6", size = 76514, upload-time = "2025-06-30T15:51:48.728Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/07/7a/d1e92665b0850c6c0508f101f9cf0410c1afa24973e1115fe9c6a185ebf7/multidict-6.6.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e5f481cccb3c5c5e5de5d00b5141dc589c1047e60d07e85bbd7dea3d4580d63f", size = 45394, upload-time = "2025-06-30T15:51:49.986Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/52/6f/dd104490e01be6ef8bf9573705d8572f8c2d2c561f06e3826b081d9e6591/multidict-6.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:10bea2ee839a759ee368b5a6e47787f399b41e70cf0c20d90dfaf4158dfb4e55", size = 43590, upload-time = "2025-06-30T15:51:51.331Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/44/fe/06e0e01b1b0611e6581b7fd5a85b43dacc08b6cea3034f902f383b0873e5/multidict-6.6.3-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:2334cfb0fa9549d6ce2c21af2bfbcd3ac4ec3646b1b1581c88e3e2b1779ec92b", size = 237292, upload-time = "2025-06-30T15:51:52.584Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ce/71/4f0e558fb77696b89c233c1ee2d92f3e1d5459070a0e89153c9e9e804186/multidict-6.6.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8fee016722550a2276ca2cb5bb624480e0ed2bd49125b2b73b7010b9090e888", size = 258385, upload-time = "2025-06-30T15:51:53.913Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e3/25/cca0e68228addad24903801ed1ab42e21307a1b4b6dd2cf63da5d3ae082a/multidict-6.6.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5511cb35f5c50a2db21047c875eb42f308c5583edf96bd8ebf7d770a9d68f6d", size = 242328, upload-time = "2025-06-30T15:51:55.672Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6e/a3/46f2d420d86bbcb8fe660b26a10a219871a0fbf4d43cb846a4031533f3e0/multidict-6.6.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:712b348f7f449948e0a6c4564a21c7db965af900973a67db432d724619b3c680", size = 268057, upload-time = "2025-06-30T15:51:57.037Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9e/73/1c743542fe00794a2ec7466abd3f312ccb8fad8dff9f36d42e18fb1ec33e/multidict-6.6.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e4e15d2138ee2694e038e33b7c3da70e6b0ad8868b9f8094a72e1414aeda9c1a", size = 269341, upload-time = "2025-06-30T15:51:59.111Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a4/11/6ec9dcbe2264b92778eeb85407d1df18812248bf3506a5a1754bc035db0c/multidict-6.6.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8df25594989aebff8a130f7899fa03cbfcc5d2b5f4a461cf2518236fe6f15961", size = 256081, upload-time = "2025-06-30T15:52:00.533Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9b/2b/631b1e2afeb5f1696846d747d36cda075bfdc0bc7245d6ba5c319278d6c4/multidict-6.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:159ca68bfd284a8860f8d8112cf0521113bffd9c17568579e4d13d1f1dc76b65", size = 253581, upload-time = "2025-06-30T15:52:02.43Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bf/0e/7e3b93f79efeb6111d3bf9a1a69e555ba1d07ad1c11bceb56b7310d0d7ee/multidict-6.6.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e098c17856a8c9ade81b4810888c5ad1914099657226283cab3062c0540b0643", size = 250750, upload-time = "2025-06-30T15:52:04.26Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ad/9e/086846c1d6601948e7de556ee464a2d4c85e33883e749f46b9547d7b0704/multidict-6.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:67c92ed673049dec52d7ed39f8cf9ebbadf5032c774058b4406d18c8f8fe7063", size = 251548, upload-time = "2025-06-30T15:52:06.002Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8c/7b/86ec260118e522f1a31550e87b23542294880c97cfbf6fb18cc67b044c66/multidict-6.6.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:bd0578596e3a835ef451784053cfd327d607fc39ea1a14812139339a18a0dbc3", size = 262718, upload-time = "2025-06-30T15:52:07.707Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8c/bd/22ce8f47abb0be04692c9fc4638508b8340987b18691aa7775d927b73f72/multidict-6.6.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:346055630a2df2115cd23ae271910b4cae40f4e336773550dca4889b12916e75", size = 259603, upload-time = "2025-06-30T15:52:09.58Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/07/9c/91b7ac1691be95cd1f4a26e36a74b97cda6aa9820632d31aab4410f46ebd/multidict-6.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:555ff55a359302b79de97e0468e9ee80637b0de1fce77721639f7cd9440b3a10", size = 251351, upload-time = "2025-06-30T15:52:10.947Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6f/5c/4d7adc739884f7a9fbe00d1eac8c034023ef8bad71f2ebe12823ca2e3649/multidict-6.6.3-cp312-cp312-win32.whl", hash = "sha256:73ab034fb8d58ff85c2bcbadc470efc3fafeea8affcf8722855fb94557f14cc5", size = 41860, upload-time = "2025-06-30T15:52:12.334Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6a/a3/0fbc7afdf7cb1aa12a086b02959307848eb6bcc8f66fcb66c0cb57e2a2c1/multidict-6.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:04cbcce84f63b9af41bad04a54d4cc4e60e90c35b9e6ccb130be2d75b71f8c17", size = 45982, upload-time = "2025-06-30T15:52:13.6Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b8/95/8c825bd70ff9b02462dc18d1295dd08d3e9e4eb66856d292ffa62cfe1920/multidict-6.6.3-cp312-cp312-win_arm64.whl", hash = "sha256:0f1130b896ecb52d2a1e615260f3ea2af55fa7dc3d7c3003ba0c3121a759b18b", size = 43210, upload-time = "2025-06-30T15:52:14.893Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d8/30/9aec301e9772b098c1f5c0ca0279237c9766d94b97802e9888010c64b0ed/multidict-6.6.3-py3-none-any.whl", hash = "sha256:8db10f29c7541fc5da4defd8cd697e1ca429db743fa716325f236079b96f775a", size = 12313, upload-time = "2025-06-30T15:53:45.437Z" }, +] + +[[package]] +name = "multiprocess" +version = "0.70.16" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "dill" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b5/ae/04f39c5d0d0def03247c2893d6f2b83c136bf3320a2154d7b8858f2ba72d/multiprocess-0.70.16.tar.gz", hash = "sha256:161af703d4652a0e1410be6abccecde4a7ddffd19341be0a7011b94aeb171ac1", size = 1772603, upload-time = "2024-01-28T18:52:34.85Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ef/76/6e712a2623d146d314f17598df5de7224c85c0060ef63fd95cc15a25b3fa/multiprocess-0.70.16-pp310-pypy310_pp73-macosx_10_13_x86_64.whl", hash = "sha256:476887be10e2f59ff183c006af746cb6f1fd0eadcfd4ef49e605cbe2659920ee", size = 134980, upload-time = "2024-01-28T18:52:15.731Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0f/ab/1e6e8009e380e22254ff539ebe117861e5bdb3bff1fc977920972237c6c7/multiprocess-0.70.16-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d951bed82c8f73929ac82c61f01a7b5ce8f3e5ef40f5b52553b4f547ce2b08ec", size = 134982, upload-time = "2024-01-28T18:52:17.783Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bc/f7/7ec7fddc92e50714ea3745631f79bd9c96424cb2702632521028e57d3a36/multiprocess-0.70.16-py310-none-any.whl", hash = "sha256:c4a9944c67bd49f823687463660a2d6daae94c289adff97e0f9d696ba6371d02", size = 134824, upload-time = "2024-01-28T18:52:26.062Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/50/15/b56e50e8debaf439f44befec5b2af11db85f6e0f344c3113ae0be0593a91/multiprocess-0.70.16-py311-none-any.whl", hash = "sha256:af4cabb0dac72abfb1e794fa7855c325fd2b55a10a44628a3c1ad3311c04127a", size = 143519, upload-time = "2024-01-28T18:52:28.115Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0a/7d/a988f258104dcd2ccf1ed40fdc97e26c4ac351eeaf81d76e266c52d84e2f/multiprocess-0.70.16-py312-none-any.whl", hash = "sha256:fc0544c531920dde3b00c29863377f87e1632601092ea2daca74e4beb40faa2e", size = 146741, upload-time = "2024-01-28T18:52:29.395Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ea/89/38df130f2c799090c978b366cfdf5b96d08de5b29a4a293df7f7429fa50b/multiprocess-0.70.16-py38-none-any.whl", hash = "sha256:a71d82033454891091a226dfc319d0cfa8019a4e888ef9ca910372a446de4435", size = 132628, upload-time = "2024-01-28T18:52:30.853Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/da/d9/f7f9379981e39b8c2511c9e0326d212accacb82f12fbfdc1aa2ce2a7b2b6/multiprocess-0.70.16-py39-none-any.whl", hash = "sha256:a0bafd3ae1b732eac64be2e72038231c1ba97724b60b09400d68f229fcc2fbf3", size = 133351, upload-time = "2024-01-28T18:52:31.981Z" }, +] + +[[package]] +name = "multitasking" +version = "0.0.12" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/17/0d/74f0293dfd7dcc3837746d0138cbedd60b31701ecc75caec7d3f281feba0/multitasking-0.0.12.tar.gz", hash = "sha256:2fba2fa8ed8c4b85e227c5dd7dc41c7d658de3b6f247927316175a57349b84d1", size = 19984, upload-time = "2025-07-20T21:27:51.636Z" } + +[[package]] +name = "mygene" +version = "3.2.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "biothings-client" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0a/ec/a256003f84196aa3fdd65a7c6f5adfc0688398fb66442eba75b39c9b7627/mygene-3.2.2.tar.gz", hash = "sha256:e729cabbc28cf5afb221bca1ab637883b375cb1a3e2f067587ec79f71affdaea", size = 5399, upload-time = "2021-04-05T21:24:30.934Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a7/b7/132b1673c0ec00881d49d56c09624942fa0ebd2fc21d73d80647efa082e9/mygene-3.2.2-py2.py3-none-any.whl", hash = "sha256:18d85d1b28ecee2be31d844607fb0c5f7d7c58573278432df819ee2a5e88fe46", size = 5357, upload-time = "2021-04-05T21:24:29.07Z" }, +] + +[[package]] +name = "nest-asyncio" +version = "1.6.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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.4.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +resolution-markers = [ + "python_full_version < '3.11' and sys_platform == 'darwin'", + "python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "(python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux')", +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fd/1d/06475e1cd5264c0b870ea2cc6fdb3e37177c1e565c43f56ff17a10e3937f/networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1", size = 2151368, upload-time = "2024-10-21T12:39:38.695Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263, upload-time = "2024-10-21T12:39:36.247Z" }, +] + +[[package]] +name = "networkx" +version = "3.5" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +resolution-markers = [ + "python_full_version >= '3.12' and sys_platform == 'darwin'", + "python_full_version >= '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "(python_full_version >= '3.12' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.12' and sys_platform != 'darwin' and sys_platform != 'linux')", + "python_full_version == '3.11.*' and sys_platform == 'darwin'", + "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "(python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'linux')", +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "click" }, + { name = "joblib" }, + { name = "regex" }, + { name = "tqdm" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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 = "numba" +version = "0.61.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "llvmlite" }, + { name = "numpy" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1c/a0/e21f57604304aa03ebb8e098429222722ad99176a4f979d34af1d1ee80da/numba-0.61.2.tar.gz", hash = "sha256:8750ee147940a6637b80ecf7f95062185ad8726c8c28a2295b8ec1160a196f7d", size = 2820615, upload-time = "2025-04-09T02:58:07.659Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/eb/ca/f470be59552ccbf9531d2d383b67ae0b9b524d435fb4a0d229fef135116e/numba-0.61.2-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:cf9f9fc00d6eca0c23fc840817ce9f439b9f03c8f03d6246c0e7f0cb15b7162a", size = 2775663, upload-time = "2025-04-09T02:57:34.143Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f5/13/3bdf52609c80d460a3b4acfb9fdb3817e392875c0d6270cf3fd9546f138b/numba-0.61.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ea0247617edcb5dd61f6106a56255baab031acc4257bddaeddb3a1003b4ca3fd", size = 2778344, upload-time = "2025-04-09T02:57:36.609Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e2/7d/bfb2805bcfbd479f04f835241ecf28519f6e3609912e3a985aed45e21370/numba-0.61.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ae8c7a522c26215d5f62ebec436e3d341f7f590079245a2f1008dfd498cc1642", size = 3824054, upload-time = "2025-04-09T02:57:38.162Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e3/27/797b2004745c92955470c73c82f0e300cf033c791f45bdecb4b33b12bdea/numba-0.61.2-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:bd1e74609855aa43661edffca37346e4e8462f6903889917e9f41db40907daa2", size = 3518531, upload-time = "2025-04-09T02:57:39.709Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b1/c6/c2fb11e50482cb310afae87a997707f6c7d8a48967b9696271347441f650/numba-0.61.2-cp310-cp310-win_amd64.whl", hash = "sha256:ae45830b129c6137294093b269ef0a22998ccc27bf7cf096ab8dcf7bca8946f9", size = 2831612, upload-time = "2025-04-09T02:57:41.559Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3f/97/c99d1056aed767503c228f7099dc11c402906b42a4757fec2819329abb98/numba-0.61.2-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:efd3db391df53aaa5cfbee189b6c910a5b471488749fd6606c3f33fc984c2ae2", size = 2775825, upload-time = "2025-04-09T02:57:43.442Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/95/9e/63c549f37136e892f006260c3e2613d09d5120672378191f2dc387ba65a2/numba-0.61.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:49c980e4171948ffebf6b9a2520ea81feed113c1f4890747ba7f59e74be84b1b", size = 2778695, upload-time = "2025-04-09T02:57:44.968Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/97/c8/8740616c8436c86c1b9a62e72cb891177d2c34c2d24ddcde4c390371bf4c/numba-0.61.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3945615cd73c2c7eba2a85ccc9c1730c21cd3958bfcf5a44302abae0fb07bb60", size = 3829227, upload-time = "2025-04-09T02:57:46.63Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fc/06/66e99ae06507c31d15ff3ecd1f108f2f59e18b6e08662cd5f8a5853fbd18/numba-0.61.2-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbfdf4eca202cebade0b7d43896978e146f39398909a42941c9303f82f403a18", size = 3523422, upload-time = "2025-04-09T02:57:48.222Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0f/a4/2b309a6a9f6d4d8cfba583401c7c2f9ff887adb5d54d8e2e130274c0973f/numba-0.61.2-cp311-cp311-win_amd64.whl", hash = "sha256:76bcec9f46259cedf888041b9886e257ae101c6268261b19fda8cfbc52bec9d1", size = 2831505, upload-time = "2025-04-09T02:57:50.108Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b4/a0/c6b7b9c615cfa3b98c4c63f4316e3f6b3bbe2387740277006551784218cd/numba-0.61.2-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:34fba9406078bac7ab052efbf0d13939426c753ad72946baaa5bf9ae0ebb8dd2", size = 2776626, upload-time = "2025-04-09T02:57:51.857Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/92/4a/fe4e3c2ecad72d88f5f8cd04e7f7cff49e718398a2fac02d2947480a00ca/numba-0.61.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4ddce10009bc097b080fc96876d14c051cc0c7679e99de3e0af59014dab7dfe8", size = 2779287, upload-time = "2025-04-09T02:57:53.658Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9a/2d/e518df036feab381c23a624dac47f8445ac55686ec7f11083655eb707da3/numba-0.61.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5b1bb509d01f23d70325d3a5a0e237cbc9544dd50e50588bc581ba860c213546", size = 3885928, upload-time = "2025-04-09T02:57:55.206Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/10/0f/23cced68ead67b75d77cfcca3df4991d1855c897ee0ff3fe25a56ed82108/numba-0.61.2-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:48a53a3de8f8793526cbe330f2a39fe9a6638efcbf11bd63f3d2f9757ae345cd", size = 3577115, upload-time = "2025-04-09T02:57:56.818Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/68/1d/ddb3e704c5a8fb90142bf9dc195c27db02a08a99f037395503bfbc1d14b3/numba-0.61.2-cp312-cp312-win_amd64.whl", hash = "sha256:97cf4f12c728cf77c9c1d7c23707e4d8fb4632b46275f8f3397de33e5877af18", size = 2831929, upload-time = "2025-04-09T02:57:58.45Z" }, +] + +[[package]] +name = "numpy" +version = "1.26.4" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/65/6e/09db70a523a96d25e115e71cc56a6f9031e7b8cd166c1ac8438307c14058/numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010", size = 15786129, upload-time = "2024-02-06T00:26:44.495Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a7/94/ace0fdea5241a27d13543ee117cbc65868e82213fb31a8eb7fe9ff23f313/numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0", size = 20631468, upload-time = "2024-02-05T23:48:01.194Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/20/f7/b24208eba89f9d1b58c1668bc6c8c4fd472b20c45573cb767f59d49fb0f6/numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a", size = 13966411, upload-time = "2024-02-05T23:48:29.038Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fc/a5/4beee6488160798683eed5bdb7eead455892c3b4e1f78d79d8d3f3b084ac/numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4", size = 14219016, upload-time = "2024-02-05T23:48:54.098Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4b/d7/ecf66c1cd12dc28b4040b15ab4d17b773b87fa9d29ca16125de01adb36cd/numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f", size = 18240889, upload-time = "2024-02-05T23:49:25.361Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/24/03/6f229fe3187546435c4f6f89f6d26c129d4f5bed40552899fcf1f0bf9e50/numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a", size = 13876746, upload-time = "2024-02-05T23:49:51.983Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/39/fe/39ada9b094f01f5a35486577c848fe274e374bbf8d8f472e1423a0bbd26d/numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2", size = 18078620, upload-time = "2024-02-05T23:50:22.515Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d5/ef/6ad11d51197aad206a9ad2286dc1aac6a378059e06e8cf22cd08ed4f20dc/numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07", size = 5972659, upload-time = "2024-02-05T23:50:35.834Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/19/77/538f202862b9183f54108557bfda67e17603fc560c384559e769321c9d92/numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5", size = 15808905, upload-time = "2024-02-05T23:51:03.701Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/11/57/baae43d14fe163fa0e4c47f307b6b2511ab8d7d30177c491960504252053/numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71", size = 20630554, upload-time = "2024-02-05T23:51:50.149Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1a/2e/151484f49fd03944c4a3ad9c418ed193cfd02724e138ac8a9505d056c582/numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef", size = 13997127, upload-time = "2024-02-05T23:52:15.314Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/79/ae/7e5b85136806f9dadf4878bf73cf223fe5c2636818ba3ab1c585d0403164/numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e", size = 14222994, upload-time = "2024-02-05T23:52:47.569Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3a/d0/edc009c27b406c4f9cbc79274d6e46d634d139075492ad055e3d68445925/numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5", size = 18252005, upload-time = "2024-02-05T23:53:15.637Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/09/bf/2b1aaf8f525f2923ff6cfcf134ae5e750e279ac65ebf386c75a0cf6da06a/numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a", size = 13885297, upload-time = "2024-02-05T23:53:42.16Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/df/a0/4e0f14d847cfc2a633a1c8621d00724f3206cfeddeb66d35698c4e2cf3d2/numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a", size = 18093567, upload-time = "2024-02-05T23:54:11.696Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d2/b7/a734c733286e10a7f1a8ad1ae8c90f2d33bf604a96548e0a4a3a6739b468/numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20", size = 5968812, upload-time = "2024-02-05T23:54:26.453Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3f/6b/5610004206cf7f8e7ad91c5a85a8c71b2f2f8051a0c0c4d5916b76d6cbb2/numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2", size = 15811913, upload-time = "2024-02-05T23:54:53.933Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/95/12/8f2020a8e8b8383ac0177dc9570aad031a3beb12e38847f7129bacd96228/numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218", size = 20335901, upload-time = "2024-02-05T23:55:32.801Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/75/5b/ca6c8bd14007e5ca171c7c03102d17b4f4e0ceb53957e8c44343a9546dcc/numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b", size = 13685868, upload-time = "2024-02-05T23:55:56.28Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/79/f8/97f10e6755e2a7d027ca783f63044d5b1bc1ae7acb12afe6a9b4286eac17/numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b", size = 13925109, upload-time = "2024-02-05T23:56:20.368Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0f/50/de23fde84e45f5c4fda2488c759b69990fd4512387a8632860f3ac9cd225/numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed", size = 17950613, upload-time = "2024-02-05T23:56:56.054Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4c/0c/9c603826b6465e82591e05ca230dfc13376da512b25ccd0894709b054ed0/numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a", size = 13572172, upload-time = "2024-02-05T23:57:21.56Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/76/8c/2ba3902e1a0fc1c74962ea9bb33a534bb05984ad7ff9515bf8d07527cadd/numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0", size = 17786643, upload-time = "2024-02-05T23:57:56.585Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/28/4a/46d9e65106879492374999e76eb85f87b15328e06bd1550668f79f7b18c6/numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110", size = 5677803, upload-time = "2024-02-05T23:58:08.963Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/16/2e/86f24451c2d530c88daf997cb8d6ac622c1d40d19f5a031ed68a4b73a374/numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818", size = 15517754, upload-time = "2024-02-05T23:58:36.364Z" }, +] + +[[package]] +name = "nvidia-cublas-cu12" +version = "12.6.4.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/af/eb/ff4b8c503fa1f1796679dce648854d58751982426e4e4b37d6fce49d259c/nvidia_cublas_cu12-12.6.4.1-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:08ed2686e9875d01b58e3cb379c6896df8e76c75e0d4a7f7dace3d7b6d9ef8eb", size = 393138322, upload-time = "2024-11-20T17:40:25.65Z" }, +] + +[[package]] +name = "nvidia-cuda-cupti-cu12" +version = "12.6.80" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/49/60/7b6497946d74bcf1de852a21824d63baad12cd417db4195fc1bfe59db953/nvidia_cuda_cupti_cu12-12.6.80-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6768bad6cab4f19e8292125e5f1ac8aa7d1718704012a0e3272a6f61c4bce132", size = 8917980, upload-time = "2024-11-20T17:36:04.019Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a5/24/120ee57b218d9952c379d1e026c4479c9ece9997a4fb46303611ee48f038/nvidia_cuda_cupti_cu12-12.6.80-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a3eff6cdfcc6a4c35db968a06fcadb061cbc7d6dde548609a941ff8701b98b73", size = 8917972, upload-time = "2024-10-01T16:58:06.036Z" }, +] + +[[package]] +name = "nvidia-cuda-nvrtc-cu12" +version = "12.6.77" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/75/2e/46030320b5a80661e88039f59060d1790298b4718944a65a7f2aeda3d9e9/nvidia_cuda_nvrtc_cu12-12.6.77-py3-none-manylinux2014_x86_64.whl", hash = "sha256:35b0cc6ee3a9636d5409133e79273ce1f3fd087abb0532d2d2e8fff1fe9efc53", size = 23650380, upload-time = "2024-10-01T17:00:14.643Z" }, +] + +[[package]] +name = "nvidia-cuda-runtime-cu12" +version = "12.6.77" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e1/23/e717c5ac26d26cf39a27fbc076240fad2e3b817e5889d671b67f4f9f49c5/nvidia_cuda_runtime_cu12-12.6.77-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ba3b56a4f896141e25e19ab287cd71e52a6a0f4b29d0d31609f60e3b4d5219b7", size = 897690, upload-time = "2024-11-20T17:35:30.697Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f0/62/65c05e161eeddbafeca24dc461f47de550d9fa8a7e04eb213e32b55cfd99/nvidia_cuda_runtime_cu12-12.6.77-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a84d15d5e1da416dd4774cb42edf5e954a3e60cc945698dc1d5be02321c44dc8", size = 897678, upload-time = "2024-10-01T16:57:33.821Z" }, +] + +[[package]] +name = "nvidia-cudnn-cu12" +version = "9.5.1.17" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "nvidia-cublas-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, +] +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2a/78/4535c9c7f859a64781e43c969a3a7e84c54634e319a996d43ef32ce46f83/nvidia_cudnn_cu12-9.5.1.17-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:30ac3869f6db17d170e0e556dd6cc5eee02647abc31ca856634d5a40f82c15b2", size = 570988386, upload-time = "2024-10-25T19:54:26.39Z" }, +] + +[[package]] +name = "nvidia-cufft-cu12" +version = "11.3.0.4" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, +] +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8f/16/73727675941ab8e6ffd86ca3a4b7b47065edcca7a997920b831f8147c99d/nvidia_cufft_cu12-11.3.0.4-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ccba62eb9cef5559abd5e0d54ceed2d9934030f51163df018532142a8ec533e5", size = 200221632, upload-time = "2024-11-20T17:41:32.357Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/60/de/99ec247a07ea40c969d904fc14f3a356b3e2a704121675b75c366b694ee1/nvidia_cufft_cu12-11.3.0.4-py3-none-manylinux2014_x86_64.whl", hash = "sha256:768160ac89f6f7b459bee747e8d175dbf53619cfe74b2a5636264163138013ca", size = 200221622, upload-time = "2024-10-01T17:03:58.79Z" }, +] + +[[package]] +name = "nvidia-cufile-cu12" +version = "1.11.1.6" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b2/66/cc9876340ac68ae71b15c743ddb13f8b30d5244af344ec8322b449e35426/nvidia_cufile_cu12-1.11.1.6-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc23469d1c7e52ce6c1d55253273d32c565dd22068647f3aa59b3c6b005bf159", size = 1142103, upload-time = "2024-11-20T17:42:11.83Z" }, +] + +[[package]] +name = "nvidia-curand-cu12" +version = "10.3.7.77" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/73/1b/44a01c4e70933637c93e6e1a8063d1e998b50213a6b65ac5a9169c47e98e/nvidia_curand_cu12-10.3.7.77-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a42cd1344297f70b9e39a1e4f467a4e1c10f1da54ff7a85c12197f6c652c8bdf", size = 56279010, upload-time = "2024-11-20T17:42:50.958Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4a/aa/2c7ff0b5ee02eaef890c0ce7d4f74bc30901871c5e45dee1ae6d0083cd80/nvidia_curand_cu12-10.3.7.77-py3-none-manylinux2014_x86_64.whl", hash = "sha256:99f1a32f1ac2bd134897fc7a203f779303261268a65762a623bf30cc9fe79117", size = 56279000, upload-time = "2024-10-01T17:04:45.274Z" }, +] + +[[package]] +name = "nvidia-cusolver-cu12" +version = "11.7.1.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "nvidia-cublas-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "nvidia-cusparse-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, +] +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f0/6e/c2cf12c9ff8b872e92b4a5740701e51ff17689c4d726fca91875b07f655d/nvidia_cusolver_cu12-11.7.1.2-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e9e49843a7707e42022babb9bcfa33c29857a93b88020c4e4434656a655b698c", size = 158229790, upload-time = "2024-11-20T17:43:43.211Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9f/81/baba53585da791d043c10084cf9553e074548408e04ae884cfe9193bd484/nvidia_cusolver_cu12-11.7.1.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:6cf28f17f64107a0c4d7802be5ff5537b2130bfc112f25d5a30df227058ca0e6", size = 158229780, upload-time = "2024-10-01T17:05:39.875Z" }, +] + +[[package]] +name = "nvidia-cusparse-cu12" +version = "12.5.4.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, +] +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/06/1e/b8b7c2f4099a37b96af5c9bb158632ea9e5d9d27d7391d7eb8fc45236674/nvidia_cusparse_cu12-12.5.4.2-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7556d9eca156e18184b94947ade0fba5bb47d69cec46bf8660fd2c71a4b48b73", size = 216561367, upload-time = "2024-11-20T17:44:54.824Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/43/ac/64c4316ba163e8217a99680c7605f779accffc6a4bcd0c778c12948d3707/nvidia_cusparse_cu12-12.5.4.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:23749a6571191a215cb74d1cdbff4a86e7b19f1200c071b3fcf844a5bea23a2f", size = 216561357, upload-time = "2024-10-01T17:06:29.861Z" }, +] + +[[package]] +name = "nvidia-cusparselt-cu12" +version = "0.6.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3b/9a/72ef35b399b0e183bc2e8f6f558036922d453c4d8237dab26c666a04244b/nvidia_cusparselt_cu12-0.6.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:e5c8a26c36445dd2e6812f1177978a24e2d37cacce7e090f297a688d1ec44f46", size = 156785796, upload-time = "2024-10-15T21:29:17.709Z" }, +] + +[[package]] +name = "nvidia-nccl-cu12" +version = "2.26.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/67/ca/f42388aed0fddd64ade7493dbba36e1f534d4e6fdbdd355c6a90030ae028/nvidia_nccl_cu12-2.26.2-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:694cf3879a206553cc9d7dbda76b13efaf610fdb70a50cba303de1b0d1530ac6", size = 201319755, upload-time = "2025-03-13T00:29:55.296Z" }, +] + +[[package]] +name = "nvidia-nvjitlink-cu12" +version = "12.6.85" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9d/d7/c5383e47c7e9bf1c99d5bd2a8c935af2b6d705ad831a7ec5c97db4d82f4f/nvidia_nvjitlink_cu12-12.6.85-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:eedc36df9e88b682efe4309aa16b5b4e78c2407eac59e8c10a6a47535164369a", size = 19744971, upload-time = "2024-11-20T17:46:53.366Z" }, +] + +[[package]] +name = "nvidia-nvtx-cu12" +version = "12.6.77" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/56/9a/fff8376f8e3d084cd1530e1ef7b879bb7d6d265620c95c1b322725c694f4/nvidia_nvtx_cu12-12.6.77-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b90bed3df379fa79afbd21be8e04a0314336b8ae16768b58f2d34cb1d04cd7d2", size = 89276, upload-time = "2024-11-20T17:38:27.621Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9e/4e/0d0c945463719429b7bd21dece907ad0bde437a2ff12b9b12fee94722ab0/nvidia_nvtx_cu12-12.6.77-py3-none-manylinux2014_x86_64.whl", hash = "sha256:6574241a3ec5fdc9334353ab8c479fe75841dbe8f4532a8fc97ce63503330ba1", size = 89265, upload-time = "2024-10-01T17:00:38.172Z" }, +] + +[[package]] +name = "olefile" +version = "0.46" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/34/81/e1ac43c6b45b4c5f8d9352396a14144bba52c8fec72a80f425f6a4d653ad/olefile-0.46.zip", hash = "sha256:133b031eaf8fd2c9399b78b8bc5b8fcbe4c31e85295749bb17a87cba8f3c3964" } + +[[package]] +name = "oletools" +version = "0.60.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "colorclass" }, + { name = "easygui" }, + { name = "msoffcrypto-tool", marker = "(platform_python_implementation != 'PyPy' and sys_platform == 'darwin') or (platform_python_implementation != 'PyPy' and sys_platform == 'win32') or (sys_platform != 'darwin' and sys_platform != 'win32')" }, + { name = "olefile" }, + { name = "pcodedmp" }, + { name = "pyparsing" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5c/2f/037f40e44706d542b94a2312ccc33ee2701ebfc9a83b46b55263d49ce55a/oletools-0.60.2.zip", hash = "sha256:ad452099f4695ffd8855113f453348200d195ee9fa341a09e197d66ee7e0b2c3", size = 3433750, upload-time = "2024-07-02T14:50:38.242Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ac/ff/05257b7183279b80ecec6333744de23f48f0faeeba46c93e6d13ce835515/oletools-0.60.2-py2.py3-none-any.whl", hash = "sha256:72ad8bd748fd0c4e7b5b4733af770d11543ebb2bf2697455f99f975fcd50cc96", size = 989449, upload-time = "2024-07-02T14:50:29.122Z" }, +] + +[[package]] +name = "ollama" +version = "0.6.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "httpx" }, + { name = "pydantic" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d6/47/f9ee32467fe92744474a8c72e138113f3b529fc266eea76abfdec9a33f3b/ollama-0.6.0.tar.gz", hash = "sha256:da2b2d846b5944cfbcee1ca1e6ee0585f6c9d45a2fe9467cbcd096a37383da2f", size = 50811, upload-time = "2025-09-24T22:46:02.417Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b5/c1/edc9f41b425ca40b26b7c104c5f6841a4537bb2552bfa6ca66e81405bb95/ollama-0.6.0-py3-none-any.whl", hash = "sha256:534511b3ccea2dff419ae06c3b58d7f217c55be7897c8ce5868dfb6b219cf7a0", size = 14130, upload-time = "2025-09-24T22:46:01.19Z" }, +] + +[[package]] +name = "onnx" +version = "1.18.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "numpy" }, + { name = "protobuf" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3d/60/e56e8ec44ed34006e6d4a73c92a04d9eea6163cc12440e35045aec069175/onnx-1.18.0.tar.gz", hash = "sha256:3d8dbf9e996629131ba3aa1afd1d8239b660d1f830c6688dd7e03157cccd6b9c", size = 12563009, upload-time = "2025-05-12T22:03:09.626Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8e/e3/ab8a09c0af43373e0422de461956a1737581325260659aeffae22a7dad18/onnx-1.18.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:4a3b50d94620e2c7c1404d1d59bc53e665883ae3fecbd856cc86da0639fd0fc3", size = 18280145, upload-time = "2025-05-12T22:01:49.875Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/04/5b/3cfd183961a0a872fe29c95f8d07264890ec65c75c94b99a4dabc950df29/onnx-1.18.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e189652dad6e70a0465035c55cc565c27aa38803dd4f4e74e4b952ee1c2de94b", size = 17422721, upload-time = "2025-05-12T22:01:52.841Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/58/52/fa649429016c5790f68c614cdebfbefd3e72ba1c458966305297d540f713/onnx-1.18.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfb1f271b1523b29f324bfd223f6a4cfbdc5a2f2f16e73563671932d33663365", size = 17584220, upload-time = "2025-05-12T22:01:56.458Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/42/52/dc166de41a5f72738b0bdfb2a19e0ebe4743cf3ecc9ae381ea3425bcb332/onnx-1.18.0-cp310-cp310-win32.whl", hash = "sha256:e03071041efd82e0317b3c45433b2f28146385b80f26f82039bc68048ac1a7a0", size = 15734494, upload-time = "2025-05-12T22:01:59.704Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a6/f9/e766a3b85b7651ddfc5f9648e0e9dc24e88b7e88ea7f8c23187530e818ea/onnx-1.18.0-cp310-cp310-win_amd64.whl", hash = "sha256:9235b3493951e11e75465d56f4cd97e3e9247f096160dd3466bfabe4cbc938bc", size = 15848421, upload-time = "2025-05-12T22:02:03.01Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ed/3a/a336dac4db1eddba2bf577191e5b7d3e4c26fcee5ec518a5a5b11d13540d/onnx-1.18.0-cp311-cp311-macosx_12_0_universal2.whl", hash = "sha256:735e06d8d0cf250dc498f54038831401063c655a8d6e5975b2527a4e7d24be3e", size = 18281831, upload-time = "2025-05-12T22:02:06.429Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/02/3a/56475a111120d1e5d11939acbcbb17c92198c8e64a205cd68e00bdfd8a1f/onnx-1.18.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73160799472e1a86083f786fecdf864cf43d55325492a9b5a1cfa64d8a523ecc", size = 17424359, upload-time = "2025-05-12T22:02:09.866Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cf/03/5eb5e9ef446ed9e78c4627faf3c1bc25e0f707116dd00e9811de232a8df5/onnx-1.18.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6acafb3823238bbe8f4340c7ac32fb218689442e074d797bee1c5c9a02fdae75", size = 17586006, upload-time = "2025-05-12T22:02:13.217Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b0/4e/70943125729ce453271a6e46bb847b4a612496f64db6cbc6cb1f49f41ce1/onnx-1.18.0-cp311-cp311-win32.whl", hash = "sha256:4c8c4bbda760c654e65eaffddb1a7de71ec02e60092d33f9000521f897c99be9", size = 15734988, upload-time = "2025-05-12T22:02:16.561Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/44/b0/435fd764011911e8f599e3361f0f33425b1004662c1ea33a0ad22e43db2d/onnx-1.18.0-cp311-cp311-win_amd64.whl", hash = "sha256:a5810194f0f6be2e58c8d6dedc6119510df7a14280dd07ed5f0f0a85bd74816a", size = 15849576, upload-time = "2025-05-12T22:02:19.569Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6c/f0/9e31f4b4626d60f1c034f71b411810bc9fafe31f4e7dd3598effd1b50e05/onnx-1.18.0-cp311-cp311-win_arm64.whl", hash = "sha256:aa1b7483fac6cdec26922174fc4433f8f5c2f239b1133c5625063bb3b35957d0", size = 15822961, upload-time = "2025-05-12T22:02:22.735Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a7/fe/16228aca685392a7114625b89aae98b2dc4058a47f0f467a376745efe8d0/onnx-1.18.0-cp312-cp312-macosx_12_0_universal2.whl", hash = "sha256:521bac578448667cbb37c50bf05b53c301243ede8233029555239930996a625b", size = 18285770, upload-time = "2025-05-12T22:02:26.116Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1e/77/ba50a903a9b5e6f9be0fa50f59eb2fca4a26ee653375408fbc72c3acbf9f/onnx-1.18.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4da451bf1c5ae381f32d430004a89f0405bc57a8471b0bddb6325a5b334aa40", size = 17421291, upload-time = "2025-05-12T22:02:29.645Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/11/23/25ec2ba723ac62b99e8fed6d7b59094dadb15e38d4c007331cc9ae3dfa5f/onnx-1.18.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99afac90b4cdb1471432203c3c1f74e16549c526df27056d39f41a9a47cfb4af", size = 17584084, upload-time = "2025-05-12T22:02:32.789Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6a/4d/2c253a36070fb43f340ff1d2c450df6a9ef50b938adcd105693fee43c4ee/onnx-1.18.0-cp312-cp312-win32.whl", hash = "sha256:ee159b41a3ae58d9c7341cf432fc74b96aaf50bd7bb1160029f657b40dc69715", size = 15734892, upload-time = "2025-05-12T22:02:35.527Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e8/92/048ba8fafe6b2b9a268ec2fb80def7e66c0b32ab2cae74de886981f05a27/onnx-1.18.0-cp312-cp312-win_amd64.whl", hash = "sha256:102c04edc76b16e9dfeda5a64c1fccd7d3d2913b1544750c01d38f1ac3c04e05", size = 15850336, upload-time = "2025-05-12T22:02:38.545Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a1/66/bbc4ffedd44165dcc407a51ea4c592802a5391ce3dc94aa5045350f64635/onnx-1.18.0-cp312-cp312-win_arm64.whl", hash = "sha256:911b37d724a5d97396f3c2ef9ea25361c55cbc9aa18d75b12a52b620b67145af", size = 15823802, upload-time = "2025-05-12T22:02:42.037Z" }, +] + +[[package]] +name = "onnxruntime" +version = "1.19.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "coloredlogs" }, + { name = "flatbuffers" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "protobuf" }, + { name = "sympy" }, +] +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/39/18/272d3d7406909141d3c9943796e3e97cafa53f4342d9231c0cfd8cb05702/onnxruntime-1.19.2-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:84fa57369c06cadd3c2a538ae2a26d76d583e7c34bdecd5769d71ca5c0fc750e", size = 16776408, upload-time = "2024-09-04T06:37:02.431Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d8/d3/eb93f4ae511cfc725d0c69e07008800f8ac018de19ea1e497b306f174ccc/onnxruntime-1.19.2-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bdc471a66df0c1cdef774accef69e9f2ca168c851ab5e4f2f3341512c7ef4666", size = 11491779, upload-time = "2024-09-04T06:37:06.203Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ca/4b/ce5958074abe4b6e8d1da9c10e443e01a681558a9ec17e5cc7619438e094/onnxruntime-1.19.2-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e3a4ce906105d99ebbe817f536d50a91ed8a4d1592553f49b3c23c4be2560ae6", size = 13170428, upload-time = "2024-09-04T06:37:09.032Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ce/0f/6df82dfe02467d12adbaa05c2bd17519c29c7df531ed600231f0c741ad22/onnxruntime-1.19.2-cp310-cp310-win32.whl", hash = "sha256:4b3d723cc154c8ddeb9f6d0a8c0d6243774c6b5930847cc83170bfe4678fafb3", size = 9591305, upload-time = "2024-09-04T06:37:11.482Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3c/d8/68b63dc86b502169d017a86fe8bc718f4b0055ef1f6895bfaddd04f2eead/onnxruntime-1.19.2-cp310-cp310-win_amd64.whl", hash = "sha256:17ed7382d2c58d4b7354fb2b301ff30b9bf308a1c7eac9546449cd122d21cae5", size = 11084902, upload-time = "2024-09-04T06:37:14.647Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f0/ff/77bee5df55f034ee81d2e1bc58b2b8511b9c54f06ce6566cb562c5d95aa5/onnxruntime-1.19.2-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:d863e8acdc7232d705d49e41087e10b274c42f09e259016a46f32c34e06dc4fd", size = 16779187, upload-time = "2024-09-04T06:37:18.245Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f3/78/e29f5fb76e0f6524f3520e8e5b9d53282784b45d14068c5112db9f712b0a/onnxruntime-1.19.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c1dfe4f660a71b31caa81fc298a25f9612815215a47b286236e61d540350d7b6", size = 11496005, upload-time = "2024-09-04T06:37:20.998Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/60/ce/be4152da5c1030ab5a159a4a792ed9abad6ba498d79ef0aeba593ff7b5bf/onnxruntime-1.19.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a36511dc07c5c964b916697e42e366fa43c48cdb3d3503578d78cef30417cb84", size = 13167809, upload-time = "2024-09-04T06:37:24.221Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e1/00/9740a074eb0e0a21ff13a2c4f32aecc5b21110b2c9b9177d8ac132b66e2d/onnxruntime-1.19.2-cp311-cp311-win32.whl", hash = "sha256:50cbb8dc69d6befad4746a69760e5b00cc3ff0a59c6c3fb27f8afa20e2cab7e7", size = 9591445, upload-time = "2024-09-04T06:37:26.766Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1e/f5/9d995a685f97508b3254f17015b4a78641b0625e79480a7aed7a7a105d7c/onnxruntime-1.19.2-cp311-cp311-win_amd64.whl", hash = "sha256:1c3e5d415b78337fa0b1b75291e9ea9fb2a4c1f148eb5811e7212fed02cfffa8", size = 11085695, upload-time = "2024-09-04T06:37:29.473Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f2/a5/2a02687a88fc8a2507bef65876c90e96b9f8de5ba1f810acbf67c140fc67/onnxruntime-1.19.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:68e7051bef9cfefcbb858d2d2646536829894d72a4130c24019219442b1dd2ed", size = 16790434, upload-time = "2024-09-04T06:37:32.77Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/47/64/da42254ec14452cad2cdd4cf407094841c0a378c0d08944e9a36172197e9/onnxruntime-1.19.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d2d366fbcc205ce68a8a3bde2185fd15c604d9645888703785b61ef174265168", size = 11486028, upload-time = "2024-09-04T06:37:35.364Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b2/92/3574f6836f33b1b25f272293e72538c38451b12c2d9aa08630bb6bc0f057/onnxruntime-1.19.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:477b93df4db467e9cbf34051662a4b27c18e131fa1836e05974eae0d6e4cf29b", size = 13175054, upload-time = "2024-09-04T06:37:38.192Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ff/c9/8c37e413a830cac7f7dc094fffbd0c998c8bcb66a6f0b0a3201a49bc742b/onnxruntime-1.19.2-cp312-cp312-win32.whl", hash = "sha256:9a174073dc5608fad05f7cf7f320b52e8035e73d80b0a23c80f840e5a97c0147", size = 9592681, upload-time = "2024-09-04T06:37:41.328Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/44/c0/59768846533786a82cafb38d8d2f900ad666bc91f0ae634774d286fa3c47/onnxruntime-1.19.2-cp312-cp312-win_amd64.whl", hash = "sha256:190103273ea4507638ffc31d66a980594b237874b65379e273125150eb044857", size = 11086411, upload-time = "2024-09-04T06:37:44.123Z" }, +] + +[[package]] +name = "onnxruntime-gpu" +version = "1.19.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "coloredlogs", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "flatbuffers", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "numpy", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "packaging", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "protobuf", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "sympy", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, +] +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d0/9c/3fa310e0730643051eb88e884f19813a6c8b67d0fbafcda610d960e589db/onnxruntime_gpu-1.19.2-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a49740e079e7c5215830d30cde3df792e903df007aa0b0fd7aa797937061b27a", size = 226178508, upload-time = "2024-09-04T06:43:40.83Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/92/82/95e3446724f9e99299c40495d5e04cb7cb319c3a4836c724dbdceb2facd9/onnxruntime_gpu-1.19.2-cp310-cp310-win_amd64.whl", hash = "sha256:b895920bb5e4241299f68874e0becdc2635ea0142939c11e7ff5ae5b28993613", size = 226376693, upload-time = "2024-09-04T06:43:53.348Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/85/33/06e856502a1d482532cfa7d4c7ca775dfddcd851c7bd1833f5177e567055/onnxruntime_gpu-1.19.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:562fc7c755393eaad9751e56149339dd201ffbfdb3ef5f43ff21d0619ba9045f", size = 226175096, upload-time = "2024-09-04T06:44:07.847Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0b/0f/d0f728c950b23d8293ceb2f261e16b8a83d967f979b4834d26a86d609a94/onnxruntime_gpu-1.19.2-cp311-cp311-win_amd64.whl", hash = "sha256:522f7495918176cb8c1a3c78bde7152d984f7096acc786c73a27643af8af87c9", size = 226377266, upload-time = "2024-09-04T06:44:21.548Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/68/55/49e5b4b4d6e9a8841dcdec2f102069716b626bf6ce9640b832a9497504eb/onnxruntime_gpu-1.19.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:554a02a3fac0119707eb87327908afd21c4e6f0fa5bf9a034398f098adc316c5", size = 226163139, upload-time = "2024-09-04T06:44:34.076Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/df/5a/0f700aaff26be2c17c9ababba125d79094742d99afe7c24a020010cf2569/onnxruntime_gpu-1.19.2-cp312-cp312-win_amd64.whl", hash = "sha256:e7c6165a405027e3c0f11d189ae7013b5d66919b3381f9bfb3405c0c0cf07968", size = 226370340, upload-time = "2024-09-04T06:44:46.276Z" }, +] + +[[package]] +name = "openai" +version = "1.99.9" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8a/d2/ef89c6f3f36b13b06e271d3cc984ddd2f62508a0972c1cbcc8485a6644ff/openai-1.99.9.tar.gz", hash = "sha256:f2082d155b1ad22e83247c3de3958eb4255b20ccf4a1de2e6681b6957b554e92", size = 506992, upload-time = "2025-08-12T02:31:10.054Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e8/fb/df274ca10698ee77b07bff952f302ea627cc12dac6b85289485dd77db6de/openai-1.99.9-py3-none-any.whl", hash = "sha256:9dbcdb425553bae1ac5d947147bebbd630d91bbfc7788394d4c4f3a35682ab3a", size = 786816, upload-time = "2025-08-12T02:31:08.34Z" }, +] + +[[package]] +name = "opencv-python" +version = "4.10.0.84" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4a/e7/b70a2d9ab205110d715906fc8ec83fbb00404aeb3a37a0654fdb68eb0c8c/opencv-python-4.10.0.84.tar.gz", hash = "sha256:72d234e4582e9658ffea8e9cae5b63d488ad06994ef12d81dc303b17472f3526", size = 95103981, upload-time = "2024-06-17T18:29:56.757Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/66/82/564168a349148298aca281e342551404ef5521f33fba17b388ead0a84dc5/opencv_python-4.10.0.84-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:fc182f8f4cda51b45f01c64e4cbedfc2f00aff799debebc305d8d0210c43f251", size = 54835524, upload-time = "2024-06-18T04:57:32.973Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/64/4a/016cda9ad7cf18c58ba074628a4eaae8aa55f3fd06a266398cef8831a5b9/opencv_python-4.10.0.84-cp37-abi3-macosx_12_0_x86_64.whl", hash = "sha256:71e575744f1d23f79741450254660442785f45a0797212852ee5199ef12eed98", size = 56475426, upload-time = "2024-06-17T19:34:10.927Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/81/e4/7a987ebecfe5ceaf32db413b67ff18eb3092c598408862fff4d7cc3fd19b/opencv_python-4.10.0.84-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09a332b50488e2dda866a6c5573ee192fe3583239fb26ff2f7f9ceb0bc119ea6", size = 41746971, upload-time = "2024-06-17T20:00:25.211Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3f/a4/d2537f47fd7fcfba966bd806e3ec18e7ee1681056d4b0a9c8d983983e4d5/opencv_python-4.10.0.84-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ace140fc6d647fbe1c692bcb2abce768973491222c067c131d80957c595b71f", size = 62548253, upload-time = "2024-06-17T18:29:43.659Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1e/39/bbf57e7b9dab623e8773f6ff36385456b7ae7fa9357a5e53db732c347eac/opencv_python-4.10.0.84-cp37-abi3-win32.whl", hash = "sha256:2db02bb7e50b703f0a2d50c50ced72e95c574e1e5a0bb35a8a86d0b35c98c236", size = 28737688, upload-time = "2024-06-17T18:28:13.177Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ec/6c/fab8113424af5049f85717e8e527ca3773299a3c6b02506e66436e19874f/opencv_python-4.10.0.84-cp37-abi3-win_amd64.whl", hash = "sha256:32dbbd94c26f611dc5cc6979e6b7aa1f55a64d6b463cc1dcd3c95505a63e48fe", size = 38842521, upload-time = "2024-06-17T18:28:21.813Z" }, +] + +[[package]] +name = "opencv-python-headless" +version = "4.10.0.84" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2f/7e/d20f68a5f1487adf19d74378d349932a386b1ece3be9be9915e5986db468/opencv-python-headless-4.10.0.84.tar.gz", hash = "sha256:f2017c6101d7c2ef8d7bc3b414c37ff7f54d64413a1847d89970b6b7069b4e1a", size = 95117755, upload-time = "2024-06-17T18:32:15.606Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1c/9b/583c8d9259f6fc19413f83fd18dd8e6cbc8eefb0b4dc6da52dd151fe3272/opencv_python_headless-4.10.0.84-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:a4f4bcb07d8f8a7704d9c8564c224c8b064c63f430e95b61ac0bffaa374d330e", size = 54835657, upload-time = "2024-06-18T04:58:12.904Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c0/7b/b4c67f5dad7a9a61c47f7a39e4050e8a4628bd64b3c3daaeb755d759f928/opencv_python_headless-4.10.0.84-cp37-abi3-macosx_12_0_x86_64.whl", hash = "sha256:5ae454ebac0eb0a0b932e3406370aaf4212e6a3fdb5038cc86c7aea15a6851da", size = 56475470, upload-time = "2024-06-17T19:34:39.604Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/91/61/f838ce2046f3ec3591ea59ea3549085e399525d3b4558c4ed60b55ed88c0/opencv_python_headless-4.10.0.84-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46071015ff9ab40fccd8a163da0ee14ce9846349f06c6c8c0f2870856ffa45db", size = 29329705, upload-time = "2024-06-17T20:00:49.406Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d1/09/248f86a404567303cdf120e4a301f389b68e3b18e5c0cc428de327da609c/opencv_python_headless-4.10.0.84-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:377d08a7e48a1405b5e84afcbe4798464ce7ee17081c1c23619c8b398ff18295", size = 49858781, upload-time = "2024-06-17T18:31:49.495Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/30/c0/66f88d58500e990a9a0a5c06f98862edf1d0a3a430781218a8c193948438/opencv_python_headless-4.10.0.84-cp37-abi3-win32.whl", hash = "sha256:9092404b65458ed87ce932f613ffbb1106ed2c843577501e5768912360fc50ec", size = 28675298, upload-time = "2024-06-17T18:28:56.897Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/26/d0/22f68eb23eea053a31655960f133c0be9726c6a881547e6e9e7e2a946c4f/opencv_python_headless-4.10.0.84-cp37-abi3-win_amd64.whl", hash = "sha256:afcf28bd1209dd58810d33defb622b325d3cbe49dcd7a43a902982c33e5fad05", size = 38754031, upload-time = "2024-06-17T18:29:04.871Z" }, +] + +[[package]] +name = "opendal" +version = "0.45.20" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2f/3f/927dfe1349ae58b9238b8eafba747af648d660a9425f486dda01a10f0b78/opendal-0.45.20.tar.gz", hash = "sha256:9f6f90d9e9f9d6e9e5a34aa7729169ef34d2f1869ad1e01ddc39b1c0ce0c9405", size = 990267, upload-time = "2025-05-26T07:02:11.819Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f7/d9/b74575762bd9178b0498125f270268e0fb122ee991188e053048da7f002c/opendal-0.45.20-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:d6069cef67f501eda221da63320bd1291aee967f5f8678ccee9e6e566ab37c78", size = 26875698, upload-time = "2025-05-26T07:00:58.995Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/56/f6/0af7d8a4afe5bae6222c4715f0563fa8c257f0525802da47120e28314353/opendal-0.45.20-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c52c4bf9433a3fa17d1f7b18f386a8f601c4b41e3fae9a839d0a861867d6086a", size = 12990070, upload-time = "2025-05-26T07:01:02.78Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/96/16/cf0cfc0838c7837f5642824738ad57f84cee658b4cfdd2b25fdfb52ca8a7/opendal-0.45.20-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:088bc9b20c5f07bbb19a9ff45c32dd3d42cf2d0b4ef40a2319ca27cdc635bf0f", size = 14396627, upload-time = "2025-05-26T07:01:05.913Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b0/76/e903436877895fcf948e36aa728b4b56a3a600c4fd3297d8e4bc38a843be/opendal-0.45.20-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:55efb4388fa03f309de497bf9b9854377fc4045da069c72c9d2df21d24c686cb", size = 13432079, upload-time = "2025-05-26T07:01:09.086Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/34/10/7863a90a592ed6bfb2ddde104db23a00586004e2197f86a255ad9f8a9401/opendal-0.45.20-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:49c966cda40dc6b7b100ea6150d2f29e01ed7db694c5a5168c5fc451872ec77c", size = 13629580, upload-time = "2025-05-26T07:01:12.144Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b4/a3/b77497101e320bcaebb7e99c43d61ca1886795c6a83001d4426cdbc3683d/opendal-0.45.20-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:e81af55e1d8c145119dfa4c9cacd1fd60c1c1fba2207ec5064cb6baae8c3c86b", size = 13316269, upload-time = "2025-05-26T07:01:15.058Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fc/36/21495e4a405d47ece52df98c323ba9467f43e0641e04819ab5732bf0f370/opendal-0.45.20-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3bbdfcb6840ab8bbd29c36a2a329c1f691023b3cd6a26f8a285dc89f39526017", size = 14576544, upload-time = "2025-05-26T07:01:18.227Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/50/28/bb822cad3f3ef15836227751dad46554c499bbefcf0eb34b4cc7e9975e9b/opendal-0.45.20-cp310-cp310-win_amd64.whl", hash = "sha256:e3987c4766a3611ea8cb3a216f21d083ac3e7fa91eb2ff7c0ebe5dc6e6958cce", size = 14944417, upload-time = "2025-05-26T07:01:21.531Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/84/77/6427e16b8630f0cc71f4a1b01648ed3264f1e04f1f6d9b5d09e5c6a4dd2f/opendal-0.45.20-cp311-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:35acdd8001e4a741532834fdbff3020ffb10b40028bb49fbe93c4f8197d66d8c", size = 26910966, upload-time = "2025-05-26T07:01:24.987Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/12/1f/83e415334739f1ab4dba55cdd349abf0b66612249055afb422a354b96ac8/opendal-0.45.20-cp311-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:629bfe8d384364bced6cbeb01f49b99779fa5151c68048a1869ff645ddcfcb25", size = 13002770, upload-time = "2025-05-26T07:01:30.385Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/49/94/c5de6ed54a02d7413636c2ccefa71d8dd09c2ada1cd6ecab202feb1fdeda/opendal-0.45.20-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d12cc5ac7e441fb93d86d1673112d9fb08580fc3226f864434f4a56a72efec53", size = 14387218, upload-time = "2025-05-26T07:01:33.017Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c6/83/713a1e1de8cbbd69af50e26644bbdeef3c1068b89f442417376fa3c0f591/opendal-0.45.20-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:45a3adae1f473052234fc4054a6f210df3ded9aff10db8d545d0a37eff3b13cc", size = 13424302, upload-time = "2025-05-26T07:01:36.417Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c7/78/c9651e753aaf6eb61887ca372a3f9c2ae57dae03c3159d24deaf018c26dc/opendal-0.45.20-cp311-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:d8947857052c85a4b0e251d50e23f5f68f0cdd9e509e32e614a5e4b2fc7424c4", size = 13622483, upload-time = "2025-05-26T07:01:38.886Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3c/9d/5d8c20c0fc93df5e349e5694167de30afdc54c5755704cc64764a6cbb309/opendal-0.45.20-cp311-abi3-musllinux_1_1_armv7l.whl", hash = "sha256:891d2f9114efeef648973049ed15e56477e8feb9e48b540bd8d6105ea22a253c", size = 13320229, upload-time = "2025-05-26T07:01:41.965Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/21/39/05262f748a2085522e0c85f03eab945589313dc9caedc002872c39162776/opendal-0.45.20-cp311-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:539de9b825f6783d6289d88c0c9ac5415daa4d892d761e3540c565bda51e8997", size = 14574280, upload-time = "2025-05-26T07:01:44.413Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/74/83/cc7c6de29b0a7585cd445258d174ca204d37729c3874ad08e515b0bf331c/opendal-0.45.20-cp311-abi3-win_amd64.whl", hash = "sha256:145efd56aa33b493d5b652c3e4f5ae5097ab69d38c132d80f108e9f5c1e4d863", size = 14929888, upload-time = "2025-05-26T07:01:46.929Z" }, +] + +[[package]] +name = "openpyxl" +version = "3.1.5" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "et-xmlfile" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3d/f9/88d94a75de065ea32619465d2f77b29a0469500e99012523b91cc4141cd1/openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050", size = 186464, upload-time = "2024-06-28T14:03:44.161Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c0/da/977ded879c29cbd04de313843e76868e6e13408a94ed6b987245dc7c8506/openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2", size = 250910, upload-time = "2024-06-28T14:03:41.161Z" }, +] + +[[package]] +name = "opensearch-py" +version = "2.7.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "certifi" }, + { name = "events" }, + { name = "python-dateutil" }, + { name = "requests" }, + { name = "urllib3" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c4/ca/5be52de5c69ecd327c16f3fc0dba82b7ffda5bbd0c0e215bdf23a4d12b12/opensearch_py-2.7.1.tar.gz", hash = "sha256:67ab76e9373669bc71da417096df59827c08369ac3795d5438c9a8be21cbd759", size = 226630, upload-time = "2024-08-22T16:12:36.455Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/80/8f/db678ae203d761922a73920215ea53a79faf3bb1ec6aa9511f809c8e234c/opensearch_py-2.7.1-py3-none-any.whl", hash = "sha256:5417650eba98a1c7648e502207cebf3a12beab623ffe0ebbf55f9b1b4b6e44e9", size = 325380, upload-time = "2024-08-22T16:12:34.67Z" }, +] + +[[package]] +name = "opentelemetry-api" +version = "1.36.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "importlib-metadata" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/27/d2/c782c88b8afbf961d6972428821c302bd1e9e7bc361352172f0ca31296e2/opentelemetry_api-1.36.0.tar.gz", hash = "sha256:9a72572b9c416d004d492cbc6e61962c0501eaf945ece9b5a0f56597d8348aa0", size = 64780, upload-time = "2025-07-29T15:12:06.02Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bb/ee/6b08dde0a022c463b88f55ae81149584b125a42183407dc1045c486cc870/opentelemetry_api-1.36.0-py3-none-any.whl", hash = "sha256:02f20bcacf666e1333b6b1f04e647dc1d5111f86b8e510238fcc56d7762cda8c", size = 65564, upload-time = "2025-07-29T15:11:47.998Z" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp" +version = "1.36.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "opentelemetry-exporter-otlp-proto-grpc" }, + { name = "opentelemetry-exporter-otlp-proto-http" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/95/7f/d31294ac28d567a14aefd855756bab79fed69c5a75df712f228f10c47e04/opentelemetry_exporter_otlp-1.36.0.tar.gz", hash = "sha256:72f166ea5a8923ac42889337f903e93af57db8893de200369b07401e98e4e06b", size = 6144, upload-time = "2025-07-29T15:12:07.153Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a0/a2/8966111a285124f3d6156a663ddf2aeddd52843c1a3d6b56cbd9b6c3fd0e/opentelemetry_exporter_otlp-1.36.0-py3-none-any.whl", hash = "sha256:de93b7c45bcc78296998775d52add7c63729e83ef2cd6560730a6b336d7f6494", size = 7018, upload-time = "2025-07-29T15:11:50.498Z" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-proto-common" +version = "1.36.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "opentelemetry-proto" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/34/da/7747e57eb341c59886052d733072bc878424bf20f1d8cf203d508bbece5b/opentelemetry_exporter_otlp_proto_common-1.36.0.tar.gz", hash = "sha256:6c496ccbcbe26b04653cecadd92f73659b814c6e3579af157d8716e5f9f25cbf", size = 20302, upload-time = "2025-07-29T15:12:07.71Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d0/ed/22290dca7db78eb32e0101738366b5bbda00d0407f00feffb9bf8c3fdf87/opentelemetry_exporter_otlp_proto_common-1.36.0-py3-none-any.whl", hash = "sha256:0fc002a6ed63eac235ada9aa7056e5492e9a71728214a61745f6ad04b923f840", size = 18349, upload-time = "2025-07-29T15:11:51.327Z" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-proto-grpc" +version = "1.36.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "googleapis-common-protos" }, + { name = "grpcio" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-otlp-proto-common" }, + { name = "opentelemetry-proto" }, + { name = "opentelemetry-sdk" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/72/6f/6c1b0bdd0446e5532294d1d41bf11fbaea39c8a2423a4cdfe4fe6b708127/opentelemetry_exporter_otlp_proto_grpc-1.36.0.tar.gz", hash = "sha256:b281afbf7036b325b3588b5b6c8bb175069e3978d1bd24071f4a59d04c1e5bbf", size = 23822, upload-time = "2025-07-29T15:12:08.292Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0c/67/5f6bd188d66d0fd8e81e681bbf5822e53eb150034e2611dd2b935d3ab61a/opentelemetry_exporter_otlp_proto_grpc-1.36.0-py3-none-any.whl", hash = "sha256:734e841fc6a5d6f30e7be4d8053adb703c70ca80c562ae24e8083a28fadef211", size = 18828, upload-time = "2025-07-29T15:11:52.235Z" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-proto-http" +version = "1.36.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "googleapis-common-protos" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-otlp-proto-common" }, + { name = "opentelemetry-proto" }, + { name = "opentelemetry-sdk" }, + { name = "requests" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/25/85/6632e7e5700ba1ce5b8a065315f92c1e6d787ccc4fb2bdab15139eaefc82/opentelemetry_exporter_otlp_proto_http-1.36.0.tar.gz", hash = "sha256:dd3637f72f774b9fc9608ab1ac479f8b44d09b6fb5b2f3df68a24ad1da7d356e", size = 16213, upload-time = "2025-07-29T15:12:08.932Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7f/41/a680d38b34f8f5ddbd78ed9f0042e1cc712d58ec7531924d71cb1e6c629d/opentelemetry_exporter_otlp_proto_http-1.36.0-py3-none-any.whl", hash = "sha256:3d769f68e2267e7abe4527f70deb6f598f40be3ea34c6adc35789bea94a32902", size = 18752, upload-time = "2025-07-29T15:11:53.164Z" }, +] + +[[package]] +name = "opentelemetry-proto" +version = "1.36.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fd/02/f6556142301d136e3b7e95ab8ea6a5d9dc28d879a99f3dd673b5f97dca06/opentelemetry_proto-1.36.0.tar.gz", hash = "sha256:0f10b3c72f74c91e0764a5ec88fd8f1c368ea5d9c64639fb455e2854ef87dd2f", size = 46152, upload-time = "2025-07-29T15:12:15.717Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b3/57/3361e06136225be8180e879199caea520f38026f8071366241ac458beb8d/opentelemetry_proto-1.36.0-py3-none-any.whl", hash = "sha256:151b3bf73a09f94afc658497cf77d45a565606f62ce0c17acb08cd9937ca206e", size = 72537, upload-time = "2025-07-29T15:12:02.243Z" }, +] + +[[package]] +name = "opentelemetry-sdk" +version = "1.36.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4c/85/8567a966b85a2d3f971c4d42f781c305b2b91c043724fa08fd37d158e9dc/opentelemetry_sdk-1.36.0.tar.gz", hash = "sha256:19c8c81599f51b71670661ff7495c905d8fdf6976e41622d5245b791b06fa581", size = 162557, upload-time = "2025-07-29T15:12:16.76Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0b/59/7bed362ad1137ba5886dac8439e84cd2df6d087be7c09574ece47ae9b22c/opentelemetry_sdk-1.36.0-py3-none-any.whl", hash = "sha256:19fe048b42e98c5c1ffe85b569b7073576ad4ce0bcb6e9b4c6a39e890a6c45fb", size = 119995, upload-time = "2025-07-29T15:12:03.181Z" }, +] + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.57b0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7e/31/67dfa252ee88476a29200b0255bda8dfc2cf07b56ad66dc9a6221f7dc787/opentelemetry_semantic_conventions-0.57b0.tar.gz", hash = "sha256:609a4a79c7891b4620d64c7aac6898f872d790d75f22019913a660756f27ff32", size = 124225, upload-time = "2025-07-29T15:12:17.873Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/05/75/7d591371c6c39c73de5ce5da5a2cc7b72d1d1cd3f8f4638f553c01c37b11/opentelemetry_semantic_conventions-0.57b0-py3-none-any.whl", hash = "sha256:757f7e76293294f124c827e514c2a3144f191ef175b069ce8d1211e1e38e9e78", size = 201627, upload-time = "2025-07-29T15:12:04.174Z" }, +] + +[[package]] +name = "orjson" +version = "3.10.18" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/81/0b/fea456a3ffe74e70ba30e01ec183a9b26bec4d497f61dcfce1b601059c60/orjson-3.10.18.tar.gz", hash = "sha256:e8da3947d92123eda795b68228cafe2724815621fe35e8e320a9e9593a4bcd53", size = 5422810, upload-time = "2025-04-29T23:30:08.423Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/27/16/2ceb9fb7bc2b11b1e4a3ea27794256e93dee2309ebe297fd131a778cd150/orjson-3.10.18-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a45e5d68066b408e4bc383b6e4ef05e717c65219a9e1390abc6155a520cac402", size = 248927, upload-time = "2025-04-29T23:28:08.643Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3d/e1/d3c0a2bba5b9906badd121da449295062b289236c39c3a7801f92c4682b0/orjson-3.10.18-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be3b9b143e8b9db05368b13b04c84d37544ec85bb97237b3a923f076265ec89c", size = 136995, upload-time = "2025-04-29T23:28:11.503Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d7/51/698dd65e94f153ee5ecb2586c89702c9e9d12f165a63e74eb9ea1299f4e1/orjson-3.10.18-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9b0aa09745e2c9b3bf779b096fa71d1cc2d801a604ef6dd79c8b1bfef52b2f92", size = 132893, upload-time = "2025-04-29T23:28:12.751Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b3/e5/155ce5a2c43a85e790fcf8b985400138ce5369f24ee6770378ee6b691036/orjson-3.10.18-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53a245c104d2792e65c8d225158f2b8262749ffe64bc7755b00024757d957a13", size = 137017, upload-time = "2025-04-29T23:28:14.498Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/46/bb/6141ec3beac3125c0b07375aee01b5124989907d61c72c7636136e4bd03e/orjson-3.10.18-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f9495ab2611b7f8a0a8a505bcb0f0cbdb5469caafe17b0e404c3c746f9900469", size = 138290, upload-time = "2025-04-29T23:28:16.211Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/77/36/6961eca0b66b7809d33c4ca58c6bd4c23a1b914fb23aba2fa2883f791434/orjson-3.10.18-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:73be1cbcebadeabdbc468f82b087df435843c809cd079a565fb16f0f3b23238f", size = 142828, upload-time = "2025-04-29T23:28:18.065Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8b/2f/0c646d5fd689d3be94f4d83fa9435a6c4322c9b8533edbb3cd4bc8c5f69a/orjson-3.10.18-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe8936ee2679e38903df158037a2f1c108129dee218975122e37847fb1d4ac68", size = 132806, upload-time = "2025-04-29T23:28:19.782Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ea/af/65907b40c74ef4c3674ef2bcfa311c695eb934710459841b3c2da212215c/orjson-3.10.18-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7115fcbc8525c74e4c2b608129bef740198e9a120ae46184dac7683191042056", size = 135005, upload-time = "2025-04-29T23:28:21.367Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c7/d1/68bd20ac6a32cd1f1b10d23e7cc58ee1e730e80624e3031d77067d7150fc/orjson-3.10.18-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:771474ad34c66bc4d1c01f645f150048030694ea5b2709b87d3bda273ffe505d", size = 413418, upload-time = "2025-04-29T23:28:23.097Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/31/31/c701ec0bcc3e80e5cb6e319c628ef7b768aaa24b0f3b4c599df2eaacfa24/orjson-3.10.18-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:7c14047dbbea52886dd87169f21939af5d55143dad22d10db6a7514f058156a8", size = 153288, upload-time = "2025-04-29T23:28:25.02Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d9/31/5e1aa99a10893a43cfc58009f9da840990cc8a9ebb75aa452210ba18587e/orjson-3.10.18-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:641481b73baec8db14fdf58f8967e52dc8bda1f2aba3aa5f5c1b07ed6df50b7f", size = 137181, upload-time = "2025-04-29T23:28:26.318Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bf/8c/daba0ac1b8690011d9242a0f37235f7d17df6d0ad941021048523b76674e/orjson-3.10.18-cp310-cp310-win32.whl", hash = "sha256:607eb3ae0909d47280c1fc657c4284c34b785bae371d007595633f4b1a2bbe06", size = 142694, upload-time = "2025-04-29T23:28:28.092Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/16/62/8b687724143286b63e1d0fab3ad4214d54566d80b0ba9d67c26aaf28a2f8/orjson-3.10.18-cp310-cp310-win_amd64.whl", hash = "sha256:8770432524ce0eca50b7efc2a9a5f486ee0113a5fbb4231526d414e6254eba92", size = 134600, upload-time = "2025-04-29T23:28:29.422Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/97/c7/c54a948ce9a4278794f669a353551ce7db4ffb656c69a6e1f2264d563e50/orjson-3.10.18-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:e0a183ac3b8e40471e8d843105da6fbe7c070faab023be3b08188ee3f85719b8", size = 248929, upload-time = "2025-04-29T23:28:30.716Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9e/60/a9c674ef1dd8ab22b5b10f9300e7e70444d4e3cda4b8258d6c2488c32143/orjson-3.10.18-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:5ef7c164d9174362f85238d0cd4afdeeb89d9e523e4651add6a5d458d6f7d42d", size = 133364, upload-time = "2025-04-29T23:28:32.392Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c1/4e/f7d1bdd983082216e414e6d7ef897b0c2957f99c545826c06f371d52337e/orjson-3.10.18-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afd14c5d99cdc7bf93f22b12ec3b294931518aa019e2a147e8aa2f31fd3240f7", size = 136995, upload-time = "2025-04-29T23:28:34.024Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/17/89/46b9181ba0ea251c9243b0c8ce29ff7c9796fa943806a9c8b02592fce8ea/orjson-3.10.18-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7b672502323b6cd133c4af6b79e3bea36bad2d16bca6c1f645903fce83909a7a", size = 132894, upload-time = "2025-04-29T23:28:35.318Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ca/dd/7bce6fcc5b8c21aef59ba3c67f2166f0a1a9b0317dcca4a9d5bd7934ecfd/orjson-3.10.18-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:51f8c63be6e070ec894c629186b1c0fe798662b8687f3d9fdfa5e401c6bd7679", size = 137016, upload-time = "2025-04-29T23:28:36.674Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1c/4a/b8aea1c83af805dcd31c1f03c95aabb3e19a016b2a4645dd822c5686e94d/orjson-3.10.18-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f9478ade5313d724e0495d167083c6f3be0dd2f1c9c8a38db9a9e912cdaf947", size = 138290, upload-time = "2025-04-29T23:28:38.3Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/36/d6/7eb05c85d987b688707f45dcf83c91abc2251e0dd9fb4f7be96514f838b1/orjson-3.10.18-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:187aefa562300a9d382b4b4eb9694806e5848b0cedf52037bb5c228c61bb66d4", size = 142829, upload-time = "2025-04-29T23:28:39.657Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d2/78/ddd3ee7873f2b5f90f016bc04062713d567435c53ecc8783aab3a4d34915/orjson-3.10.18-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9da552683bc9da222379c7a01779bddd0ad39dd699dd6300abaf43eadee38334", size = 132805, upload-time = "2025-04-29T23:28:40.969Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8c/09/c8e047f73d2c5d21ead9c180203e111cddeffc0848d5f0f974e346e21c8e/orjson-3.10.18-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e450885f7b47a0231979d9c49b567ed1c4e9f69240804621be87c40bc9d3cf17", size = 135008, upload-time = "2025-04-29T23:28:42.284Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0c/4b/dccbf5055ef8fb6eda542ab271955fc1f9bf0b941a058490293f8811122b/orjson-3.10.18-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:5e3c9cc2ba324187cd06287ca24f65528f16dfc80add48dc99fa6c836bb3137e", size = 413419, upload-time = "2025-04-29T23:28:43.673Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8a/f3/1eac0c5e2d6d6790bd2025ebfbefcbd37f0d097103d76f9b3f9302af5a17/orjson-3.10.18-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:50ce016233ac4bfd843ac5471e232b865271d7d9d44cf9d33773bcd883ce442b", size = 153292, upload-time = "2025-04-29T23:28:45.573Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1f/b4/ef0abf64c8f1fabf98791819ab502c2c8c1dc48b786646533a93637d8999/orjson-3.10.18-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b3ceff74a8f7ffde0b2785ca749fc4e80e4315c0fd887561144059fb1c138aa7", size = 137182, upload-time = "2025-04-29T23:28:47.229Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a9/a3/6ea878e7b4a0dc5c888d0370d7752dcb23f402747d10e2257478d69b5e63/orjson-3.10.18-cp311-cp311-win32.whl", hash = "sha256:fdba703c722bd868c04702cac4cb8c6b8ff137af2623bc0ddb3b3e6a2c8996c1", size = 142695, upload-time = "2025-04-29T23:28:48.564Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/79/2a/4048700a3233d562f0e90d5572a849baa18ae4e5ce4c3ba6247e4ece57b0/orjson-3.10.18-cp311-cp311-win_amd64.whl", hash = "sha256:c28082933c71ff4bc6ccc82a454a2bffcef6e1d7379756ca567c772e4fb3278a", size = 134603, upload-time = "2025-04-29T23:28:50.442Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/03/45/10d934535a4993d27e1c84f1810e79ccf8b1b7418cef12151a22fe9bb1e1/orjson-3.10.18-cp311-cp311-win_arm64.whl", hash = "sha256:a6c7c391beaedd3fa63206e5c2b7b554196f14debf1ec9deb54b5d279b1b46f5", size = 131400, upload-time = "2025-04-29T23:28:51.838Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/21/1a/67236da0916c1a192d5f4ccbe10ec495367a726996ceb7614eaa687112f2/orjson-3.10.18-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:50c15557afb7f6d63bc6d6348e0337a880a04eaa9cd7c9d569bcb4e760a24753", size = 249184, upload-time = "2025-04-29T23:28:53.612Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b3/bc/c7f1db3b1d094dc0c6c83ed16b161a16c214aaa77f311118a93f647b32dc/orjson-3.10.18-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:356b076f1662c9813d5fa56db7d63ccceef4c271b1fb3dd522aca291375fcf17", size = 133279, upload-time = "2025-04-29T23:28:55.055Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/af/84/664657cd14cc11f0d81e80e64766c7ba5c9b7fc1ec304117878cc1b4659c/orjson-3.10.18-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:559eb40a70a7494cd5beab2d73657262a74a2c59aff2068fdba8f0424ec5b39d", size = 136799, upload-time = "2025-04-29T23:28:56.828Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9a/bb/f50039c5bb05a7ab024ed43ba25d0319e8722a0ac3babb0807e543349978/orjson-3.10.18-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f3c29eb9a81e2fbc6fd7ddcfba3e101ba92eaff455b8d602bf7511088bbc0eae", size = 132791, upload-time = "2025-04-29T23:28:58.751Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/93/8c/ee74709fc072c3ee219784173ddfe46f699598a1723d9d49cbc78d66df65/orjson-3.10.18-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6612787e5b0756a171c7d81ba245ef63a3533a637c335aa7fcb8e665f4a0966f", size = 137059, upload-time = "2025-04-29T23:29:00.129Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6a/37/e6d3109ee004296c80426b5a62b47bcadd96a3deab7443e56507823588c5/orjson-3.10.18-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ac6bd7be0dcab5b702c9d43d25e70eb456dfd2e119d512447468f6405b4a69c", size = 138359, upload-time = "2025-04-29T23:29:01.704Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4f/5d/387dafae0e4691857c62bd02839a3bf3fa648eebd26185adfac58d09f207/orjson-3.10.18-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9f72f100cee8dde70100406d5c1abba515a7df926d4ed81e20a9730c062fe9ad", size = 142853, upload-time = "2025-04-29T23:29:03.576Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/27/6f/875e8e282105350b9a5341c0222a13419758545ae32ad6e0fcf5f64d76aa/orjson-3.10.18-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9dca85398d6d093dd41dc0983cbf54ab8e6afd1c547b6b8a311643917fbf4e0c", size = 133131, upload-time = "2025-04-29T23:29:05.753Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/48/b2/73a1f0b4790dcb1e5a45f058f4f5dcadc8a85d90137b50d6bbc6afd0ae50/orjson-3.10.18-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:22748de2a07fcc8781a70edb887abf801bb6142e6236123ff93d12d92db3d406", size = 134834, upload-time = "2025-04-29T23:29:07.35Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/56/f5/7ed133a5525add9c14dbdf17d011dd82206ca6840811d32ac52a35935d19/orjson-3.10.18-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:3a83c9954a4107b9acd10291b7f12a6b29e35e8d43a414799906ea10e75438e6", size = 413368, upload-time = "2025-04-29T23:29:09.301Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/11/7c/439654221ed9c3324bbac7bdf94cf06a971206b7b62327f11a52544e4982/orjson-3.10.18-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:303565c67a6c7b1f194c94632a4a39918e067bd6176a48bec697393865ce4f06", size = 153359, upload-time = "2025-04-29T23:29:10.813Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/48/e7/d58074fa0cc9dd29a8fa2a6c8d5deebdfd82c6cfef72b0e4277c4017563a/orjson-3.10.18-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:86314fdb5053a2f5a5d881f03fca0219bfdf832912aa88d18676a5175c6916b5", size = 137466, upload-time = "2025-04-29T23:29:12.26Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/57/4d/fe17581cf81fb70dfcef44e966aa4003360e4194d15a3f38cbffe873333a/orjson-3.10.18-cp312-cp312-win32.whl", hash = "sha256:187ec33bbec58c76dbd4066340067d9ece6e10067bb0cc074a21ae3300caa84e", size = 142683, upload-time = "2025-04-29T23:29:13.865Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e6/22/469f62d25ab5f0f3aee256ea732e72dc3aab6d73bac777bd6277955bceef/orjson-3.10.18-cp312-cp312-win_amd64.whl", hash = "sha256:f9f94cf6d3f9cd720d641f8399e390e7411487e493962213390d1ae45c7814fc", size = 134754, upload-time = "2025-04-29T23:29:15.338Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/10/b0/1040c447fac5b91bc1e9c004b69ee50abb0c1ffd0d24406e1350c58a7fcb/orjson-3.10.18-cp312-cp312-win_arm64.whl", hash = "sha256:3d600be83fe4514944500fa8c2a0a77099025ec6482e8087d7659e891f23058a", size = 131218, upload-time = "2025-04-29T23:29:17.324Z" }, +] + +[[package]] +name = "ormsgpack" +version = "1.5.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c5/70/11a6ab33136c2f98bb64e96743a55c7a87b87bae0413460cab7cc5764951/ormsgpack-1.5.0.tar.gz", hash = "sha256:00c0743ebaa8d21f1c868fbb609c99151ea79e67fec98b51a29077efd91ce348", size = 54353, upload-time = "2024-04-20T07:13:53.382Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e5/6f/38f1466b866eecc3ad9851573dec366b7eb7f0788725324825561a1e9abd/ormsgpack-1.5.0-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:98efdbb1f8c4a172a05143bbbc000491ca7d99644521ad90a15d5e96c7895fba", size = 426631, upload-time = "2024-04-20T07:13:09.925Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f6/31/4542b5c2e25218f559722907866c8d00da4fad2842912920a0add257904a/ormsgpack-1.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:091bb019b3101dd034515dce13852f4250aa2ee406e6ac5cb8d745dc1e146e6f", size = 276769, upload-time = "2024-04-20T07:13:12.213Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8f/cd/228d631ef16b640301d99515dc906a4d5339096ca83eb30b7bf64b2623eb/ormsgpack-1.5.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7ee97e618e852891e3cb3ae8a0662d54e289e7ba438fc245e70643c6f0b16849", size = 280603, upload-time = "2024-04-20T07:13:14.085Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/53/9b/1fc54fd06f4b46f4ce5041ce3382abe36e7f63f621809925369435494eaa/ormsgpack-1.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:789725c38493509a1c7a0a25d9baaf2edc120a38a51e0a85008215de25d034c7", size = 276102, upload-time = "2024-04-20T07:13:15.431Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f2/0b/53946f84370eafd9f80ffeeb21ecbeb0caac2ab044c79e33e555fb1b95bc/ormsgpack-1.5.0-cp310-none-win_amd64.whl", hash = "sha256:c81dfcaef16e1a0583f1491441d6f7888b742d72dc299fdeddc3da069774ede4", size = 154899, upload-time = "2024-04-20T07:13:17.279Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2b/5d/86e05c01247081ecae8dfbf8bf160ff4d7656417f6a01bae51d23d60cc2c/ormsgpack-1.5.0-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:a94c8dfe1ee41e536b9c20af48b191fcae89b015b7fcbef0909915c0d5624c8d", size = 426630, upload-time = "2024-04-20T07:13:18.991Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3e/36/389f38d3ab157ceeef91971f82eec7058bc7c3453d9d4d5ef29892889761/ormsgpack-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb56792348a674ae8915106bdf7b02720852fc195fdda23884c24a5f05e5fd9a", size = 276769, upload-time = "2024-04-20T07:13:21.044Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/db/2c/a51aec641011337813cdca24e5a4eee6f5a2bbc7f489af04144fc9d8a307/ormsgpack-1.5.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:044856a25c2fa19261f02a6f7a9b5329e90a51cf45780cface01b667cc21ac3d", size = 280602, upload-time = "2024-04-20T07:13:22.98Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c0/66/d33df5efe53dae713dba02876fe179c2e1bfeab7dc1896d4c8cef7d6fb80/ormsgpack-1.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:111214ea0970eb49b19ffa1fde94996f09efa4d8585986be9d5b81492fa1e3fc", size = 276101, upload-time = "2024-04-20T07:13:24.927Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/63/c0/f4270c6622198fc121847a9e9b567e88cc60e84b0125647163e97fa9a89b/ormsgpack-1.5.0-cp311-none-win_amd64.whl", hash = "sha256:e2a28bfc10cb492659c8704007567483aa3cfc863a22fd5973309373f396c9e1", size = 154908, upload-time = "2024-04-20T07:13:26.759Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/47/19/df1626f9c149a20d2273eecf97ae913a026be2730264db86126ac3e594db/ormsgpack-1.5.0-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:a921b0d54b5fb5ba1ea4e87c65caa8992736224f1fc5ce8f46a882e918c8e22d", size = 427447, upload-time = "2024-04-20T07:13:28.226Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/82/cc/bad6d4a237ff0943cb1c8c4a12fe95bcd7ff81c0f8bca26340efd599aa1d/ormsgpack-1.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6d423668e2c3abdbc474562b1c73360ff7326f06cb9532dcb73254b5b63dae4", size = 276867, upload-time = "2024-04-20T07:13:29.436Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c7/d1/3ed38a54923fe04eace750c0f0adbc149fb2b028375c71e864aee5e2d6d6/ormsgpack-1.5.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eeb2dd4ed3e503a8266dcbfbb8d810a36baa34e4bb4229e90e9c213058a06d74", size = 280728, upload-time = "2024-04-20T07:13:31.459Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9b/52/0261a80de2486793b4844c2668b17f49d03a20aba13a8d3d975831b1d866/ormsgpack-1.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f13bd643df1324e8797caba4c5c0168a87524df8424e8413ba29723e89a586a", size = 276644, upload-time = "2024-04-20T07:13:32.708Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b7/f0/2ebda08824d4f658c5ad048bcbe64e352b637b661b4d26c51d7403d30569/ormsgpack-1.5.0-cp312-none-win_amd64.whl", hash = "sha256:e016da381a126478c4bafab0ae19d3a2537f6471341ecced4bb61471e8841cad", size = 155198, upload-time = "2024-04-20T07:13:34.759Z" }, +] + +[[package]] +name = "outcome" +version = "1.3.0.post0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "attrs" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/98/df/77698abfac98571e65ffeb0c1fba8ffd692ab8458d617a0eed7d9a8d38f2/outcome-1.3.0.post0.tar.gz", hash = "sha256:9dcf02e65f2971b80047b377468e72a268e15c0af3cf1238e6ff14f7f91143b8" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/55/8b/5ab7257531a5d830fc8000c476e63c935488d74609b50f9384a643ec0a62/outcome-1.3.0.post0-py2.py3-none-any.whl", hash = "sha256:e771c5ce06d1415e356078d3bdd68523f284b4ce5419828922b6871e65eda82b" }, +] + +[[package]] +name = "packaging" +version = "24.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950, upload-time = "2024-11-08T09:47:47.202Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451, upload-time = "2024-11-08T09:47:44.722Z" }, +] + +[[package]] +name = "pandas" +version = "2.2.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/packages/aa/70/c853aec59839bceed032d52010ff5f1b8d87dc3114b762e4ba2727661a3b/pandas-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1948ddde24197a0f7add2bdc4ca83bf2b1ef84a1bc8ccffd95eda17fd836ecb5", size = 12580827, upload-time = "2024-09-20T13:08:42.347Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/99/f2/c4527768739ffa4469b2b4fff05aa3768a478aed89a2f271a79a40eee984/pandas-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:381175499d3802cde0eabbaf6324cce0c4f5d52ca6f8c377c29ad442f50f6348", size = 11303897, upload-time = "2024-09-20T13:08:45.807Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ed/12/86c1747ea27989d7a4064f806ce2bae2c6d575b950be087837bdfcabacc9/pandas-2.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d9c45366def9a3dd85a6454c0e7908f2b3b8e9c138f5dc38fed7ce720d8453ed", size = 66480908, upload-time = "2024-09-20T18:37:13.513Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/44/50/7db2cd5e6373ae796f0ddad3675268c8d59fb6076e66f0c339d61cea886b/pandas-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86976a1c5b25ae3f8ccae3a5306e443569ee3c3faf444dfd0f41cda24667ad57", size = 13064210, upload-time = "2024-09-20T13:08:48.325Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/61/61/a89015a6d5536cb0d6c3ba02cebed51a95538cf83472975275e28ebf7d0c/pandas-2.2.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b8661b0238a69d7aafe156b7fa86c44b881387509653fdf857bebc5e4008ad42", size = 16754292, upload-time = "2024-09-20T19:01:54.443Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ce/0d/4cc7b69ce37fac07645a94e1d4b0880b15999494372c1523508511b09e40/pandas-2.2.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37e0aced3e8f539eccf2e099f65cdb9c8aa85109b0be6e93e2baff94264bdc6f", size = 14416379, upload-time = "2024-09-20T13:08:50.882Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/31/9e/6ebb433de864a6cd45716af52a4d7a8c3c9aaf3a98368e61db9e69e69a9c/pandas-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:56534ce0746a58afaf7942ba4863e0ef81c9c50d3f0ae93e9497d6a41a057645", size = 11598471, upload-time = "2024-09-20T13:08:53.332Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a8/44/d9502bf0ed197ba9bf1103c9867d5904ddcaf869e52329787fc54ed70cc8/pandas-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66108071e1b935240e74525006034333f98bcdb87ea116de573a6a0dccb6c039", size = 12602222, upload-time = "2024-09-20T13:08:56.254Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/52/11/9eac327a38834f162b8250aab32a6781339c69afe7574368fffe46387edf/pandas-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7c2875855b0ff77b2a64a0365e24455d9990730d6431b9e0ee18ad8acee13dbd", size = 11321274, upload-time = "2024-09-20T13:08:58.645Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/45/fb/c4beeb084718598ba19aa9f5abbc8aed8b42f90930da861fcb1acdb54c3a/pandas-2.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd8d0c3be0515c12fed0bdbae072551c8b54b7192c7b1fda0ba56059a0179698", size = 15579836, upload-time = "2024-09-20T19:01:57.571Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cd/5f/4dba1d39bb9c38d574a9a22548c540177f78ea47b32f99c0ff2ec499fac5/pandas-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c124333816c3a9b03fbeef3a9f230ba9a737e9e5bb4060aa2107a86cc0a497fc", size = 13058505, upload-time = "2024-09-20T13:09:01.501Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b9/57/708135b90391995361636634df1f1130d03ba456e95bcf576fada459115a/pandas-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:63cc132e40a2e084cf01adf0775b15ac515ba905d7dcca47e9a251819c575ef3", size = 16744420, upload-time = "2024-09-20T19:02:00.678Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/86/4a/03ed6b7ee323cf30404265c284cee9c65c56a212e0a08d9ee06984ba2240/pandas-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29401dbfa9ad77319367d36940cd8a0b3a11aba16063e39632d98b0e931ddf32", size = 14440457, upload-time = "2024-09-20T13:09:04.105Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ed/8c/87ddf1fcb55d11f9f847e3c69bb1c6f8e46e2f40ab1a2d2abadb2401b007/pandas-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:3fc6873a41186404dad67245896a6e440baacc92f5b716ccd1bc9ed2995ab2c5", size = 11617166, upload-time = "2024-09-20T13:09:06.917Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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 = "parameterized" +version = "0.9.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ea/49/00c0c0cc24ff4266025a53e41336b79adaa5a4ebfad214f433d623f9865e/parameterized-0.9.0.tar.gz", hash = "sha256:7fc905272cefa4f364c1a3429cbbe9c0f98b793988efb5bf90aac80f08db09b1", size = 24351, upload-time = "2023-03-27T02:01:11.592Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/00/2f/804f58f0b856ab3bf21617cccf5b39206e6c4c94c2cd227bde125ea6105f/parameterized-0.9.0-py2.py3-none-any.whl", hash = "sha256:4e0758e3d41bea3bbd05ec14fc2c24736723f243b28d702081aef438c9372b1b", size = 20475, upload-time = "2023-03-27T02:01:09.31Z" }, +] + +[[package]] +name = "patsy" +version = "1.0.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d1/81/74f6a65b848ffd16c18f920620ce999fe45fe27f01ab3911260ce4ed85e4/patsy-1.0.1.tar.gz", hash = "sha256:e786a9391eec818c054e359b737bbce692f051aee4c661f4141cc88fb459c0c4", size = 396010, upload-time = "2024-11-12T14:10:54.642Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/87/2b/b50d3d08ea0fc419c183a84210571eba005328efa62b6b98bc28e9ead32a/patsy-1.0.1-py2.py3-none-any.whl", hash = "sha256:751fb38f9e97e62312e921a1954b81e1bb2bcda4f5eeabaf94db251ee791509c", size = 232923, upload-time = "2024-11-12T14:10:52.85Z" }, +] + +[[package]] +name = "pcodedmp" +version = "1.2.6" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "oletools" }, + { name = "win-unicode-console", marker = "platform_python_implementation != 'PyPy' and sys_platform == 'win32'" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3d/20/6d461e29135f474408d0d7f95b2456a9ba245560768ee51b788af10f7429/pcodedmp-1.2.6.tar.gz", hash = "sha256:025f8c809a126f45a082ffa820893e6a8d990d9d7ddb68694b5a9f0a6dbcd955", size = 35549, upload-time = "2019-07-30T18:05:42.516Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ba/72/b380fb5c89d89c3afafac8cf02a71a45f4f4a4f35531ca949a34683962d1/pcodedmp-1.2.6-py2.py3-none-any.whl", hash = "sha256:4441f7c0ab4cbda27bd4668db3b14f36261d86e5059ce06c0828602cbe1c4278", size = 30939, upload-time = "2019-07-30T18:05:40.483Z" }, +] + +[[package]] +name = "pdfminer-six" +version = "20221105" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "charset-normalizer" }, + { name = "cryptography" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ac/6e/89c532d108e362cbaf76fdb972e7a5e85723c225f08e1646fb86878d4f7f/pdfminer.six-20221105.tar.gz", hash = "sha256:8448ab7b939d18b64820478ecac5394f482d7a79f5f7eaa7703c6c959c175e1d", size = 7361391, upload-time = "2022-11-05T16:33:46.725Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/46/68/b3fb5f073bcd3df9143a3520289c147351bfa3c1b096d44081f38fd1c247/pdfminer.six-20221105-py3-none-any.whl", hash = "sha256:1eaddd712d5b2732f8ac8486824533514f8ba12a0787b3d5fe1e686cd826532d", size = 5613896, upload-time = "2022-11-05T16:33:45.016Z" }, +] + +[[package]] +name = "pdfplumber" +version = "0.10.4" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "pdfminer-six" }, + { name = "pillow" }, + { name = "pypdfium2" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/25/b2/a3ebd1987165b088dfa328fc1ddbf621b62f1785a4daafb4090c91246b61/pdfplumber-0.10.4.tar.gz", hash = "sha256:1c83a4e1fe75525ce1f161fa55a8142209a2da69b45542ce2c45be879e804fd6", size = 102756, upload-time = "2024-02-10T23:38:01.106Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/53/5c/4523bfce8ba473b0e33931f9638f69c3573b3b72b0c63c73d779848d182f/pdfplumber-0.10.4-py3-none-any.whl", hash = "sha256:c8f200259703324cd39a5c93b181a0d2370a6b2b6da670c117e75c3da6aca4a4", size = 54718, upload-time = "2024-02-10T23:37:58.882Z" }, +] + +[[package]] +name = "peewee" +version = "3.17.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8d/a5/89cdbc4a7f6d7a0624c120be102db770ee717aa371066581e3daf2beb96f/peewee-3.17.1.tar.gz", hash = "sha256:e009ac4227c4fdc0058a56e822ad5987684f0a1fbb20fed577200785102581c3", size = 2951636, upload-time = "2024-02-05T15:04:14.549Z" } + +[[package]] +name = "pillow" +version = "10.4.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cd/74/ad3d526f3bf7b6d3f408b73fde271ec69dfac8b81341a318ce825f2b3812/pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06", size = 46555059, upload-time = "2024-07-01T09:48:43.583Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0e/69/a31cccd538ca0b5272be2a38347f8839b97a14be104ea08b0db92f749c74/pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e", size = 3509271, upload-time = "2024-07-01T09:45:22.07Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9a/9e/4143b907be8ea0bce215f2ae4f7480027473f8b61fcedfda9d851082a5d2/pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d", size = 3375658, upload-time = "2024-07-01T09:45:25.292Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8a/25/1fc45761955f9359b1169aa75e241551e74ac01a09f487adaaf4c3472d11/pillow-10.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856", size = 4332075, upload-time = "2024-07-01T09:45:27.94Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5e/dd/425b95d0151e1d6c951f45051112394f130df3da67363b6bc75dc4c27aba/pillow-10.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f", size = 4444808, upload-time = "2024-07-01T09:45:30.305Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b1/84/9a15cc5726cbbfe7f9f90bfb11f5d028586595907cd093815ca6644932e3/pillow-10.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b", size = 4356290, upload-time = "2024-07-01T09:45:32.868Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b5/5b/6651c288b08df3b8c1e2f8c1152201e0b25d240e22ddade0f1e242fc9fa0/pillow-10.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc", size = 4525163, upload-time = "2024-07-01T09:45:35.279Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/07/8b/34854bf11a83c248505c8cb0fcf8d3d0b459a2246c8809b967963b6b12ae/pillow-10.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e", size = 4463100, upload-time = "2024-07-01T09:45:37.74Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/78/63/0632aee4e82476d9cbe5200c0cdf9ba41ee04ed77887432845264d81116d/pillow-10.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46", size = 4592880, upload-time = "2024-07-01T09:45:39.89Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/df/56/b8663d7520671b4398b9d97e1ed9f583d4afcbefbda3c6188325e8c297bd/pillow-10.4.0-cp310-cp310-win32.whl", hash = "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984", size = 2235218, upload-time = "2024-07-01T09:45:42.771Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f4/72/0203e94a91ddb4a9d5238434ae6c1ca10e610e8487036132ea9bf806ca2a/pillow-10.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141", size = 2554487, upload-time = "2024-07-01T09:45:45.176Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bd/52/7e7e93d7a6e4290543f17dc6f7d3af4bd0b3dd9926e2e8a35ac2282bc5f4/pillow-10.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1", size = 2243219, upload-time = "2024-07-01T09:45:47.274Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a7/62/c9449f9c3043c37f73e7487ec4ef0c03eb9c9afc91a92b977a67b3c0bbc5/pillow-10.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c", size = 3509265, upload-time = "2024-07-01T09:45:49.812Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f4/5f/491dafc7bbf5a3cc1845dc0430872e8096eb9e2b6f8161509d124594ec2d/pillow-10.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be", size = 3375655, upload-time = "2024-07-01T09:45:52.462Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/73/d5/c4011a76f4207a3c151134cd22a1415741e42fa5ddecec7c0182887deb3d/pillow-10.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3", size = 4340304, upload-time = "2024-07-01T09:45:55.006Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ac/10/c67e20445a707f7a610699bba4fe050583b688d8cd2d202572b257f46600/pillow-10.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6", size = 4452804, upload-time = "2024-07-01T09:45:58.437Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a9/83/6523837906d1da2b269dee787e31df3b0acb12e3d08f024965a3e7f64665/pillow-10.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe", size = 4365126, upload-time = "2024-07-01T09:46:00.713Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ba/e5/8c68ff608a4203085158cff5cc2a3c534ec384536d9438c405ed6370d080/pillow-10.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319", size = 4533541, upload-time = "2024-07-01T09:46:03.235Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f4/7c/01b8dbdca5bc6785573f4cee96e2358b0918b7b2c7b60d8b6f3abf87a070/pillow-10.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d", size = 4471616, upload-time = "2024-07-01T09:46:05.356Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c8/57/2899b82394a35a0fbfd352e290945440e3b3785655a03365c0ca8279f351/pillow-10.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696", size = 4600802, upload-time = "2024-07-01T09:46:08.145Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4d/d7/a44f193d4c26e58ee5d2d9db3d4854b2cfb5b5e08d360a5e03fe987c0086/pillow-10.4.0-cp311-cp311-win32.whl", hash = "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496", size = 2235213, upload-time = "2024-07-01T09:46:10.211Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c1/d0/5866318eec2b801cdb8c82abf190c8343d8a1cd8bf5a0c17444a6f268291/pillow-10.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91", size = 2554498, upload-time = "2024-07-01T09:46:12.685Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d4/c8/310ac16ac2b97e902d9eb438688de0d961660a87703ad1561fd3dfbd2aa0/pillow-10.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22", size = 2243219, upload-time = "2024-07-01T09:46:14.83Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/05/cb/0353013dc30c02a8be34eb91d25e4e4cf594b59e5a55ea1128fde1e5f8ea/pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94", size = 3509350, upload-time = "2024-07-01T09:46:17.177Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e7/cf/5c558a0f247e0bf9cec92bff9b46ae6474dd736f6d906315e60e4075f737/pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597", size = 3374980, upload-time = "2024-07-01T09:46:19.169Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/84/48/6e394b86369a4eb68b8a1382c78dc092245af517385c086c5094e3b34428/pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80", size = 4343799, upload-time = "2024-07-01T09:46:21.883Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3b/f3/a8c6c11fa84b59b9df0cd5694492da8c039a24cd159f0f6918690105c3be/pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca", size = 4459973, upload-time = "2024-07-01T09:46:24.321Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7d/1b/c14b4197b80150fb64453585247e6fb2e1d93761fa0fa9cf63b102fde822/pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef", size = 4370054, upload-time = "2024-07-01T09:46:26.825Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/55/77/40daddf677897a923d5d33329acd52a2144d54a9644f2a5422c028c6bf2d/pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a", size = 4539484, upload-time = "2024-07-01T09:46:29.355Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/40/54/90de3e4256b1207300fb2b1d7168dd912a2fb4b2401e439ba23c2b2cabde/pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b", size = 4477375, upload-time = "2024-07-01T09:46:31.756Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/13/24/1bfba52f44193860918ff7c93d03d95e3f8748ca1de3ceaf11157a14cf16/pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9", size = 4608773, upload-time = "2024-07-01T09:46:33.73Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/55/04/5e6de6e6120451ec0c24516c41dbaf80cce1b6451f96561235ef2429da2e/pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42", size = 2235690, upload-time = "2024-07-01T09:46:36.587Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/74/0a/d4ce3c44bca8635bd29a2eab5aa181b654a734a29b263ca8efe013beea98/pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a", size = 2554951, upload-time = "2024-07-01T09:46:38.777Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b5/ca/184349ee40f2e92439be9b3502ae6cfc43ac4b50bc4fc6b3de7957563894/pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9", size = 2243427, upload-time = "2024-07-01T09:46:43.15Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/38/30/095d4f55f3a053392f75e2eae45eba3228452783bab3d9a920b951ac495c/pillow-10.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4", size = 3493889, upload-time = "2024-07-01T09:48:04.815Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f3/e8/4ff79788803a5fcd5dc35efdc9386af153569853767bff74540725b45863/pillow-10.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da", size = 3346160, upload-time = "2024-07-01T09:48:07.206Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d7/ac/4184edd511b14f760c73f5bb8a5d6fd85c591c8aff7c2229677a355c4179/pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026", size = 3435020, upload-time = "2024-07-01T09:48:09.66Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/da/21/1749cd09160149c0a246a81d646e05f35041619ce76f6493d6a96e8d1103/pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e", size = 3490539, upload-time = "2024-07-01T09:48:12.529Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b6/f5/f71fe1888b96083b3f6dfa0709101f61fc9e972c0c8d04e9d93ccef2a045/pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5", size = 3476125, upload-time = "2024-07-01T09:48:14.891Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/96/b9/c0362c54290a31866c3526848583a2f45a535aa9d725fd31e25d318c805f/pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885", size = 3579373, upload-time = "2024-07-01T09:48:17.601Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/52/3b/ce7a01026a7cf46e5452afa86f97a5e88ca97f562cafa76570178ab56d8d/pillow-10.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5", size = 2554661, upload-time = "2024-07-01T09:48:20.293Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.3.8" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362, upload-time = "2025-05-07T22:47:42.121Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" }, +] + +[[package]] +name = "playwright" +version = "1.54.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "greenlet" }, + { name = "pyee" }, +] +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f3/09/33d5bfe393a582d8dac72165a9e88b274143c9df411b65ece1cc13f42988/playwright-1.54.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:bf3b845af744370f1bd2286c2a9536f474cc8a88dc995b72ea9a5be714c9a77d", size = 40439034, upload-time = "2025-07-22T13:58:04.816Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e1/7b/51882dc584f7aa59f446f2bb34e33c0e5f015de4e31949e5b7c2c10e54f0/playwright-1.54.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:780928b3ca2077aea90414b37e54edd0c4bbb57d1aafc42f7aa0b3fd2c2fac02", size = 38702308, upload-time = "2025-07-22T13:58:08.211Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/73/a1/7aa8ae175b240c0ec8849fcf000e078f3c693f9aa2ffd992da6550ea0dff/playwright-1.54.0-py3-none-macosx_11_0_universal2.whl", hash = "sha256:81d0b6f28843b27f288cfe438af0a12a4851de57998009a519ea84cee6fbbfb9", size = 40439037, upload-time = "2025-07-22T13:58:11.37Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/34/a9/45084fd23b6206f954198296ce39b0acf50debfdf3ec83a593e4d73c9c8a/playwright-1.54.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:09919f45cc74c64afb5432646d7fef0d19fff50990c862cb8d9b0577093f40cc", size = 45920135, upload-time = "2025-07-22T13:58:14.494Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/02/d4/6a692f4c6db223adc50a6e53af405b45308db39270957a6afebddaa80ea2/playwright-1.54.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13ae206c55737e8e3eae51fb385d61c0312eeef31535643bb6232741b41b6fdc", size = 45302695, upload-time = "2025-07-22T13:58:18.901Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/72/7a/4ee60a1c3714321db187bebbc40d52cea5b41a856925156325058b5fca5a/playwright-1.54.0-py3-none-win32.whl", hash = "sha256:0b108622ffb6906e28566f3f31721cd57dda637d7e41c430287804ac01911f56", size = 35469309, upload-time = "2025-07-22T13:58:21.917Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/aa/77/8f8fae05a242ef639de963d7ae70a69d0da61d6d72f1207b8bbf74ffd3e7/playwright-1.54.0-py3-none-win_amd64.whl", hash = "sha256:9e5aee9ae5ab1fdd44cd64153313a2045b136fcbcfb2541cc0a3d909132671a2", size = 35469311, upload-time = "2025-07-22T13:58:24.707Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/33/ff/99a6f4292a90504f2927d34032a4baf6adb498dc3f7cf0f3e0e22899e310/playwright-1.54.0-py3-none-win_arm64.whl", hash = "sha256:a975815971f7b8dca505c441a4c56de1aeb56a211290f8cc214eeef5524e8d75", size = 31239119, upload-time = "2025-07-22T13:58:27.56Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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 = "pluginlib" +version = "0.9.4" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "setuptools" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/58/38/ca974ba2d8ccc7954d8ccb0394cce184ac6269bd1fbfe06f70a0da3c8946/pluginlib-0.9.4.tar.gz", hash = "sha256:88727037138f759a3952f6391ae3751536f04ad8be6023607620ea49695a3a83", size = 46541, upload-time = "2024-11-24T17:14:53.814Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b0/b5/c869b3d2ed1613afeb02c635be11f5d35fa5b2b665f4d059cfe5b8e82941/pluginlib-0.9.4-py2.py3-none-any.whl", hash = "sha256:d4cfb7d74a6d2454e256b6512fbc4bc2dd7620cb7764feb67331ef56ce4b33f2", size = 25132, upload-time = "2024-11-24T17:14:52.824Z" }, +] + +[[package]] +name = "polars-lts-cpu" +version = "1.9.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/30/b8/81ada35f862f5b37456b225f578128ec4314da5a4627b440acf01180fbf9/polars_lts_cpu-1.9.0.tar.gz", hash = "sha256:4848ea9f54cb59cce6b08a5f161a5c488684b55729f2ef076c65ca92759a985e", size = 4028430, upload-time = "2024-10-01T19:54:10.186Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9a/72/24127dc03627b0c50bef745b1ee0451359de17ba8e9a70027fdaf9f9c76e/polars_lts_cpu-1.9.0-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:1dd1484b9a08171184f198c7b1a1bdcea7457de4a7d481949ec410ee96a49bc9", size = 31460193, upload-time = "2024-10-01T19:53:28.538Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0d/36/2a816b333b2eeff12187026958cd2fac4a31f7f00e8a50f85ab08ed0531e/polars_lts_cpu-1.9.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:fb10fd9f8a8c0145ec70c4f3dde3ff1ef9f18079964c9dc40e8aac3d7521a862", size = 28171134, upload-time = "2024-10-01T19:53:32.642Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/69/df/c393f41ad2da9cb0397436e3b424a1982b093d02181fc09ffbcefb98ff30/polars_lts_cpu-1.9.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eb332c2e5837759a5cb406566a1d0feddf145711176cf89f745dba010c739a9", size = 32625386, upload-time = "2024-10-01T19:53:37.792Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/27/f2/cad4bccf463695de44869d17419f891b356b3af7275dcc64be95264317e5/polars_lts_cpu-1.9.0-cp38-abi3-manylinux_2_24_aarch64.whl", hash = "sha256:e8f8eb6d9c5b017eb4ffb6276255b77d4eb8508e30bc2898e2b55ebe8bc9715c", size = 29764771, upload-time = "2024-10-01T19:53:41.776Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/53/b1/d7236cbcef17de67938be6068ff72153bffed7543b496a93b31b88e0c670/polars_lts_cpu-1.9.0-cp38-abi3-win_amd64.whl", hash = "sha256:0c199d86e3d6b775264e3a596ebfbae4c7848b88bed4248822b7b498b2e96f2d", size = 32949159, upload-time = "2024-10-01T19:53:45.92Z" }, +] + +[[package]] +name = "pooch" +version = "1.8.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "packaging" }, + { name = "platformdirs" }, + { name = "requests" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c6/77/b3d3e00c696c16cf99af81ef7b1f5fe73bd2a307abca41bd7605429fe6e5/pooch-1.8.2.tar.gz", hash = "sha256:76561f0de68a01da4df6af38e9955c4c9d1a5c90da73f7e40276a5728ec83d10", size = 59353, upload-time = "2024-06-06T16:53:46.224Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a8/87/77cc11c7a9ea9fd05503def69e3d18605852cd0d4b0d3b8f15bbeb3ef1d1/pooch-1.8.2-py3-none-any.whl", hash = "sha256:3529a57096f7198778a5ceefd5ac3ef0e4d06a6ddaf9fc2d609b806f25302c47", size = 64574, upload-time = "2024-06-06T16:53:44.343Z" }, +] + +[[package]] +name = "pot" +version = "0.9.5" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "numpy" }, + { name = "scipy" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c1/40/3e0c8dd88328d944f9d82b30cafd2a1c911bddff0b8bccc8dc9dd5e45b7c/pot-0.9.5.tar.gz", hash = "sha256:9644ee7ff51c3cffa3c2632b9dd9dff4f3520266f9fb771450935ffb646d6042", size = 440808, upload-time = "2024-11-07T10:05:05.567Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/87/53/acd66a8e50f992e6ca578181009e81d367ad738d0ac135f63d0de3ca92cd/POT-0.9.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:34d766c38e65a69c087b01a854fe89fbd152c3e8af93da2227b6c40aed6d37b9", size = 410989, upload-time = "2024-11-07T10:04:04.166Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/24/51/43c68e7cb1dc7c40286d9e19f6cb599108cd01c2b32307296eba9cb01a05/POT-0.9.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5407377256de11b6fdc94bbba9b50ea5a2301570905fc9014541cc8473806d9", size = 351111, upload-time = "2024-11-07T10:04:06.604Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3f/87/17069069948e40fa0e41366e6412322c7849d4b2a0ddae0428d10b571604/POT-0.9.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2f37039cd356198c1fb994e7d935b9bf75d44f2a40319d298bf8cc149eb360d5", size = 344289, upload-time = "2024-11-07T10:04:08.151Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/21/49/7bbb5ac2989abd775ae200cdbcf1a2e023cf07e8d1d6afc7d673d4e380d3/POT-0.9.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00a18427c9abdd107a2285ea0a814c6b22e95a1af8f88a37c56f23cd216f7a6b", size = 858699, upload-time = "2024-11-07T10:04:10.231Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/97/ad/1724a238cef180c04a3d63e8702cbe91f0abe946eb7a55c3857cd0ac1d9b/POT-0.9.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0dc608cea1107289a58dec33cddc1b0a3fea77ff36d66e2c8ac7aeea543969a", size = 865565, upload-time = "2024-11-07T10:04:12.421Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1c/e9/a1901cbbf765b765ab4adace1711adc3eef01db526dc898e31fbdca653a5/POT-0.9.5-cp310-cp310-win32.whl", hash = "sha256:8312bee055389db47adab063749c8d77b5981534177ca6cd9b91e4fb68f69d00", size = 344137, upload-time = "2024-11-07T10:04:14.693Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/95/00/2ef88c57c0ee5ff55a95bcb3ff62d904039bb460809d7577ec314b5e7186/POT-0.9.5-cp310-cp310-win_amd64.whl", hash = "sha256:043706d69202ac87e140121ba32ed1b038f2b3fc4a5549586187239a583cd50d", size = 348385, upload-time = "2024-11-07T10:04:15.851Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/08/81/c9eaa405d40567452d102385a2077b4d34f7961dd7ea3354b7749efd4ea7/POT-0.9.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b5f000da00e408ff781672a4895bfa8daacec055bd534c9e66ead479f3c6d83c", size = 410977, upload-time = "2024-11-07T10:04:17.396Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/43/32/8d319ab8eee96397569115aac644b19136170966667c59b026c277e1b026/POT-0.9.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9eddd9ff29bdb17d4db8ba00ba18d42656c694a128591502bf59afc1369e1bb3", size = 351059, upload-time = "2024-11-07T10:04:18.821Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/23/7c/ed772734847ada457af0fdb9dd7073bd3823915721bf64147a1434da5a0c/POT-0.9.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7eb9b88c73387a9966775a6f6d077d9d071814783701d2656dc05b5032a9662d", size = 344293, upload-time = "2024-11-07T10:04:20.193Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8d/af/a99bc77cf4f79ec04b23d415da005e83aa2a2b91d4216045c87f46d3109f/POT-0.9.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9f44446056f5fc9d132ed8e431732c33cbe754fb1e6d73636f1b6ae811be7df", size = 891139, upload-time = "2024-11-07T10:04:22.344Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/68/e8/efc53871cc5b086565702e123d62b37aa40320023b46b30923bb9055b287/POT-0.9.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7f5d27bc9063e01b03d906bb77e7b3428065fdd72ed64233b249584ead2e2bf", size = 897470, upload-time = "2024-11-07T10:04:23.686Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a1/dd/aab8edf448d68fa6be6454887667e04a7bf2b2a5929f2ec35c49f83ef286/POT-0.9.5-cp311-cp311-win32.whl", hash = "sha256:cd79a8b4d35b706f2124f73ebff3bb1ce3450e01cc8f610eda3b6ce13616b829", size = 343915, upload-time = "2024-11-07T10:04:24.98Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fe/ee/9cd8b16e4e8e7254951b83fc6f871763e7e1315078b17b7008662833ed63/POT-0.9.5-cp311-cp311-win_amd64.whl", hash = "sha256:6680aadb69df2f75a413fe9c58bd1c5cb744d017a7c8ba8841654fd0dc75433b", size = 348566, upload-time = "2024-11-07T10:04:26.557Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cb/95/deecc996c5e147159f37191b90a6cf4ee2494e40badc79bed743bfb6478b/POT-0.9.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7d57f96b333c9816a2af7817753108739b38155e52648c5967681dbd89d92ed2", size = 410824, upload-time = "2024-11-07T10:04:28.313Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d3/d3/d9ae1ae96ad461a900b4ffb38f0a830201d4c43135e1a3be48a82e77303e/POT-0.9.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:afad647c78f999439f8c5cbcf74b03c5c0afefb08727cd7d68994130fabfc761", size = 351023, upload-time = "2024-11-07T10:04:29.851Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7d/97/ca785fc539388696838f34ab6bde8ee8ad625999221e3746c8d410f8c20f/POT-0.9.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bca891c28592d6e0e8f04b35989de7005f0fb9b3923f00537f1b269c5084aa7b", size = 344150, upload-time = "2024-11-07T10:04:30.974Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bc/bd/fd000d9217a6cb47f25414d1bfce885fcb28fc23876266422a3a2d8fab31/POT-0.9.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:088c930a5fcd1e8e36fb6af710df47ce6e9331b6b5a28eb09c673df4186dcb10", size = 894749, upload-time = "2024-11-07T10:04:32.163Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5b/39/9c3eed29e954ddbac3ebe68123213826c8995e8acf8b54aa79d1956fda6a/POT-0.9.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfb18268fac1e982e21821a03f802802a0d579c4690988b764115dd886dc38f5", size = 901694, upload-time = "2024-11-07T10:04:33.919Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/83/8d/bf8af71e2f36da7598da946a81fbaebb362abaebf6eeba81ebc8efbc860a/POT-0.9.5-cp312-cp312-win32.whl", hash = "sha256:931fa46ff8e01d47309207243988c783a2d8364452bc080b130c5d319349ad3f", size = 343682, upload-time = "2024-11-07T10:04:35.19Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6e/95/14902c778117ad9ac7af62dd1d951942440c57df991d7f937f416ee6320f/POT-0.9.5-cp312-cp312-win_amd64.whl", hash = "sha256:be786612b391c2e4d3b5db4e7d51cdb2360284e3a6949990051c2eb102f60d3c", size = 347949, upload-time = "2024-11-07T10:04:36.497Z" }, +] + +[[package]] +name = "primp" +version = "0.15.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/56/0b/a87556189da4de1fc6360ca1aa05e8335509633f836cdd06dd17f0743300/primp-0.15.0.tar.gz", hash = "sha256:1af8ea4b15f57571ff7fc5e282a82c5eb69bc695e19b8ddeeda324397965b30a", size = 113022, upload-time = "2025-04-17T11:41:05.315Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f5/5a/146ac964b99ea7657ad67eb66f770be6577dfe9200cb28f9a95baffd6c3f/primp-0.15.0-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:1b281f4ca41a0c6612d4c6e68b96e28acfe786d226a427cd944baa8d7acd644f", size = 3178914, upload-time = "2025-04-17T11:40:59.558Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bc/8a/cc2321e32db3ce64d6e32950d5bcbea01861db97bfb20b5394affc45b387/primp-0.15.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:489cbab55cd793ceb8f90bb7423c6ea64ebb53208ffcf7a044138e3c66d77299", size = 2955079, upload-time = "2025-04-17T11:40:57.398Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c3/7b/cbd5d999a07ff2a21465975d4eb477ae6f69765e8fe8c9087dab250180d8/primp-0.15.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c18b45c23f94016215f62d2334552224236217aaeb716871ce0e4dcfa08eb161", size = 3281018, upload-time = "2025-04-17T11:40:55.308Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1b/6e/a6221c612e61303aec2bcac3f0a02e8b67aee8c0db7bdc174aeb8010f975/primp-0.15.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e985a9cba2e3f96a323722e5440aa9eccaac3178e74b884778e926b5249df080", size = 3255229, upload-time = "2025-04-17T11:40:47.811Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3b/54/bfeef5aca613dc660a69d0760a26c6b8747d8fdb5a7f20cb2cee53c9862f/primp-0.15.0-cp38-abi3-manylinux_2_34_armv7l.whl", hash = "sha256:6b84a6ffa083e34668ff0037221d399c24d939b5629cd38223af860de9e17a83", size = 3014522, upload-time = "2025-04-17T11:40:50.191Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ac/96/84078e09f16a1dad208f2fe0f8a81be2cf36e024675b0f9eec0c2f6e2182/primp-0.15.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:592f6079646bdf5abbbfc3b0a28dac8de943f8907a250ce09398cda5eaebd260", size = 3418567, upload-time = "2025-04-17T11:41:01.595Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6c/80/8a7a9587d3eb85be3d0b64319f2f690c90eb7953e3f73a9ddd9e46c8dc42/primp-0.15.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5a728e5a05f37db6189eb413d22c78bd143fa59dd6a8a26dacd43332b3971fe8", size = 3606279, upload-time = "2025-04-17T11:41:03.61Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0c/dd/f0183ed0145e58cf9d286c1b2c14f63ccee987a4ff79ac85acc31b5d86bd/primp-0.15.0-cp38-abi3-win_amd64.whl", hash = "sha256:aeb6bd20b06dfc92cfe4436939c18de88a58c640752cf7f30d9e4ae893cdec32", size = 3149967, upload-time = "2025-04-17T11:41:07.067Z" }, +] + +[[package]] +name = "proces" +version = "0.1.7" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2c/3d/4159b57736ced0fd22553226df20a985ef7655519c80ffcb8a9fb49ebeee/proces-0.1.7.tar.gz", hash = "sha256:70a05d9e973dd685f7a9092c58be695a8181a411d63796c213232fd3fdc43775" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6f/88/06cc0c7d890ed8d7e16ef0e56880dea516a21643fb1f3a69a50f4cc6f716/proces-0.1.7-py3-none-any.whl", hash = "sha256:308325bbc96877263f06e57e5e9c760c4b42cc722887ad60be6b18fc37d68762" }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.51" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bb/6e/9d084c929dfe9e3bfe0c6a47e31f78a25c54627d64a66e884a8bf5474f1c/prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed", size = 428940, upload-time = "2025-04-15T09:18:47.731Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ce/4f/5249960887b1fbe561d9ff265496d170b55a735b76724f10ef19f9e40716/prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07", size = 387810, upload-time = "2025-04-15T09:18:44.753Z" }, +] + +[[package]] +name = "propcache" +version = "0.3.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/packages/ab/14/510deed325e262afeb8b360043c5d7c960da7d3ecd6d6f9496c9c56dc7f4/propcache-0.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:22d9962a358aedbb7a2e36187ff273adeaab9743373a272976d2e348d08c7770", size = 73178, upload-time = "2025-06-09T22:53:40.126Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cd/4e/ad52a7925ff01c1325653a730c7ec3175a23f948f08626a534133427dcff/propcache-0.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0d0fda578d1dc3f77b6b5a5dce3b9ad69a8250a891760a548df850a5e8da87f3", size = 43133, upload-time = "2025-06-09T22:53:41.965Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/63/7c/e9399ba5da7780871db4eac178e9c2e204c23dd3e7d32df202092a1ed400/propcache-0.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3def3da3ac3ce41562d85db655d18ebac740cb3fa4367f11a52b3da9d03a5cc3", size = 43039, upload-time = "2025-06-09T22:53:43.268Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/22/e1/58da211eb8fdc6fc854002387d38f415a6ca5f5c67c1315b204a5d3e9d7a/propcache-0.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bec58347a5a6cebf239daba9bda37dffec5b8d2ce004d9fe4edef3d2815137e", size = 201903, upload-time = "2025-06-09T22:53:44.872Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c4/0a/550ea0f52aac455cb90111c8bab995208443e46d925e51e2f6ebdf869525/propcache-0.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55ffda449a507e9fbd4aca1a7d9aa6753b07d6166140e5a18d2ac9bc49eac220", size = 213362, upload-time = "2025-06-09T22:53:46.707Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5a/af/9893b7d878deda9bb69fcf54600b247fba7317761b7db11fede6e0f28bd0/propcache-0.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64a67fb39229a8a8491dd42f864e5e263155e729c2e7ff723d6e25f596b1e8cb", size = 210525, upload-time = "2025-06-09T22:53:48.547Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7c/bb/38fd08b278ca85cde36d848091ad2b45954bc5f15cce494bb300b9285831/propcache-0.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9da1cf97b92b51253d5b68cf5a2b9e0dafca095e36b7f2da335e27dc6172a614", size = 198283, upload-time = "2025-06-09T22:53:50.067Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/78/8c/9fe55bd01d362bafb413dfe508c48753111a1e269737fa143ba85693592c/propcache-0.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5f559e127134b07425134b4065be45b166183fdcb433cb6c24c8e4149056ad50", size = 191872, upload-time = "2025-06-09T22:53:51.438Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/54/14/4701c33852937a22584e08abb531d654c8bcf7948a8f87ad0a4822394147/propcache-0.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:aff2e4e06435d61f11a428360a932138d0ec288b0a31dd9bd78d200bd4a2b339", size = 199452, upload-time = "2025-06-09T22:53:53.229Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/16/44/447f2253d859602095356007657ee535e0093215ea0b3d1d6a41d16e5201/propcache-0.3.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4927842833830942a5d0a56e6f4839bc484785b8e1ce8d287359794818633ba0", size = 191567, upload-time = "2025-06-09T22:53:54.541Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f2/b3/e4756258749bb2d3b46defcff606a2f47410bab82be5824a67e84015b267/propcache-0.3.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6107ddd08b02654a30fb8ad7a132021759d750a82578b94cd55ee2772b6ebea2", size = 193015, upload-time = "2025-06-09T22:53:56.44Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1e/df/e6d3c7574233164b6330b9fd697beeac402afd367280e6dc377bb99b43d9/propcache-0.3.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:70bd8b9cd6b519e12859c99f3fc9a93f375ebd22a50296c3a295028bea73b9e7", size = 204660, upload-time = "2025-06-09T22:53:57.839Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b2/53/e4d31dd5170b4a0e2e6b730f2385a96410633b4833dc25fe5dffd1f73294/propcache-0.3.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2183111651d710d3097338dd1893fcf09c9f54e27ff1a8795495a16a469cc90b", size = 206105, upload-time = "2025-06-09T22:53:59.638Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7f/fe/74d54cf9fbe2a20ff786e5f7afcfde446588f0cf15fb2daacfbc267b866c/propcache-0.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fb075ad271405dcad8e2a7ffc9a750a3bf70e533bd86e89f0603e607b93aa64c", size = 196980, upload-time = "2025-06-09T22:54:01.071Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/22/ec/c469c9d59dada8a7679625e0440b544fe72e99311a4679c279562051f6fc/propcache-0.3.2-cp310-cp310-win32.whl", hash = "sha256:404d70768080d3d3bdb41d0771037da19d8340d50b08e104ca0e7f9ce55fce70", size = 37679, upload-time = "2025-06-09T22:54:03.003Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/38/35/07a471371ac89d418f8d0b699c75ea6dca2041fbda360823de21f6a9ce0a/propcache-0.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:7435d766f978b4ede777002e6b3b6641dd229cd1da8d3d3106a45770365f9ad9", size = 41459, upload-time = "2025-06-09T22:54:04.134Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/80/8d/e8b436717ab9c2cfc23b116d2c297305aa4cd8339172a456d61ebf5669b8/propcache-0.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0b8d2f607bd8f80ddc04088bc2a037fdd17884a6fcadc47a96e334d72f3717be", size = 74207, upload-time = "2025-06-09T22:54:05.399Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d6/29/1e34000e9766d112171764b9fa3226fa0153ab565d0c242c70e9945318a7/propcache-0.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06766d8f34733416e2e34f46fea488ad5d60726bb9481d3cddf89a6fa2d9603f", size = 43648, upload-time = "2025-06-09T22:54:08.023Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/46/92/1ad5af0df781e76988897da39b5f086c2bf0f028b7f9bd1f409bb05b6874/propcache-0.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2dc1f4a1df4fecf4e6f68013575ff4af84ef6f478fe5344317a65d38a8e6dc9", size = 43496, upload-time = "2025-06-09T22:54:09.228Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b3/ce/e96392460f9fb68461fabab3e095cb00c8ddf901205be4eae5ce246e5b7e/propcache-0.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be29c4f4810c5789cf10ddf6af80b041c724e629fa51e308a7a0fb19ed1ef7bf", size = 217288, upload-time = "2025-06-09T22:54:10.466Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c5/2a/866726ea345299f7ceefc861a5e782b045545ae6940851930a6adaf1fca6/propcache-0.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59d61f6970ecbd8ff2e9360304d5c8876a6abd4530cb752c06586849ac8a9dc9", size = 227456, upload-time = "2025-06-09T22:54:11.828Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/de/03/07d992ccb6d930398689187e1b3c718339a1c06b8b145a8d9650e4726166/propcache-0.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62180e0b8dbb6b004baec00a7983e4cc52f5ada9cd11f48c3528d8cfa7b96a66", size = 225429, upload-time = "2025-06-09T22:54:13.823Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5d/e6/116ba39448753b1330f48ab8ba927dcd6cf0baea8a0ccbc512dfb49ba670/propcache-0.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c144ca294a204c470f18cf4c9d78887810d04a3e2fbb30eea903575a779159df", size = 213472, upload-time = "2025-06-09T22:54:15.232Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a6/85/f01f5d97e54e428885a5497ccf7f54404cbb4f906688a1690cd51bf597dc/propcache-0.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5c2a784234c28854878d68978265617aa6dc0780e53d44b4d67f3651a17a9a2", size = 204480, upload-time = "2025-06-09T22:54:17.104Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e3/79/7bf5ab9033b8b8194cc3f7cf1aaa0e9c3256320726f64a3e1f113a812dce/propcache-0.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5745bc7acdafa978ca1642891b82c19238eadc78ba2aaa293c6863b304e552d7", size = 214530, upload-time = "2025-06-09T22:54:18.512Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/31/0b/bd3e0c00509b609317df4a18e6b05a450ef2d9a963e1d8bc9c9415d86f30/propcache-0.3.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:c0075bf773d66fa8c9d41f66cc132ecc75e5bb9dd7cce3cfd14adc5ca184cb95", size = 205230, upload-time = "2025-06-09T22:54:19.947Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7a/23/fae0ff9b54b0de4e819bbe559508da132d5683c32d84d0dc2ccce3563ed4/propcache-0.3.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5f57aa0847730daceff0497f417c9de353c575d8da3579162cc74ac294c5369e", size = 206754, upload-time = "2025-06-09T22:54:21.716Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b7/7f/ad6a3c22630aaa5f618b4dc3c3598974a72abb4c18e45a50b3cdd091eb2f/propcache-0.3.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:eef914c014bf72d18efb55619447e0aecd5fb7c2e3fa7441e2e5d6099bddff7e", size = 218430, upload-time = "2025-06-09T22:54:23.17Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5b/2c/ba4f1c0e8a4b4c75910742f0d333759d441f65a1c7f34683b4a74c0ee015/propcache-0.3.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2a4092e8549031e82facf3decdbc0883755d5bbcc62d3aea9d9e185549936dcf", size = 223884, upload-time = "2025-06-09T22:54:25.539Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/88/e4/ebe30fc399e98572019eee82ad0caf512401661985cbd3da5e3140ffa1b0/propcache-0.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:85871b050f174bc0bfb437efbdb68aaf860611953ed12418e4361bc9c392749e", size = 211480, upload-time = "2025-06-09T22:54:26.892Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/96/0a/7d5260b914e01d1d0906f7f38af101f8d8ed0dc47426219eeaf05e8ea7c2/propcache-0.3.2-cp311-cp311-win32.whl", hash = "sha256:36c8d9b673ec57900c3554264e630d45980fd302458e4ac801802a7fd2ef7897", size = 37757, upload-time = "2025-06-09T22:54:28.241Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e1/2d/89fe4489a884bc0da0c3278c552bd4ffe06a1ace559db5ef02ef24ab446b/propcache-0.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53af8cb6a781b02d2ea079b5b853ba9430fcbe18a8e3ce647d5982a3ff69f39", size = 41500, upload-time = "2025-06-09T22:54:29.4Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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 = "5.27.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/71/a5/d61e4263e62e6db1990c120d682870e5c50a30fb6b26119a214c7a014847/protobuf-5.27.2.tar.gz", hash = "sha256:f3ecdef226b9af856075f28227ff2c90ce3a594d092c39bee5513573f25e2714", size = 401640, upload-time = "2024-06-25T20:54:53.874Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e9/9d/318d07d4edd1dc1a29ae67f7bb42b6e8a570f817ebe8608bf3c9c518d4e8/protobuf-5.27.2-cp310-abi3-win32.whl", hash = "sha256:354d84fac2b0d76062e9b3221f4abbbacdfd2a4d8af36bab0474f3a0bb30ab38", size = 405829, upload-time = "2024-06-25T20:54:22.034Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b1/04/73b8fd7f34f3a2b2b64aa31a173b8aebbdb0c55523df4c027846bb44bc1e/protobuf-5.27.2-cp310-abi3-win_amd64.whl", hash = "sha256:0e341109c609749d501986b835f667c6e1e24531096cff9d34ae411595e26505", size = 426919, upload-time = "2024-06-25T20:54:28.399Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/75/44/6ae304790fad936bb4cf09907a05d669b7600458a02b6c960fdaaeeab06e/protobuf-5.27.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a109916aaac42bff84702fb5187f3edadbc7c97fc2c99c5ff81dd15dcce0d1e5", size = 412246, upload-time = "2024-06-25T20:54:30.159Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cd/c7/a534268f9c3780be1ba50f5ed96243fa9cf6224a445de662c34e91ce0e61/protobuf-5.27.2-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:176c12b1f1c880bf7a76d9f7c75822b6a2bc3db2d28baa4d300e8ce4cde7409b", size = 307143, upload-time = "2024-06-25T20:54:36.048Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/27/e4/8dc4546be46873f8950cb44cdfe19b79d66d26e53c4ee5e3440406257fcd/protobuf-5.27.2-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:b848dbe1d57ed7c191dfc4ea64b8b004a3f9ece4bf4d0d80a367b76df20bf36e", size = 309259, upload-time = "2024-06-25T20:54:38.074Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3a/fa/4c3ac5527ed2e5f3577167ecd5f8180ffcdc8bdd59c9f143409c19706456/protobuf-5.27.2-py3-none-any.whl", hash = "sha256:54330f07e4949d09614707c48b06d1a22f8ffb5763c159efd5c0928326a91470", size = 164772, upload-time = "2024-06-25T20:54:52.196Z" }, +] + +[[package]] +name = "psutil" +version = "7.0.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", size = 497003, upload-time = "2025-02-13T21:54:07.946Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ed/e6/2d26234410f8b8abdbf891c9da62bee396583f713fb9f3325a4760875d22/psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25", size = 238051, upload-time = "2025-02-13T21:54:12.36Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/04/8b/30f930733afe425e3cbfc0e1468a30a18942350c1a8816acfade80c005c4/psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da", size = 239535, upload-time = "2025-02-13T21:54:16.07Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2a/ed/d362e84620dd22876b55389248e522338ed1bf134a5edd3b8231d7207f6d/psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91", size = 275004, upload-time = "2025-02-13T21:54:18.662Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34", size = 277986, upload-time = "2025-02-13T21:54:21.811Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/eb/a2/709e0fe2f093556c17fbafda93ac032257242cabcc7ff3369e2cb76a97aa/psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993", size = 279544, upload-time = "2025-02-13T21:54:24.68Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053, upload-time = "2025-02-13T21:54:34.31Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885, upload-time = "2025-02-13T21:54:37.486Z" }, +] + +[[package]] +name = "psycopg2-binary" +version = "2.9.9" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fc/07/e720e53bfab016ebcc34241695ccc06a9e3d91ba19b40ca81317afbdc440/psycopg2-binary-2.9.9.tar.gz", hash = "sha256:7f01846810177d829c7692f1f5ada8096762d9172af1b1a28d4ab5b77c923c1c", size = 384973, upload-time = "2023-10-03T12:48:55.128Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0a/7c/6aaf8c3cb05d86d2c3f407b95bac0c71a43f2718e38c1091972aacb5e1b2/psycopg2_binary-2.9.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c2470da5418b76232f02a2fcd2229537bb2d5a7096674ce61859c3229f2eb202", size = 2822503, upload-time = "2023-10-03T12:45:56.956Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/72/3d/acab427845198794aafd963dd073ee35810e2c52606e8a28c12db39821fb/psycopg2_binary-2.9.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c6af2a6d4b7ee9615cbb162b0738f6e1fd1f5c3eda7e5da17861eacf4c717ea7", size = 2552645, upload-time = "2023-10-03T12:46:00.447Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ed/be/6c787962d706e55a528ef1693dd7251de657ae60e4d9d767ed61e8e2975c/psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75723c3c0fbbf34350b46a3199eb50638ab22a0228f93fb472ef4d9becc2382b", size = 2850980, upload-time = "2023-10-03T12:46:02.666Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/83/50/a054076c6358753661cd1da59f4dabc03e83d51690371f3fd1edb9e2cf72/psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83791a65b51ad6ee6cf0845634859d69a038ea9b03d7b26e703f94c7e93dbcf9", size = 3080543, upload-time = "2023-10-03T12:46:06.768Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9c/02/826dc5cdfc9515423ec912ba00cc2e4eb09f69e0339b177c9c742f2a09a2/psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ef4854e82c09e84cc63084a9e4ccd6d9b154f1dbdd283efb92ecd0b5e2b8c84", size = 3264316, upload-time = "2023-10-03T12:46:08.966Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bc/0d/486e3fa27f39a00168abfcf14a3d8444f437f4b755cc34316da1124f293d/psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed1184ab8f113e8d660ce49a56390ca181f2981066acc27cf637d5c1e10ce46e", size = 3019508, upload-time = "2023-10-03T12:46:11.576Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/41/af/bce37630c525d2b9cf93f930110fc98616d6aca308d59b833b83b3a38176/psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d2997c458c690ec2bc6b0b7ecbafd02b029b7b4283078d3b32a852a7ce3ddd98", size = 2355821, upload-time = "2023-10-03T12:46:14.864Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3b/76/e46dae1b2273814ef80231f86d59cadf94ec36fd757045ed713c5b75cde7/psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b58b4710c7f4161b5e9dcbe73bb7c62d65670a87df7bcce9e1faaad43e715245", size = 2534855, upload-time = "2023-10-03T12:46:17.217Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0e/6d/e97245eabff29d7c2de5fc1fc17cf7ef427beee93d20a5ae114c6e6718bd/psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0c009475ee389757e6e34611d75f6e4f05f0cf5ebb76c6037508318e1a1e0d7e", size = 2486614, upload-time = "2023-10-03T12:46:19.877Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/70/a7/2cd2c9d5e23b556c11e3b7da41895808d9b056f8f34f50de4375a35b4951/psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8dbf6d1bc73f1d04ec1734bae3b4fb0ee3cb2a493d35ede9badbeb901fb40f6f", size = 2454928, upload-time = "2023-10-03T12:46:22.908Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/63/41/815d19767e2adb1a585213b801c954f46102f305c352c4a4f96287342d44/psycopg2_binary-2.9.9-cp310-cp310-win32.whl", hash = "sha256:3f78fd71c4f43a13d342be74ebbc0666fe1f555b8837eb113cb7416856c79682", size = 1025249, upload-time = "2023-10-03T12:46:24.819Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5e/4c/9233e0e206634a5387f3ab40f334a5325fb8bef2ca4e12ee7dbdeaf96afc/psycopg2_binary-2.9.9-cp310-cp310-win_amd64.whl", hash = "sha256:876801744b0dee379e4e3c38b76fc89f88834bb15bf92ee07d94acd06ec890a0", size = 1163645, upload-time = "2023-10-03T12:46:27.677Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a5/ac/702d300f3df169b9d0cbef0340d9f34a78bc18dc2dbafbcb39ff0f165cf8/psycopg2_binary-2.9.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ee825e70b1a209475622f7f7b776785bd68f34af6e7a46e2e42f27b659b5bc26", size = 2822581, upload-time = "2023-10-03T12:46:30.64Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7a/1f/a6cf0cdf944253f7c45d90fbc876cc8bed5cc9942349306245715c0d88d6/psycopg2_binary-2.9.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1ea665f8ce695bcc37a90ee52de7a7980be5161375d42a0b6c6abedbf0d81f0f", size = 2552633, upload-time = "2023-10-03T12:46:32.808Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/81/0b/3adf561107c865928455891156d1dde5325253f7f4316fe56cd2c3f73570/psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:143072318f793f53819048fdfe30c321890af0c3ec7cb1dfc9cc87aa88241de2", size = 2851075, upload-time = "2023-10-03T12:46:35.138Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f7/98/c2fedcbf0a9607519a010dcf88571138b2251062dbde3610cdba5ba1eee1/psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c332c8d69fb64979ebf76613c66b985414927a40f8defa16cf1bc028b7b0a7b0", size = 3080509, upload-time = "2023-10-03T12:46:37.44Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c2/05/81e8bc7fca95574c9323e487d9ce1b58a4cfcc17f89b8fe843af46361211/psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7fc5a5acafb7d6ccca13bfa8c90f8c51f13d8fb87d95656d3950f0158d3ce53", size = 3264303, upload-time = "2023-10-03T12:46:40.73Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ce/85/62825cabc6aad53104b7b6d12eb2ad74737d268630032d07b74d4444cb72/psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:977646e05232579d2e7b9c59e21dbe5261f403a88417f6a6512e70d3f8a046be", size = 3019515, upload-time = "2023-10-03T12:46:43.038Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e9/b0/9ca2b8e01a0912c9a14234fd5df7a241a1e44778c5797bf4b8eaa8dc3d3a/psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b6356793b84728d9d50ead16ab43c187673831e9d4019013f1402c41b1db9b27", size = 2355892, upload-time = "2023-10-03T12:46:45.632Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/73/17/ba28bb0022db5e2015a82d2df1c4b0d419c37fa07a588b3aff3adc4939f6/psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bc7bb56d04601d443f24094e9e31ae6deec9ccb23581f75343feebaf30423359", size = 2534903, upload-time = "2023-10-03T12:46:47.934Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3b/92/b463556409cdc12791cd8b1dae0072bf8efe817ef68b7ea3d9cf7d0e5656/psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:77853062a2c45be16fd6b8d6de2a99278ee1d985a7bd8b103e97e41c034006d2", size = 2486597, upload-time = "2023-10-03T12:46:50.598Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/92/57/96576e07132d7f7a1ac1df939575e6fdd8951aea337ee152b586bb51a971/psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:78151aa3ec21dccd5cdef6c74c3e73386dcdfaf19bced944169697d7ac7482fc", size = 2454908, upload-time = "2023-10-03T12:46:52.903Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7c/ae/cedd56e1f4a2b0e37213283caf3733a875c4c76f3372241e19c0d2a87355/psycopg2_binary-2.9.9-cp311-cp311-win32.whl", hash = "sha256:dc4926288b2a3e9fd7b50dc6a1909a13bbdadfc67d93f3374d984e56f885579d", size = 1024240, upload-time = "2023-10-03T12:46:55.009Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/25/1f/7ae31759142999a8d06b3e250c1346c4abcdcada8fa884376775dc1de686/psycopg2_binary-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:b76bedd166805480ab069612119ea636f5ab8f8771e640ae103e05a4aae3e417", size = 1163655, upload-time = "2023-10-03T12:46:57.038Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a7/d0/5f2db14e7b53552276ab613399a83f83f85b173a862d3f20580bc7231139/psycopg2_binary-2.9.9-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8532fd6e6e2dc57bcb3bc90b079c60de896d2128c5d9d6f24a63875a95a088cf", size = 2823784, upload-time = "2023-10-03T12:47:00.404Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/18/ca/da384fd47233e300e3e485c90e7aab5d7def896d1281239f75901faf87d4/psycopg2_binary-2.9.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0605eaed3eb239e87df0d5e3c6489daae3f7388d455d0c0b4df899519c6a38d", size = 2553308, upload-time = "2023-11-01T10:40:33.984Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/50/66/fa53d2d3d92f6e1ef469d92afc6a4fe3f6e8a9a04b687aa28fb1f1d954ee/psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f8544b092a29a6ddd72f3556a9fcf249ec412e10ad28be6a0c0d948924f2212", size = 2851283, upload-time = "2023-10-03T12:47:02.736Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/04/37/2429360ac5547378202db14eec0dde76edbe1f6627df5a43c7e164922859/psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d423c8d8a3c82d08fe8af900ad5b613ce3632a1249fd6a223941d0735fce493", size = 3081839, upload-time = "2023-10-03T12:47:05.027Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/62/2a/c0530b59d7e0d09824bc2102ecdcec0456b8ca4d47c0caa82e86fce3ed4c/psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e5afae772c00980525f6d6ecf7cbca55676296b580c0e6abb407f15f3706996", size = 3264488, upload-time = "2023-10-03T12:47:08.962Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/19/57/9f172b900795ea37246c78b5f52e00f4779984370855b3e161600156906d/psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e6f98446430fdf41bd36d4faa6cb409f5140c1c2cf58ce0bbdaf16af7d3f119", size = 3020700, upload-time = "2023-10-03T12:47:12.23Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/94/68/1176fc14ea76861b7b8360be5176e87fb20d5091b137c76570eb4e237324/psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c77e3d1862452565875eb31bdb45ac62502feabbd53429fdc39a1cc341d681ba", size = 2355968, upload-time = "2023-10-03T12:47:14.817Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/70/bb/aec2646a705a09079d008ce88073401cd61fc9b04f92af3eb282caa3a2ec/psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:cb16c65dcb648d0a43a2521f2f0a2300f40639f6f8c1ecbc662141e4e3e1ee07", size = 2536101, upload-time = "2023-10-03T12:47:17.454Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/14/33/12818c157e333cb9d9e6753d1b2463b6f60dbc1fade115f8e4dc5c52cac4/psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:911dda9c487075abd54e644ccdf5e5c16773470a6a5d3826fda76699410066fb", size = 2487064, upload-time = "2023-10-03T12:47:20.717Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/56/a2/7851c68fe8768f3c9c246198b6356ee3e4a8a7f6820cc798443faada3400/psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:57fede879f08d23c85140a360c6a77709113efd1c993923c59fde17aa27599fe", size = 2456257, upload-time = "2023-10-03T12:47:23.004Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6f/ee/3ba07c6dc7c3294e717e94720da1597aedc82a10b1b180203ce183d4631a/psycopg2_binary-2.9.9-cp312-cp312-win32.whl", hash = "sha256:64cf30263844fa208851ebb13b0732ce674d8ec6a0c86a4e160495d299ba3c93", size = 1024709, upload-time = "2023-10-28T09:37:24.991Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7b/08/9c66c269b0d417a0af9fb969535f0371b8c538633535a7a6a5ca3f9231e2/psycopg2_binary-2.9.9-cp312-cp312-win_amd64.whl", hash = "sha256:81ff62668af011f9a48787564ab7eded4e9fb17a4a6a74af5ffa6a457400d2ab", size = 1163864, upload-time = "2023-10-28T09:37:28.155Z" }, +] + +[[package]] +name = "py" +version = "1.11.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/98/ff/fec109ceb715d2a6b4c4a85a61af3b40c723a961e8828319fbcb15b868dc/py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719", size = 207796, upload-time = "2021-11-04T17:17:01.377Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378", size = 98708, upload-time = "2021-11-04T17:17:00.152Z" }, +] + +[[package]] +name = "py-mini-racer" +version = "0.6.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/50/97/a578b918b2e5923dd754cb60bb8b8aeffc85255ffb92566e3c65b148ff72/py_mini_racer-0.6.0.tar.gz", hash = "sha256:f71e36b643d947ba698c57cd9bd2232c83ca997b0802fc2f7f79582377040c11", size = 5994836, upload-time = "2021-04-22T07:58:35.993Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/29/a9/8ce0ca222ef04d602924a1e099be93f5435ca6f3294182a30574d4159ca2/py_mini_racer-0.6.0-py2.py3-none-manylinux1_x86_64.whl", hash = "sha256:42896c24968481dd953eeeb11de331f6870917811961c9b26ba09071e07180e2", size = 5416149, upload-time = "2021-04-22T07:58:25.615Z" }, +] + +[[package]] +name = "pyarrow" +version = "17.0.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/27/4e/ea6d43f324169f8aec0e57569443a38bab4b398d09769ca64f7b4d467de3/pyarrow-17.0.0.tar.gz", hash = "sha256:4beca9521ed2c0921c1023e68d097d0299b62c362639ea315572a58f3f50fd28", size = 1112479, upload-time = "2024-07-17T10:41:25.092Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/39/5d/78d4b040bc5ff2fc6c3d03e80fca396b742f6c125b8af06bcf7427f931bc/pyarrow-17.0.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:a5c8b238d47e48812ee577ee20c9a2779e6a5904f1708ae240f53ecbee7c9f07", size = 28994846, upload-time = "2024-07-16T10:29:13.082Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3b/73/8ed168db7642e91180330e4ea9f3ff8bab404678f00d32d7df0871a4933b/pyarrow-17.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:db023dc4c6cae1015de9e198d41250688383c3f9af8f565370ab2b4cb5f62655", size = 27165908, upload-time = "2024-07-16T10:29:20.362Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/81/36/e78c24be99242063f6d0590ef68c857ea07bdea470242c361e9a15bd57a4/pyarrow-17.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da1e060b3876faa11cee287839f9cc7cdc00649f475714b8680a05fd9071d545", size = 39264209, upload-time = "2024-07-16T10:29:27.621Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/18/4c/3db637d7578f683b0a8fb8999b436bdbedd6e3517bd4f90c70853cf3ad20/pyarrow-17.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75c06d4624c0ad6674364bb46ef38c3132768139ddec1c56582dbac54f2663e2", size = 39862883, upload-time = "2024-07-16T10:29:34.34Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/81/3c/0580626896c842614a523e66b351181ed5bb14e5dfc263cd68cea2c46d90/pyarrow-17.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:fa3c246cc58cb5a4a5cb407a18f193354ea47dd0648194e6265bd24177982fe8", size = 38723009, upload-time = "2024-07-16T10:29:41.123Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ee/fb/c1b47f0ada36d856a352da261a44d7344d8f22e2f7db3945f8c3b81be5dd/pyarrow-17.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:f7ae2de664e0b158d1607699a16a488de3d008ba99b3a7aa5de1cbc13574d047", size = 39855626, upload-time = "2024-07-16T10:29:49.004Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/19/09/b0a02908180a25d57312ab5919069c39fddf30602568980419f4b02393f6/pyarrow-17.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:5984f416552eea15fd9cee03da53542bf4cddaef5afecefb9aa8d1010c335087", size = 25147242, upload-time = "2024-07-16T10:29:56.195Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f9/46/ce89f87c2936f5bb9d879473b9663ce7a4b1f4359acc2f0eb39865eaa1af/pyarrow-17.0.0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:1c8856e2ef09eb87ecf937104aacfa0708f22dfeb039c363ec99735190ffb977", size = 29028748, upload-time = "2024-07-16T10:30:02.609Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8d/8e/ce2e9b2146de422f6638333c01903140e9ada244a2a477918a368306c64c/pyarrow-17.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2e19f569567efcbbd42084e87f948778eb371d308e137a0f97afe19bb860ccb3", size = 27190965, upload-time = "2024-07-16T10:30:10.718Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3b/c8/5675719570eb1acd809481c6d64e2136ffb340bc387f4ca62dce79516cea/pyarrow-17.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b244dc8e08a23b3e352899a006a26ae7b4d0da7bb636872fa8f5884e70acf15", size = 39269081, upload-time = "2024-07-16T10:30:18.878Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5e/78/3931194f16ab681ebb87ad252e7b8d2c8b23dad49706cadc865dff4a1dd3/pyarrow-17.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b72e87fe3e1db343995562f7fff8aee354b55ee83d13afba65400c178ab2597", size = 39864921, upload-time = "2024-07-16T10:30:27.008Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d8/81/69b6606093363f55a2a574c018901c40952d4e902e670656d18213c71ad7/pyarrow-17.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:dc5c31c37409dfbc5d014047817cb4ccd8c1ea25d19576acf1a001fe07f5b420", size = 38740798, upload-time = "2024-07-16T10:30:34.814Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4c/21/9ca93b84b92ef927814cb7ba37f0774a484c849d58f0b692b16af8eebcfb/pyarrow-17.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:e3343cb1e88bc2ea605986d4b94948716edc7a8d14afd4e2c097232f729758b4", size = 39871877, upload-time = "2024-07-16T10:30:42.672Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/30/d1/63a7c248432c71c7d3ee803e706590a0b81ce1a8d2b2ae49677774b813bb/pyarrow-17.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:a27532c38f3de9eb3e90ecab63dfda948a8ca859a66e3a47f5f42d1e403c4d03", size = 25151089, upload-time = "2024-07-16T10:30:49.279Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d4/62/ce6ac1275a432b4a27c55fe96c58147f111d8ba1ad800a112d31859fae2f/pyarrow-17.0.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:9b8a823cea605221e61f34859dcc03207e52e409ccf6354634143e23af7c8d22", size = 29019418, upload-time = "2024-07-16T10:30:55.573Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8e/0a/dbd0c134e7a0c30bea439675cc120012337202e5fac7163ba839aa3691d2/pyarrow-17.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f1e70de6cb5790a50b01d2b686d54aaf73da01266850b05e3af2a1bc89e16053", size = 27152197, upload-time = "2024-07-16T10:31:02.036Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cb/05/3f4a16498349db79090767620d6dc23c1ec0c658a668d61d76b87706c65d/pyarrow-17.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0071ce35788c6f9077ff9ecba4858108eebe2ea5a3f7cf2cf55ebc1dbc6ee24a", size = 39263026, upload-time = "2024-07-16T10:31:10.351Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c2/0c/ea2107236740be8fa0e0d4a293a095c9f43546a2465bb7df34eee9126b09/pyarrow-17.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:757074882f844411fcca735e39aae74248a1531367a7c80799b4266390ae51cc", size = 39880798, upload-time = "2024-07-16T10:31:17.66Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f6/b0/b9164a8bc495083c10c281cc65064553ec87b7537d6f742a89d5953a2a3e/pyarrow-17.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:9ba11c4f16976e89146781a83833df7f82077cdab7dc6232c897789343f7891a", size = 38715172, upload-time = "2024-07-16T10:31:25.965Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f1/c4/9625418a1413005e486c006e56675334929fad864347c5ae7c1b2e7fe639/pyarrow-17.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b0c6ac301093b42d34410b187bba560b17c0330f64907bfa4f7f7f2444b0cf9b", size = 39874508, upload-time = "2024-07-16T10:31:33.721Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ae/49/baafe2a964f663413be3bd1cf5c45ed98c5e42e804e2328e18f4570027c1/pyarrow-17.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:392bc9feabc647338e6c89267635e111d71edad5fcffba204425a7c8d13610d7", size = 25099235, upload-time = "2024-07-16T10:31:40.893Z" }, +] + +[[package]] +name = "pyasn1" +version = "0.6.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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 = "pyclipper" +version = "1.3.0.post5" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1b/3d/e5b5ff36b24f3fc9b962a68ce4f6932ab698b8ba860261f402be37b85d17/pyclipper-1.3.0.post5.tar.gz", hash = "sha256:c0239f928e0bf78a3efc2f2f615a10bfcdb9f33012d46d64c8d1225b4bde7096", size = 164719, upload-time = "2023-09-07T13:02:33.824Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/00/38/abcb5e560563b54c552b23a128d824230658cf6c28ae2003f89c23691084/pyclipper-1.3.0.post5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3c45f99b8180dd4df4c86642657ca92b7d5289a5e3724521822e0f9461961fe2", size = 277364, upload-time = "2023-09-07T13:01:22.438Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a6/27/04f7977ac7ad12091d32333dea5dcdf704ed189bfb3f54f460b015b9c6f9/pyclipper-1.3.0.post5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:567ffd419a0bdc3727fa4562cfa1f18484691817a2bc0bc675750aa28ed98bd4", size = 145678, upload-time = "2023-09-07T13:01:23.976Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1c/81/4aa8403e587a4c60e00b479c11254a6e3200f3b985dcf4caecf0d8c21261/pyclipper-1.3.0.post5-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:59c8c75661a6d87e98b1655851578a2917d3c8859912c9a4f1956b9830940fd9", size = 908260, upload-time = "2023-09-07T13:01:25.587Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ba/c0/adb676ffe4f11d7d2cd2adf847865e419d24380f8de93c5a4cbddf83b23e/pyclipper-1.3.0.post5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a496efa146d2d88b59350021739e4685e439dc569b6654e9e6d5e42e9a0b1666", size = 926738, upload-time = "2023-09-07T13:01:27.732Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2d/85/d6c927908ec53303f8e417aefa7b345061389151bdee434fc6fc16e422a7/pyclipper-1.3.0.post5-cp310-cp310-win32.whl", hash = "sha256:02a98d09af9b60bcf8e9480d153c0839e20b92689f5602f87242a4933842fecd", size = 99179, upload-time = "2023-09-07T13:01:29.823Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/60/61/354f484ab7969a601327646bbaeb1b799508b4e81946ea4d52bbf9d779c6/pyclipper-1.3.0.post5-cp310-cp310-win_amd64.whl", hash = "sha256:847f1e2fc3994bb498fe675f55c98129b95dc26a5c92304ba4cf0ab40721ea3d", size = 108216, upload-time = "2023-09-07T13:01:31.04Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/21/47/9c6a9d2523735d7a5ec2991e6a05370b96e19db26c5628fedd1143dc6e4f/pyclipper-1.3.0.post5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b7a983ae019932bfa0a1971a2dc8c856704add5f3d567bed8fac02dbc0e7f0bf", size = 279155, upload-time = "2023-09-07T13:01:32.539Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/05/f0/3e4ca96c1adb32f254ba0ba3a5a4cf4bd6794c285177f10357f3574d11d5/pyclipper-1.3.0.post5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d8760075c395b924f894aa16ee06e8c040c6f9b63e0903e49de3cc8d82d9e637", size = 146859, upload-time = "2023-09-07T13:01:33.926Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ed/79/64cfb4bf0338c3dcd4ef4b819f0fb48a65bc9a9b5b2644cf21a665d08ae8/pyclipper-1.3.0.post5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4ea61ca5899d3346c614951342c506f119601ed0a1f4889a9cc236558afec6b", size = 952640, upload-time = "2023-09-07T13:01:35.936Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/43/64/9c8e0c7d96d32c63e38f92da92e4e38685e30773644d9dcb73d2325beb47/pyclipper-1.3.0.post5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46499b361ae067662b22578401d83d57716f3cc0071d592feb07d504b439fea7", size = 971470, upload-time = "2023-09-07T13:01:37.513Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/da/ff/29f1fa1473d6c8abaa2f41f60dfeb23e92819ee67f7d9387715e53e2a414/pyclipper-1.3.0.post5-cp311-cp311-win32.whl", hash = "sha256:d5c77e39ab05a6cf277c819639968b21e6959e996ea1a074afc24236541708ff", size = 99360, upload-time = "2023-09-07T13:01:39.159Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f3/ec/56da9f2d5d846f144530d5313a05078afb7cfc26ec179be5af35f057d064/pyclipper-1.3.0.post5-cp311-cp311-win_amd64.whl", hash = "sha256:0f78a1c18ff4f9276f78d9353d6ed4309c3886a9d0172437e48328aef499165e", size = 108311, upload-time = "2023-09-07T13:01:40.526Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/87/f0/2a9dbd3359bd834b24691ba65b1011c1a7a7cafd92691165506ece1eeb3b/pyclipper-1.3.0.post5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:5237282f906049c307e6c90333c7d56f6b8712bf087ef97b141830c40b09ca0a", size = 278102, upload-time = "2023-09-07T13:01:41.778Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/28/30/1b532eff31728e0233684eada4153773af11a81992bb9791160ed27760af/pyclipper-1.3.0.post5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aca8635573646b65c054399433fb3493637f1445db942de8a52fca9ef493ba3d", size = 145946, upload-time = "2023-09-07T13:01:43.768Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b0/95/e0d4c036c1c936b6f7e6265d15f56b5b3ceb4a4d7dcb491cdd2604882f93/pyclipper-1.3.0.post5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1158a2b13d59bdfab33d1d928f7b72c8c7fb8a76e7d2283839cb45d7c0ff2140", size = 947205, upload-time = "2023-09-07T13:01:45.147Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/22/f3/c5b39f3515d7af0c96b67f6eb13b62d0cd471f348ebafa106d6fcb8d9d33/pyclipper-1.3.0.post5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a041f1a7982b17cf92fd3be349ec41ff1901792149c166bf283f469567b52d6", size = 966618, upload-time = "2023-09-07T13:01:47.346Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0a/67/1fe463403bbd2ea7ca79f328a118b12fff495d0e83c98bdf5afd187ccccc/pyclipper-1.3.0.post5-cp312-cp312-win32.whl", hash = "sha256:bf3a2ccd6e4e078250b0a31a12c519b0be6d1bc160acfceee62407dbd68558f6", size = 98762, upload-time = "2023-09-07T13:01:49.468Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a1/f0/760e614b84dd4d8f03dd5432dda100d699e23074c263b38b6c117adc8395/pyclipper-1.3.0.post5-cp312-cp312-win_amd64.whl", hash = "sha256:2ce6e0a6ab32182c26537965cf521822cd11a28a7ffcef48635a94c6ca8559ef", size = 108190, upload-time = "2023-09-07T13:01:50.699Z" }, +] + +[[package]] +name = "pycparser" +version = "2.22" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736, upload-time = "2024-03-30T13:22:22.564Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552, upload-time = "2024-03-30T13:22:20.476Z" }, +] + +[[package]] +name = "pycryptodome" +version = "3.23.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8e/a6/8452177684d5e906854776276ddd34eca30d1b1e15aa1ee9cefc289a33f5/pycryptodome-3.23.0.tar.gz", hash = "sha256:447700a657182d60338bab09fdb27518f8856aecd80ae4c6bdddb67ff5da44ef", size = 4921276, upload-time = "2025-05-17T17:21:45.242Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/db/6c/a1f71542c969912bb0e106f64f60a56cc1f0fabecf9396f45accbe63fa68/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:187058ab80b3281b1de11c2e6842a357a1f71b42cb1e15bce373f3d238135c27", size = 2495627, upload-time = "2025-05-17T17:20:47.139Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6e/4e/a066527e079fc5002390c8acdd3aca431e6ea0a50ffd7201551175b47323/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:cfb5cd445280c5b0a4e6187a7ce8de5a07b5f3f897f235caa11f1f435f182843", size = 1640362, upload-time = "2025-05-17T17:20:50.392Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/50/52/adaf4c8c100a8c49d2bd058e5b551f73dfd8cb89eb4911e25a0c469b6b4e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67bd81fcbe34f43ad9422ee8fd4843c8e7198dd88dd3d40e6de42ee65fbe1490", size = 2182625, upload-time = "2025-05-17T17:20:52.866Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5f/e9/a09476d436d0ff1402ac3867d933c61805ec2326c6ea557aeeac3825604e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8987bd3307a39bc03df5c8e0e3d8be0c4c3518b7f044b0f4c15d1aa78f52575", size = 2268954, upload-time = "2025-05-17T17:20:55.027Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f9/c5/ffe6474e0c551d54cab931918127c46d70cab8f114e0c2b5a3c071c2f484/pycryptodome-3.23.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa0698f65e5b570426fc31b8162ed4603b0c2841cbb9088e2b01641e3065915b", size = 2308534, upload-time = "2025-05-17T17:20:57.279Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/18/28/e199677fc15ecf43010f2463fde4c1a53015d1fe95fb03bca2890836603a/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:53ecbafc2b55353edcebd64bf5da94a2a2cdf5090a6915bcca6eca6cc452585a", size = 2181853, upload-time = "2025-05-17T17:20:59.322Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ce/ea/4fdb09f2165ce1365c9eaefef36625583371ee514db58dc9b65d3a255c4c/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:156df9667ad9f2ad26255926524e1c136d6664b741547deb0a86a9acf5ea631f", size = 2342465, upload-time = "2025-05-17T17:21:03.83Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/22/82/6edc3fc42fe9284aead511394bac167693fb2b0e0395b28b8bedaa07ef04/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:dea827b4d55ee390dc89b2afe5927d4308a8b538ae91d9c6f7a5090f397af1aa", size = 2267414, upload-time = "2025-05-17T17:21:06.72Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/59/fe/aae679b64363eb78326c7fdc9d06ec3de18bac68be4b612fc1fe8902693c/pycryptodome-3.23.0-cp37-abi3-win32.whl", hash = "sha256:507dbead45474b62b2bbe318eb1c4c8ee641077532067fec9c1aa82c31f84886", size = 1768484, upload-time = "2025-05-17T17:21:08.535Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/54/2f/e97a1b8294db0daaa87012c24a7bb714147c7ade7656973fd6c736b484ff/pycryptodome-3.23.0-cp37-abi3-win_amd64.whl", hash = "sha256:c75b52aacc6c0c260f204cbdd834f76edc9fb0d8e0da9fbf8352ef58202564e2", size = 1799636, upload-time = "2025-05-17T17:21:10.393Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/18/3d/f9441a0d798bf2b1e645adc3265e55706aead1255ccdad3856dbdcffec14/pycryptodome-3.23.0-cp37-abi3-win_arm64.whl", hash = "sha256:11eeeb6917903876f134b56ba11abe95c0b0fd5e3330def218083c7d98bbcb3c", size = 1703675, upload-time = "2025-05-17T17:21:13.146Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d9/12/e33935a0709c07de084d7d58d330ec3f4daf7910a18e77937affdb728452/pycryptodome-3.23.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ddb95b49df036ddd264a0ad246d1be5b672000f12d6961ea2c267083a5e19379", size = 1623886, upload-time = "2025-05-17T17:21:20.614Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/22/0b/aa8f9419f25870889bebf0b26b223c6986652bdf071f000623df11212c90/pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e95564beb8782abfd9e431c974e14563a794a4944c29d6d3b7b5ea042110b4", size = 1672151, upload-time = "2025-05-17T17:21:22.666Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d4/5e/63f5cbde2342b7f70a39e591dbe75d9809d6338ce0b07c10406f1a140cdc/pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14e15c081e912c4b0d75632acd8382dfce45b258667aa3c67caf7a4d4c13f630", size = 1664461, upload-time = "2025-05-17T17:21:25.225Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d6/92/608fbdad566ebe499297a86aae5f2a5263818ceeecd16733006f1600403c/pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7fc76bf273353dc7e5207d172b83f569540fc9a28d63171061c42e361d22353", size = 1702440, upload-time = "2025-05-17T17:21:27.991Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d1/92/2eadd1341abd2989cce2e2740b4423608ee2014acb8110438244ee97d7ff/pycryptodome-3.23.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:45c69ad715ca1a94f778215a11e66b7ff989d792a4d63b68dc586a1da1392ff5", size = 1803005, upload-time = "2025-05-17T17:21:31.37Z" }, +] + +[[package]] +name = "pycryptodomex" +version = "3.20.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/31/a4/b03a16637574312c1b54c55aedeed8a4cb7d101d44058d46a0e5706c63e1/pycryptodomex-3.20.0.tar.gz", hash = "sha256:7a710b79baddd65b806402e14766c721aee8fb83381769c27920f26476276c1e", size = 4794613, upload-time = "2024-01-10T11:32:34.067Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7a/09/668b587ddaf2aa0f94ea45bca73e7c564816fd9329a05e8f7f870425981d/pycryptodomex-3.20.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:59af01efb011b0e8b686ba7758d59cf4a8263f9ad35911bfe3f416cee4f5c08c", size = 2430400, upload-time = "2024-01-10T11:31:44.072Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4c/c4/9b1e8fca01c4b5a0e1c6f52ba19478b2692af4694afe8c89ebbe24348604/pycryptodomex-3.20.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:82ee7696ed8eb9a82c7037f32ba9b7c59e51dda6f105b39f043b6ef293989cb3", size = 1593362, upload-time = "2024-01-10T11:31:47.048Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4e/b9/91af61ec562b87c0932122666603a37cd17f991bc05faf9123b598d1e518/pycryptodomex-3.20.0-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91852d4480a4537d169c29a9d104dda44094c78f1f5b67bca76c29a91042b623", size = 2065201, upload-time = "2024-01-10T11:31:49.86Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/20/7a/3162173af8597f0399b45c6aaa4939ccae908476fdf1b3a3cc30631fc9fb/pycryptodomex-3.20.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca649483d5ed251d06daf25957f802e44e6bb6df2e8f218ae71968ff8f8edc4", size = 2139169, upload-time = "2024-01-10T11:31:53.189Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1b/43/e67f7767a76db1067008127a04617165579e6a65b5c3acb230c7383ca514/pycryptodomex-3.20.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e186342cfcc3aafaad565cbd496060e5a614b441cacc3995ef0091115c1f6c5", size = 2167742, upload-time = "2024-01-10T11:31:56.322Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bb/29/fb592db3f98b1ed330561518ff4706e869045b0cf27632a4310444731aa1/pycryptodomex-3.20.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:25cd61e846aaab76d5791d006497134602a9e451e954833018161befc3b5b9ed", size = 2057793, upload-time = "2024-01-10T11:31:58.39Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8d/ca/7f296284fad77182ad2b2c198a7ece14b04cc9e6e905b1082c015f2254d3/pycryptodomex-3.20.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:9c682436c359b5ada67e882fec34689726a09c461efd75b6ea77b2403d5665b7", size = 2196243, upload-time = "2024-01-10T11:32:01.309Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/48/7d/0f2b09490b98cc6a902ac15dda8760c568b9c18cfe70e0ef7a16de64d53a/pycryptodomex-3.20.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:7a7a8f33a1f1fb762ede6cc9cbab8f2a9ba13b196bfaf7bc6f0b39d2ba315a43", size = 2158708, upload-time = "2024-01-10T11:32:03.55Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b0/1c/375adb14b71ee1c8d8232904e928b3e7af5bbbca7c04e4bec94fe8e90c3d/pycryptodomex-3.20.0-cp35-abi3-win32.whl", hash = "sha256:c39778fd0548d78917b61f03c1fa8bfda6cfcf98c767decf360945fe6f97461e", size = 1726798, upload-time = "2024-01-10T11:32:05.521Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b2/e8/1b92184ab7e5595bf38000587e6f8cf9556ebd1bf0a583619bee2057afbd/pycryptodomex-3.20.0-cp35-abi3-win_amd64.whl", hash = "sha256:2a47bcc478741b71273b917232f521fd5704ab4b25d301669879e7273d3586cc", size = 1762906, upload-time = "2024-01-10T11:32:07.563Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/eb/df/3f1ea084e43b91e6d2b6b3493cc948864c17ea5d93ff1261a03812fbfd1a/pycryptodomex-3.20.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f2e497413560e03421484189a6b65e33fe800d3bd75590e6d78d4dfdb7accf3b", size = 1569076, upload-time = "2024-01-10T11:32:14.793Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c9/f3/83ffbdfa0c8f9154bcd8866895f6cae5a3ec749da8b0840603cf936c4412/pycryptodomex-3.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e48217c7901edd95f9f097feaa0388da215ed14ce2ece803d3f300b4e694abea", size = 1609872, upload-time = "2024-01-10T11:32:17.268Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c9/9d/c113e640aaf02af5631ae2686b742aac5cd0e1402b9d6512b1c7ec5ef05d/pycryptodomex-3.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d00fe8596e1cc46b44bf3907354e9377aa030ec4cd04afbbf6e899fc1e2a7781", size = 1640752, upload-time = "2024-01-10T11:32:20.027Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e4/8a/7c621942787a20d4cb7c32f0c49f183781c6b8753e6ba4f92e57a6d8b1f5/pycryptodomex-3.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:88afd7a3af7ddddd42c2deda43d53d3dfc016c11327d0915f90ca34ebda91499", size = 1744274, upload-time = "2024-01-10T11:32:22.083Z" }, +] + +[[package]] +name = "pydantic" +version = "2.9.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a9/b7/d9e3f12af310e1120c21603644a1cd86f59060e040ec5c3a80b8f05fae30/pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f", size = 769917, upload-time = "2024-09-17T15:59:54.273Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/df/e4/ba44652d562cbf0bf320e0f3810206149c8a4e99cdbf66da82e97ab53a15/pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12", size = 434928, upload-time = "2024-09-17T15:59:51.827Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.23.4" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e2/aa/6b6a9b9f8537b872f552ddd46dd3da230367754b6f707b8e1e963f515ea3/pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863", size = 402156, upload-time = "2024-09-16T16:06:44.786Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5c/8b/d3ae387f66277bd8104096d6ec0a145f4baa2966ebb2cad746c0920c9526/pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b", size = 1867835, upload-time = "2024-09-16T16:03:57.223Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/46/76/f68272e4c3a7df8777798282c5e47d508274917f29992d84e1898f8908c7/pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166", size = 1776689, upload-time = "2024-09-16T16:03:59.266Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cc/69/5f945b4416f42ea3f3bc9d2aaec66c76084a6ff4ff27555bf9415ab43189/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb", size = 1800748, upload-time = "2024-09-16T16:04:01.011Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/50/ab/891a7b0054bcc297fb02d44d05c50e68154e31788f2d9d41d0b72c89fdf7/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916", size = 1806469, upload-time = "2024-09-16T16:04:02.323Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/31/7c/6e3fa122075d78f277a8431c4c608f061881b76c2b7faca01d317ee39b5d/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07", size = 2002246, upload-time = "2024-09-16T16:04:03.688Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ad/6f/22d5692b7ab63fc4acbc74de6ff61d185804a83160adba5e6cc6068e1128/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232", size = 2659404, upload-time = "2024-09-16T16:04:05.299Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/11/ac/1e647dc1121c028b691028fa61a4e7477e6aeb5132628fde41dd34c1671f/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2", size = 2053940, upload-time = "2024-09-16T16:04:06.604Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/91/75/984740c17f12c3ce18b5a2fcc4bdceb785cce7df1511a4ce89bca17c7e2d/pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f", size = 1921437, upload-time = "2024-09-16T16:04:08.071Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a0/74/13c5f606b64d93f0721e7768cd3e8b2102164866c207b8cd6f90bb15d24f/pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3", size = 1966129, upload-time = "2024-09-16T16:04:10.363Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/18/03/9c4aa5919457c7b57a016c1ab513b1a926ed9b2bb7915bf8e506bf65c34b/pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071", size = 2110908, upload-time = "2024-09-16T16:04:12.412Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/92/2c/053d33f029c5dc65e5cf44ff03ceeefb7cce908f8f3cca9265e7f9b540c8/pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119", size = 1735278, upload-time = "2024-09-16T16:04:13.732Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/de/81/7dfe464eca78d76d31dd661b04b5f2036ec72ea8848dd87ab7375e185c23/pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f", size = 1917453, upload-time = "2024-09-16T16:04:15.996Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5d/30/890a583cd3f2be27ecf32b479d5d615710bb926d92da03e3f7838ff3e58b/pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8", size = 1865160, upload-time = "2024-09-16T16:04:18.628Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1d/9a/b634442e1253bc6889c87afe8bb59447f106ee042140bd57680b3b113ec7/pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d", size = 1776777, upload-time = "2024-09-16T16:04:20.038Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/75/9a/7816295124a6b08c24c96f9ce73085032d8bcbaf7e5a781cd41aa910c891/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e", size = 1799244, upload-time = "2024-09-16T16:04:21.799Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a9/8f/89c1405176903e567c5f99ec53387449e62f1121894aa9fc2c4fdc51a59b/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607", size = 1805307, upload-time = "2024-09-16T16:04:23.324Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d5/a5/1a194447d0da1ef492e3470680c66048fef56fc1f1a25cafbea4bc1d1c48/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd", size = 2000663, upload-time = "2024-09-16T16:04:25.203Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/13/a5/1df8541651de4455e7d587cf556201b4f7997191e110bca3b589218745a5/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea", size = 2655941, upload-time = "2024-09-16T16:04:27.211Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/44/31/a3899b5ce02c4316865e390107f145089876dff7e1dfc770a231d836aed8/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e", size = 2052105, upload-time = "2024-09-16T16:04:28.611Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1b/aa/98e190f8745d5ec831f6d5449344c48c0627ac5fed4e5340a44b74878f8e/pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b", size = 1919967, upload-time = "2024-09-16T16:04:30.045Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ae/35/b6e00b6abb2acfee3e8f85558c02a0822e9a8b2f2d812ea8b9079b118ba0/pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0", size = 1964291, upload-time = "2024-09-16T16:04:32.376Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/13/46/7bee6d32b69191cd649bbbd2361af79c472d72cb29bb2024f0b6e350ba06/pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64", size = 2109666, upload-time = "2024-09-16T16:04:33.923Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/39/ef/7b34f1b122a81b68ed0a7d0e564da9ccdc9a2924c8d6c6b5b11fa3a56970/pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f", size = 1732940, upload-time = "2024-09-16T16:04:35.467Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2f/76/37b7e76c645843ff46c1d73e046207311ef298d3f7b2f7d8f6ac60113071/pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3", size = 1916804, upload-time = "2024-09-16T16:04:37.06Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/74/7b/8e315f80666194b354966ec84b7d567da77ad927ed6323db4006cf915f3f/pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231", size = 1856459, upload-time = "2024-09-16T16:04:38.438Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/14/de/866bdce10ed808323d437612aca1ec9971b981e1c52e5e42ad9b8e17a6f6/pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee", size = 1770007, upload-time = "2024-09-16T16:04:40.229Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/dc/69/8edd5c3cd48bb833a3f7ef9b81d7666ccddd3c9a635225214e044b6e8281/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87", size = 1790245, upload-time = "2024-09-16T16:04:41.794Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/80/33/9c24334e3af796ce80d2274940aae38dd4e5676298b4398eff103a79e02d/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8", size = 1801260, upload-time = "2024-09-16T16:04:43.991Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a5/6f/e9567fd90104b79b101ca9d120219644d3314962caa7948dd8b965e9f83e/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327", size = 1996872, upload-time = "2024-09-16T16:04:45.593Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2d/ad/b5f0fe9e6cfee915dd144edbd10b6e9c9c9c9d7a56b69256d124b8ac682e/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2", size = 2661617, upload-time = "2024-09-16T16:04:47.3Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/06/c8/7d4b708f8d05a5cbfda3243aad468052c6e99de7d0937c9146c24d9f12e9/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36", size = 2071831, upload-time = "2024-09-16T16:04:48.893Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/89/4d/3079d00c47f22c9a9a8220db088b309ad6e600a73d7a69473e3a8e5e3ea3/pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126", size = 1917453, upload-time = "2024-09-16T16:04:51.099Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e9/88/9df5b7ce880a4703fcc2d76c8c2d8eb9f861f79d0c56f4b8f5f2607ccec8/pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e", size = 1968793, upload-time = "2024-09-16T16:04:52.604Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e3/b9/41f7efe80f6ce2ed3ee3c2dcfe10ab7adc1172f778cc9659509a79518c43/pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24", size = 2116872, upload-time = "2024-09-16T16:04:54.41Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/63/08/b59b7a92e03dd25554b0436554bf23e7c29abae7cce4b1c459cd92746811/pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84", size = 1738535, upload-time = "2024-09-16T16:04:55.828Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/88/8d/479293e4d39ab409747926eec4329de5b7129beaedc3786eca070605d07f/pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9", size = 1917992, upload-time = "2024-09-16T16:04:57.395Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/13/a9/5d582eb3204464284611f636b55c0a7410d748ff338756323cb1ce721b96/pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5", size = 1857135, upload-time = "2024-09-16T16:06:10.45Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2c/57/faf36290933fe16717f97829eabfb1868182ac495f99cf0eda9f59687c9d/pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec", size = 1740583, upload-time = "2024-09-16T16:06:12.298Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/91/7c/d99e3513dc191c4fec363aef1bf4c8af9125d8fa53af7cb97e8babef4e40/pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480", size = 1793637, upload-time = "2024-09-16T16:06:14.092Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/29/18/812222b6d18c2d13eebbb0f7cdc170a408d9ced65794fdb86147c77e1982/pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068", size = 1941963, upload-time = "2024-09-16T16:06:16.757Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0f/36/c1f3642ac3f05e6bb4aec3ffc399fa3f84895d259cf5f0ce3054b7735c29/pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801", size = 1915332, upload-time = "2024-09-16T16:06:18.677Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f7/ca/9c0854829311fb446020ebb540ee22509731abad886d2859c855dd29b904/pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728", size = 1957926, upload-time = "2024-09-16T16:06:20.591Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c0/1c/7836b67c42d0cd4441fcd9fafbf6a027ad4b79b6559f80cf11f89fd83648/pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433", size = 2100342, upload-time = "2024-09-16T16:06:22.888Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a9/f9/b6bcaf874f410564a78908739c80861a171788ef4d4f76f5009656672dfe/pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753", size = 1920344, upload-time = "2024-09-16T16:06:24.849Z" }, +] + +[[package]] +name = "pydantic-settings" +version = "2.10.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/68/85/1ea668bbab3c50071ca613c6ab30047fb36ab0da1b92fa8f17bbc38fd36c/pydantic_settings-2.10.1.tar.gz", hash = "sha256:06f0062169818d0f5524420a360d632d5857b83cffd4d42fe29597807a1614ee", size = 172583, upload-time = "2025-06-24T13:26:46.841Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/58/f0/427018098906416f580e3cf1366d3b1abfb408a0652e9f31600c24a1903c/pydantic_settings-2.10.1-py3-none-any.whl", hash = "sha256:a60952460b99cf661dc25c29c0ef171721f98bfcb52ef8d9ea4c943d7c8cc796", size = 45235, upload-time = "2025-06-24T13:26:45.485Z" }, +] + +[[package]] +name = "pydash" +version = "7.0.7" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1a/15/dfb29b8c49e40b9bfd2482f0d81b49deeef8146cc528d21dd8e67751e945/pydash-7.0.7.tar.gz", hash = "sha256:cc935d5ac72dd41fb4515bdf982e7c864c8b5eeea16caffbab1936b849aaa49a", size = 184993, upload-time = "2024-01-28T02:22:34.143Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ad/bf/7f7413f9f2aad4c1167cb05a231903fe65847fc91b7115a4dd9d9ebd4f1f/pydash-7.0.7-py3-none-any.whl", hash = "sha256:c3c5b54eec0a562e0080d6f82a14ad4d5090229847b7e554235b5c1558c745e1", size = 110286, upload-time = "2024-01-28T02:22:31.355Z" }, +] + +[[package]] +name = "pydivert" +version = "2.1.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cf/71/2da9bcf742df3ab23f75f10fedca074951dd13a84bda8dea3077f68ae9a6/pydivert-2.1.0.tar.gz", hash = "sha256:f0e150f4ff591b78e35f514e319561dadff7f24a82186a171dd4d465483de5b4", size = 91057, upload-time = "2017-10-20T21:36:58.165Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ca/8f/86d7931c62013a5a7ebf4e1642a87d4a6050c0f570e714f61b0df1984c62/pydivert-2.1.0-py2.py3-none-any.whl", hash = "sha256:382db488e3c37c03ec9ec94e061a0b24334d78dbaeebb7d4e4d32ce4355d9da1", size = 104718, upload-time = "2017-10-20T21:36:56.726Z" }, +] + +[[package]] +name = "pyee" +version = "13.0.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/95/03/1fd98d5841cd7964a27d729ccf2199602fe05eb7a405c1462eb7277945ed/pyee-13.0.0.tar.gz", hash = "sha256:b391e3c5a434d1f5118a25615001dbc8f669cf410ab67d04c4d4e07c55481c37", size = 31250, upload-time = "2025-03-17T18:53:15.955Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9b/4d/b9add7c84060d4c1906abe9a7e5359f2a60f7a9a4f67268b2766673427d8/pyee-13.0.0-py3-none-any.whl", hash = "sha256:48195a3cddb3b1515ce0695ed76036b5ccc2ef3a9f963ff9f77aec0139845498", size = 15730, upload-time = "2025-03-17T18:53:14.532Z" }, +] + +[[package]] +name = "pyexecjs" +version = "1.5.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ba/8e/aedef81641c8dca6fd0fb7294de5bed9c45f3397d67fddf755c1042c2642/PyExecJS-1.5.1.tar.gz", hash = "sha256:34cc1d070976918183ff7bdc0ad71f8157a891c92708c00c5fbbff7a769f505c", size = 13344, upload-time = "2018-01-18T04:33:55.126Z" } + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pyicu" +version = "2.15.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/88/b0/c8b61bac55424e2ff80e20d7251c3f002baff3c07c34cee3849e3505d8f5/pyicu-2.15.3.tar.gz", hash = "sha256:f32e78e1cb64d0aeb14f027e037a8944861d3114548818a6adf0081ef51aefc3", size = 267569, upload-time = "2025-09-15T20:58:50.936Z" } + +[[package]] +name = "pyjwt" +version = "2.8.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/30/72/8259b2bccfe4673330cea843ab23f86858a419d8f1493f66d413a76c7e3b/PyJWT-2.8.0.tar.gz", hash = "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de", size = 78313, upload-time = "2023-07-18T20:02:22.594Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2b/4f/e04a8067c7c96c364cef7ef73906504e2f40d690811c021e1a1901473a19/PyJWT-2.8.0-py3-none-any.whl", hash = "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320", size = 22591, upload-time = "2023-07-18T20:02:21.561Z" }, +] + +[package.optional-dependencies] +crypto = [ + { name = "cryptography" }, +] + +[[package]] +name = "pymysql" +version = "1.1.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b3/8f/ce59b5e5ed4ce8512f879ff1fa5ab699d211ae2495f1adaa5fbba2a1eada/pymysql-1.1.1.tar.gz", hash = "sha256:e127611aaf2b417403c60bf4dc570124aeb4a57f5f37b8e95ae399a42f904cd0", size = 47678, upload-time = "2024-05-21T11:03:43.722Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0c/94/e4181a1f6286f545507528c78016e00065ea913276888db2262507693ce5/PyMySQL-1.1.1-py3-none-any.whl", hash = "sha256:4de15da4c61dc132f4fb9ab763063e693d521a80fd0e87943b9a453dd4c19d6c", size = 44972, upload-time = "2024-05-21T11:03:41.216Z" }, +] + +[[package]] +name = "pynndescent" +version = "0.5.13" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "joblib" }, + { name = "llvmlite" }, + { name = "numba" }, + { name = "scikit-learn" }, + { name = "scipy" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7e/58/560a4db5eb3794d922fe55804b10326534ded3d971e1933c1eef91193f5e/pynndescent-0.5.13.tar.gz", hash = "sha256:d74254c0ee0a1eeec84597d5fe89fedcf778593eeabe32c2f97412934a9800fb", size = 2975955, upload-time = "2024-06-17T15:48:32.914Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d2/53/d23a97e0a2c690d40b165d1062e2c4ccc796be458a1ce59f6ba030434663/pynndescent-0.5.13-py3-none-any.whl", hash = "sha256:69aabb8f394bc631b6ac475a1c7f3994c54adf3f51cd63b2730fefba5771b949", size = 56850, upload-time = "2024-06-17T15:48:31.184Z" }, +] + +[[package]] +name = "pyodbc" +version = "5.2.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a0/36/a1ac7d23a1611e7ccd4d27df096f3794e8d1e7faa040260d9d41b6fc3185/pyodbc-5.2.0.tar.gz", hash = "sha256:de8be39809c8ddeeee26a4b876a6463529cd487a60d1393eb2a93e9bcd44a8f5", size = 116908, upload-time = "2024-10-16T01:40:13.425Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/30/01/05c4a4ec122c4a8a37fa1be5bdbf6fb23724a2ee3b1b771bb46f710158a9/pyodbc-5.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eb0850e3e3782f57457feed297e220bb20c3e8fd7550d7a6b6bb96112bd9b6fe", size = 72483, upload-time = "2024-10-16T01:39:23.697Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/73/22/ba718cc5508bdfbb53e1906018d7f597be37241c769dda8a48f52af96fe3/pyodbc-5.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0dae0fb86078c87acf135dbe5afd3c7d15d52ab0db5965c44159e84058c3e2fb", size = 71794, upload-time = "2024-10-16T01:39:25.372Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/24/e4/9d859ea3642059c10a6644a00ccb1f8b8e02c1e4f49ab34250db1273c2c5/pyodbc-5.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6493b9c7506ca964b80ad638d0dc82869df7058255d71f04fdd1405e88bcb36b", size = 332850, upload-time = "2024-10-16T01:39:27.789Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b9/a7/98c3555c10cfeb343ec7eea69ecb17476aa3ace72131ea8a4a1f8250318c/pyodbc-5.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e04de873607fb960e71953c164c83e8e5d9291ce0d69e688e54947b254b04902", size = 336009, upload-time = "2024-10-16T01:39:29.694Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/24/c1/d5b16dd62eb70f281bc90cdc1e3c46af7acda3f0f6afb34553206506ccb2/pyodbc-5.2.0-cp310-cp310-win32.whl", hash = "sha256:74135cb10c1dcdbd99fe429c61539c232140e62939fa7c69b0a373cc552e4a08", size = 62407, upload-time = "2024-10-16T01:39:31.894Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f5/12/22c83669abee4ca5915aa89172cf1673b58ca05f44dabeb8b0bac9b7fecc/pyodbc-5.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:d287121eeaa562b9ab3d4c52fa77c793dfedd127049273eb882a05d3d67a8ce8", size = 68874, upload-time = "2024-10-16T01:39:33.325Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8f/a2/5907ce319a571eb1e271d6a475920edfeacd92da1021bb2a15ed1b7f6ac1/pyodbc-5.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4627779f0a608b51ce2d2fe6d1d395384e65ca36248bf9dbb6d7cf2c8fda1cab", size = 72536, upload-time = "2024-10-16T01:39:34.715Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e1/b8/bd438ab2bb9481615142784b0c9778079a87ae1bca7a0fe8aabfc088aa9f/pyodbc-5.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4d997d3b6551273647825c734158ca8a6f682df269f6b3975f2499c01577ddec", size = 71825, upload-time = "2024-10-16T01:39:36.343Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8b/82/cf71ae99b511a7f20c380ce470de233a0291fa3798afa74e0adc8fad1675/pyodbc-5.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5102007a8c78dd2fc1c1b6f6147de8cfc020f81013e4b46c33e66aaa7d1bf7b1", size = 342304, upload-time = "2024-10-16T01:39:37.82Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/43/ea/03fe042f4a390df05e753ddd21c6cab006baae1eee71ce230f6e2a883944/pyodbc-5.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e3cbc7075a46c411b531ada557c4aef13d034060a70077717124cabc1717e2d", size = 346186, upload-time = "2024-10-16T01:39:39.3Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f9/80/48178bb50990147adb72ec9e377e94517a0dfaf2f2a6e3fe477d9a33671f/pyodbc-5.2.0-cp311-cp311-win32.whl", hash = "sha256:de1ee7ec2eb326b7be5e2c4ce20d472c5ef1a6eb838d126d1d26779ff5486e49", size = 62418, upload-time = "2024-10-16T01:39:40.797Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7c/6b/f0ad7d8a535d58f35f375ffbf367c68d0ec54452a431d23b0ebee4cd44c6/pyodbc-5.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:113f904b9852c12f10c7a3288f5a3563ecdbbefe3ccc829074a9eb8255edcd29", size = 68871, upload-time = "2024-10-16T01:39:41.997Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/26/26/104525b728fedfababd3143426b9d0008c70f0d604a3bf5d4773977d83f4/pyodbc-5.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:be43d1ece4f2cf4d430996689d89a1a15aeb3a8da8262527e5ced5aee27e89c3", size = 73014, upload-time = "2024-10-16T01:39:43.332Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4f/7d/bb632488b603bcd2a6753b858e8bc7dd56146dd19bd72003cc09ae6e3fc0/pyodbc-5.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9f7badd0055221a744d76c11440c0856fd2846ed53b6555cf8f0a8893a3e4b03", size = 72515, upload-time = "2024-10-16T01:39:44.506Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ab/38/a1b9bfe5a7062672268553c2d6ff93676173b0fb4bd583e8c4f74a0e296f/pyodbc-5.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad633c52f4f4e7691daaa2278d6e6ebb2fe4ae7709e610e22c7dd1a1d620cf8b", size = 348561, upload-time = "2024-10-16T01:39:45.986Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/71/82/ddb1c41c682550116f391aa6cab2052910046a30d63014bbe6d09c4958f4/pyodbc-5.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97d086a8f7a302b74c9c2e77bedf954a603b19168af900d4d3a97322e773df63", size = 353962, upload-time = "2024-10-16T01:39:47.254Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e5/29/fec0e739d0c1cab155843ed71d0717f5e1694effe3f28d397168f48bcd92/pyodbc-5.2.0-cp312-cp312-win32.whl", hash = "sha256:0e4412f8e608db2a4be5bcc75f9581f386ed6a427dbcb5eac795049ba6fc205e", size = 63050, upload-time = "2024-10-16T01:39:48.8Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/21/7f/3a47e022a97b017ffb73351a1061e4401bcb5aa4fc0162d04f4e5452e4fc/pyodbc-5.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:b1f5686b142759c5b2bdbeaa0692622c2ebb1f10780eb3c174b85f5607fbcf55", size = 69485, upload-time = "2024-10-16T01:39:49.732Z" }, +] + +[[package]] +name = "pyopenssl" +version = "25.1.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/04/8c/cd89ad05804f8e3c17dea8f178c3f40eeab5694c30e0c9f5bcd49f576fc3/pyopenssl-25.1.0.tar.gz", hash = "sha256:8d031884482e0c67ee92bf9a4d8cceb08d92aba7136432ffb0703c5280fc205b", size = 179937, upload-time = "2025-05-17T16:28:31.31Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/80/28/2659c02301b9500751f8d42f9a6632e1508aa5120de5e43042b8b30f8d5d/pyopenssl-25.1.0-py3-none-any.whl", hash = "sha256:2b11f239acc47ac2e5aca04fd7fa829800aeee22a2eb30d744572a157bd8a1ab", size = 56771, upload-time = "2025-05-17T16:28:29.197Z" }, +] + +[[package]] +name = "pyparsing" +version = "3.2.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bb/22/f1129e69d94ffff626bdb5c835506b3a5b4f3d070f17ea295e12c2c6f60f/pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be", size = 1088608, upload-time = "2025-03-25T05:01:28.114Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/05/e7/df2285f3d08fee213f2d041540fa4fc9ca6c2d44cf36d3a035bf2a8d2bcc/pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf", size = 111120, upload-time = "2025-03-25T05:01:24.908Z" }, +] + +[[package]] +name = "pypdf" +version = "6.0.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/20/ac/a300a03c3b34967c050677ccb16e7a4b65607ee5df9d51e8b6d713de4098/pypdf-6.0.0.tar.gz", hash = "sha256:282a99d2cc94a84a3a3159f0d9358c0af53f85b4d28d76ea38b96e9e5ac2a08d", size = 5033827, upload-time = "2025-08-11T14:22:02.352Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2c/83/2cacc506eb322bb31b747bc06ccb82cc9aa03e19ee9c1245e538e49d52be/pypdf-6.0.0-py3-none-any.whl", hash = "sha256:56ea60100ce9f11fc3eec4f359da15e9aec3821b036c1f06d2b660d35683abb8", size = 310465, upload-time = "2025-08-11T14:22:00.481Z" }, +] + +[[package]] +name = "pypdf2" +version = "3.0.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9f/bb/18dc3062d37db6c491392007dfd1a7f524bb95886eb956569ac38a23a784/PyPDF2-3.0.1.tar.gz", hash = "sha256:a74408f69ba6271f71b9352ef4ed03dc53a31aa404d29b5d31f53bfecfee1440", size = 227419, upload-time = "2022-12-31T10:36:13.13Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8e/5e/c86a5643653825d3c913719e788e41386bee415c2b87b4f955432f2de6b2/pypdf2-3.0.1-py3-none-any.whl", hash = "sha256:d16e4205cfee272fbdc0568b68d82be796540b1537508cef59388f839c191928", size = 232572, upload-time = "2022-12-31T10:36:10.327Z" }, +] + +[[package]] +name = "pypdfium2" +version = "4.30.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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 = "pyreadline3" +version = "3.5.4" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0f/49/4cea918a08f02817aabae639e3d0ac046fef9f9180518a3ad394e22da148/pyreadline3-3.5.4.tar.gz", hash = "sha256:8d57d53039a1c75adba8e50dd3d992b28143480816187ea5efbd5c78e6c885b7", size = 99839, upload-time = "2024-09-19T02:40:10.062Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5a/dc/491b7661614ab97483abf2056be1deee4dc2490ecbf7bff9ab5cdbac86e1/pyreadline3-3.5.4-py3-none-any.whl", hash = "sha256:eaf8e6cc3c49bcccf145fc6067ba8643d1df34d604a1ec0eccbf7a18e6d3fae6", size = 83178, upload-time = "2024-09-19T02:40:08.598Z" }, +] + +[[package]] +name = "pysocks" +version = "1.7.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bd/11/293dd436aea955d45fc4e8a35b6ae7270f5b8e00b53cf6c024c83b657a11/PySocks-1.7.1.tar.gz", hash = "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0", size = 284429, upload-time = "2019-09-20T02:07:35.714Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8d/59/b4572118e098ac8e46e399a1dd0f2d85403ce8bbaad9ec79373ed6badaf9/PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5", size = 16725, upload-time = "2019-09-20T02:06:22.938Z" }, +] + +[[package]] +name = "pystemmer" +version = "2.2.0.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/26/12/0378ba4391a4674067ae9db0e025ec998f6ca74caddd22fbdc59dc19aafb/pystemmer-2.2.0.3.tar.gz", hash = "sha256:9ac74c8d0f3358dbb050f64cddbb8d55021d831d92305d7c20780ea8d6c0020e", size = 303860, upload-time = "2024-10-10T00:36:25.921Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c1/c6/6929a114ca2b41464cd7643b5c7b12be2ef0bf43f65c3bba02bff3ddf163/PyStemmer-2.2.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2935aa78a89b04899de4a8b8b6339806e0d5cd93811de52e98829b5762cf913c", size = 214158, upload-time = "2024-10-10T00:56:09.408Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f7/27/b3950b55542ec9c59bae8484d0a3dcb88cba9f2268222f60f5538c8a853f/PyStemmer-2.2.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:31c9d3c808647d4c569737b32b40ed23c67133d2b89033ebc8b5756cadf6f1c1", size = 220116, upload-time = "2024-10-10T00:56:11.454Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b0/f5/d3fd6aab0d6a3c400f9a1bff1ec091ec29a34d76ef3b69b8472801b6d979/PyStemmer-2.2.0.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:584ead989545a60919e4015371dd2f69ff0ca985e76618d41930f77b9e248286", size = 612115, upload-time = "2024-10-10T00:56:13.639Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9e/53/2fd493c6d61d8cd42a3b3f8268ddae6658f3c8bad0adf205f218606f236d/PyStemmer-2.2.0.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be904f4d0d522de98ff9f0a348d8748c2f95926523b7b04ee75b50967289782d", size = 646708, upload-time = "2024-10-10T00:56:16.149Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/99/76/b09792a8141fb03f27f61c75333c84793298287f0d978a755deeb96029c3/PyStemmer-2.2.0.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:7024cdbcf4bbc2a5e1c277e11a10cb2b7481b7f99946cdcfa7271d5e9799399a", size = 626855, upload-time = "2024-10-10T00:56:18.097Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/70/f2/3275207f690d1eeea8e0e4900f5311e9e48497b39946e5519b8b4aa068d3/PyStemmer-2.2.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:aa0f70f84c69b7a6a38ddbea51a29f855c42120e8069ea4c450021a2c7dc42d8", size = 656727, upload-time = "2024-10-10T00:56:19.931Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6a/cd/224127bdf035803cfeddf83029d4398ee122289f11f5c2252832885de1c0/PyStemmer-2.2.0.3-cp310-cp310-win32.whl", hash = "sha256:85e583ec705b1b1c0503bc9cdbca027d3446cbc7cf7de3d29f1e0ab58999e5fe", size = 141159, upload-time = "2024-10-10T00:56:22.08Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/aa/94/51643ed8adb4a558c784fbbb9df2877a103cd34a55d2c2863030badab54d/PyStemmer-2.2.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:4556b2718bb22052f39a50f3166c4ee0e140c58ee06bbab31d57d765159d2f00", size = 184865, upload-time = "2024-10-10T00:56:23.506Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/03/bf/664bf08622e54aea83006f168ee3655a50dcebc7df66e023557bb1333d00/PyStemmer-2.2.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0c76ac603ff774fe3137340083315f34d6afbcd4ebebab99c1564c00c1c318ee", size = 214216, upload-time = "2024-10-10T00:56:25.104Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/59/b2/591ff043474cc1d2e2a86d4460b310b6bcc6d4fa91cc59ef4ffb887e64e4/PyStemmer-2.2.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ee100ba27a07d2fc3bd29cdd619cdff51735ed059002574c550697d1d160b7c9", size = 220087, upload-time = "2024-10-10T00:56:27.2Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a5/8d/c5101e915ea17f0ae8faefe7370c997315947d488bc1c8087dc5dee0bf43/PyStemmer-2.2.0.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3932f794e84bf29bdf4952d018b00c290fd06b055648f8e8fb9132e6684c4472", size = 630851, upload-time = "2024-10-10T00:56:28.817Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b2/9b/91b46ea4d920014181b2d7256086a68ae1374f40bfeebee13497674511c4/PyStemmer-2.2.0.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f74f6e0bb2034880bf4688ab5b95f97bb90952086682a93f080b260b454f933e", size = 669279, upload-time = "2024-10-10T00:56:31.5Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b0/02/cbd096d8ba891384ba9f3e684ff3cfd52b7f7131581b7f5e4b35d66b7289/PyStemmer-2.2.0.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:af925366939839e4bf11f426388201195c305a3edcdd9097e8775fbd083ff309", size = 639701, upload-time = "2024-10-10T00:56:33.817Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d6/ad/10571e4e79bd9b9101bf33d274b7ad2b09c4d5ddc241cdac451ea889cdc0/PyStemmer-2.2.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b199cbab2ce93ee1dd76da4d0523af5af4446d775b7bcb75dfdfcd2a8226404e", size = 673230, upload-time = "2024-10-10T00:56:35.939Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c4/01/e4ae834f93cffda555907b7d8e4678113d94d2874ba39e7cbbfe14ad9373/PyStemmer-2.2.0.3-cp311-cp311-win32.whl", hash = "sha256:e9bbaa5aa38a2f82bb1eaa6b97396e58c3a7f87e46607f52c7fda53927616eda", size = 140866, upload-time = "2024-10-10T00:56:38.397Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e8/d4/6ee439fe4ece1ebe40d13b7ef6f6bcd42a9775d725032db40b226be66bf8/PyStemmer-2.2.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:258af638eb68273f130c9878de2bb4a427fe99e86900b9b9b09c1cd7a185c189", size = 184766, upload-time = "2024-10-10T00:56:39.974Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/81/7c/e09c0ac1aa4571ded5f46c14aed5d4828b61392611ed1c59a8faf1cae98a/PyStemmer-2.2.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c30c44241065beb9432273874f199fc109473338d9f2c921a3387fd534fd94a7", size = 214916, upload-time = "2024-10-10T00:56:41.803Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e5/dc/b30dd228af740b1ffe18e4cfb48c22aa8481ce1e79405cac31086fb849bd/PyStemmer-2.2.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a6adf0b86b6be85f0cf80b2b255b2b0179782b4a3f39c0a6c5b3dd07af5f95eb", size = 220418, upload-time = "2024-10-10T00:56:43.354Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f7/68/eb9f06b2f04f408ed748d6e71fb2f5ae92deba0eea07b6e374268d53cb49/PyStemmer-2.2.0.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d42b41082553fa23a4ce191860fd7caffdeaf8507e84db630a97ed154bd2320", size = 644074, upload-time = "2024-10-10T00:56:45.45Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a6/9f/e95eeccf29ad1e1dbf4a14b76e2fa5b6698ba06f10634b097b32258839b0/PyStemmer-2.2.0.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec763ee2994402c534bf898ff318edd158c32071c3ffbdcd7ae7b7c884250471", size = 683255, upload-time = "2024-10-10T00:56:47.314Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3b/4b/8b077958eb760ac647d2362f8bd1ae99097fa351edc6333fde0dee51f912/PyStemmer-2.2.0.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:264f09d5f70b09c845a6f0d0d4973de674056fd50452cb9383ffae8fc0967f1d", size = 649228, upload-time = "2024-10-10T00:56:50.101Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7f/13/3d57038439293d93e40af89b48bc06c714f8cd8c5d22a9e6d0870f49ea1c/PyStemmer-2.2.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5634f38a781b9a893550c23380af080ca5291d19c2bcb1753a34022d1d0de7cb", size = 682987, upload-time = "2024-10-10T00:56:53.094Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9a/95/f04305ab6fc6312b4727b725ab983342237c7bb95d21d98bd768146dd9e8/PyStemmer-2.2.0.3-cp312-cp312-win32.whl", hash = "sha256:186c2e90ea2c3d0fab21f10f17b48fb7d716cba5f49b68f7f0fe539db4ff0499", size = 141412, upload-time = "2024-10-10T00:56:54.64Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0f/73/861a71009f20331cd22aff1c37b9c39ef3af4d57d6816df79d51b3fca533/PyStemmer-2.2.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:320c1da333f5f8571e2b313c9fa6c0a7a79d8a00a2ad0bf29932d931d236d7e8", size = 185395, upload-time = "2024-10-10T00:56:56.177Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0e/69/cbc7f00bda703f5b60a665a68e2d9df6c077c59da2bea52f35b1a496c549/PyStemmer-2.2.0.3-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ff3feeac41968fd8b50e9d6b8a03a5f15b27e765a0826f06dc32155f8f22909c", size = 170258, upload-time = "2024-10-10T00:58:12.984Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/21/31/eb832a4296814acd8c403eb555ada436700d93ab526c824e2f1d69dced15/PyStemmer-2.2.0.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:41a31d8ad810063e2cc675d93d0951dbfbb6ede278e111f15d74b7d781612364", size = 173914, upload-time = "2024-10-10T00:58:14.569Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f4/37/a4a4b8d9db835fbf55dd532c357286b58c7418188a38233f067ef0197ad0/PyStemmer-2.2.0.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4abcb516040d7a561eb95c60125f9f5636080c154f46d365b14cd33197ac74fd", size = 218327, upload-time = "2024-10-10T00:58:16.79Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/50/11/e9a0e32573391d335560e9caf05011d15c8b7d368f318d3a4516275c8938/PyStemmer-2.2.0.3-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8c307f1d5084e6074bc1826df9453887e589e92bab63851991b444f68a08b7e", size = 225148, upload-time = "2024-10-10T00:58:18.347Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/65/ac/3822a77de25f035b77130bfd30dcf57871c8818795918c3806b9b4e152ca/PyStemmer-2.2.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7f0d5f36922ea94599f79f86383972e91cdeab28918f8e1535cd589d2b5fb345", size = 180277, upload-time = "2024-10-10T00:58:19.973Z" }, +] + +[[package]] +name = "pytest" +version = "8.3.5" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" }, +] + +[[package]] +name = "python-calamine" +version = "0.4.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cc/03/269f96535705b2f18c8977fa58e76763b4e4727a9b3ae277a9468c8ffe05/python_calamine-0.4.0.tar.gz", hash = "sha256:94afcbae3fec36d2d7475095a59d4dc6fae45829968c743cb799ebae269d7bbf", size = 127737, upload-time = "2025-07-04T06:05:28.626Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8e/06/885c73fd472cb76af4c4650174a7c11b77a8bc40585044bc445ac694e5e6/python_calamine-0.4.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:06011f11fd8d2dbfe0bc9bd8bd135c191aafe66f2d0c9eecf0ae3cb38f42f888", size = 832924, upload-time = "2025-07-04T06:03:13.192Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2d/76/28c704d875046cc1ca92d8c6e680f6bc38d83735397fee821929691fd57f/python_calamine-0.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:12e350e5967bf3206a8b472d9b6c348ff37ae791dba1a1715e076b2c39328557", size = 812087, upload-time = "2025-07-04T06:03:14.781Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ff/71/dc00e6d9187044d15c85cd0875d8aba2b3e0d3051ceabd47d859f40f69c8/python_calamine-0.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35be298f69006e86b0311a538c1c9694ce3012237c33572d3dfe2bea6b5b9820", size = 874841, upload-time = "2025-07-04T06:03:16.403Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4b/f6/be5e35263ceec21e77810d7900235124a9a83fd3c0afbbbb79da658d535c/python_calamine-0.4.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7abb10367aea435ca473b9b698636db912f2ab164f19a6c9675710ed926f33ac", size = 878236, upload-time = "2025-07-04T06:03:18.016Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9c/61/0068297ec0000b2c0755a608c8068a1070b8193675d2ff603d390291aa45/python_calamine-0.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:58c2c4440982ec6db64c826136661f84f84bc0d8ee0cdd64a38128cd217797eb", size = 1015229, upload-time = "2025-07-04T06:03:19.714Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d1/1d/8a9c7d491d31db1def335f4d90b85ea29940d84c59a27a88a442b840fda7/python_calamine-0.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e58cd89154fd1b5ef77c609f63dce108d390ece5a5f3225ca3ebedc8d343e9d5", size = 924810, upload-time = "2025-07-04T06:03:20.922Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/40/87/a104c11b320d3fb7f1997d97207addce0fe5e1d41e5fd2e8adb0fb8b1325/python_calamine-0.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f90f85e04c281d96c6dc5551176fc4e32c95257c3a2d384a947b3e68275c7d6", size = 887826, upload-time = "2025-07-04T06:03:22.607Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8c/a5/9580de7758950b39c5048787909b639771c92ad6ea7f909a48dca1dbc6fe/python_calamine-0.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d5f8408b01d8097b2e662d0205ca09695788fb5f3492ade27de4ad4160cb6bd4", size = 929943, upload-time = "2025-07-04T06:03:24.272Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/25/a1/2b8282ec4acdf1879f9d46d84a6907843d2a331639719a2cfc90356345b5/python_calamine-0.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3d61957c10d37e6bf508fafdf52e6bb3112db8196e30bca8bc4b4560db2cc5f5", size = 1052980, upload-time = "2025-07-04T06:03:26.022Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/95/03/ae56667a2a2eb273eea5f754212454c7073f8abe8e0fdca0edfbe5c0cf37/python_calamine-0.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a5a58bbfcad9c1192dada189e367ed46e72037fcaec585e970fa919b92e07a57", size = 1058045, upload-time = "2025-07-04T06:03:27.342Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/52/60/d7aaa39977ab401de33ae975dd704805ec9c76117f0fe3a53e45b718822c/python_calamine-0.4.0-cp310-cp310-win32.whl", hash = "sha256:f06415096bcd9218b6c15d39ee2006ec0f32282e3d08605391d2a8a52187f9ca", size = 663988, upload-time = "2025-07-04T06:03:29.004Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6a/f5/fdfeccd7d66e5c9c834288df6a657858a046d94a6e4cd624418cb5bb96dd/python_calamine-0.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:e457d1e07acb2798b72e70bd4e88f07cd486ca5129a19fadc6aa19a2cd4e76e8", size = 692422, upload-time = "2025-07-04T06:03:30.624Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d4/a5/bcd82326d0ff1ab5889e7a5e13c868b483fc56398e143aae8e93149ba43b/python_calamine-0.4.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d1687f8c4d7852920c7b4e398072f183f88dd273baf5153391edc88b7454b8c0", size = 833019, upload-time = "2025-07-04T06:03:32.214Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f6/1a/a681f1d2f28164552e91ef47bcde6708098aa64a5f5fe3952f22362d340a/python_calamine-0.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:258d04230bebbbafa370a15838049d912d6a0a2c4da128943d8160ca4b6db58e", size = 812268, upload-time = "2025-07-04T06:03:33.855Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3d/92/2fc911431733739d4e7a633cefa903fa49a6b7a61e8765bad29a4a7c47b1/python_calamine-0.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c686e491634934f059553d55f77ac67ca4c235452d5b444f98fe79b3579f1ea5", size = 875733, upload-time = "2025-07-04T06:03:35.154Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f4/f0/48bfae6802eb360028ca6c15e9edf42243aadd0006b6ac3e9edb41a57119/python_calamine-0.4.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4480af7babcc2f919c638a554b06b7b145d9ab3da47fd696d68c2fc6f67f9541", size = 878325, upload-time = "2025-07-04T06:03:36.638Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a4/dc/f8c956e15bac9d5d1e05cd1b907ae780e40522d2fd103c8c6e2f21dff4ed/python_calamine-0.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e405b87a8cd1e90a994e570705898634f105442029f25bab7da658ee9cbaa771", size = 1015038, upload-time = "2025-07-04T06:03:37.971Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/54/3f/e69ab97c7734fb850fba2f506b775912fd59f04e17488582c8fbf52dbc72/python_calamine-0.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a831345ee42615f0dfcb0ed60a3b1601d2f946d4166edae64fd9a6f9bbd57fc1", size = 924969, upload-time = "2025-07-04T06:03:39.253Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/79/03/b4c056b468908d87a3de94389166e0f4dba725a70bc39e03bc039ba96f6b/python_calamine-0.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9951b8e4cafb3e1623bb5dfc31a18d38ef43589275f9657e99dfcbe4c8c4b33e", size = 888020, upload-time = "2025-07-04T06:03:41.099Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/86/4f/b9092f7c970894054083656953184e44cb2dadff8852425e950d4ca419af/python_calamine-0.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a6619fe3b5c9633ed8b178684605f8076c9d8d85b29ade15f7a7713fcfdee2d0", size = 930337, upload-time = "2025-07-04T06:03:42.89Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/64/da/137239027bf253aabe7063450950085ec9abd827d0cbc5170f585f38f464/python_calamine-0.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2cc45b8e76ee331f6ea88ca23677be0b7a05b502cd4423ba2c2bc8dad53af1be", size = 1054568, upload-time = "2025-07-04T06:03:44.153Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/80/96/74c38bcf6b6825d5180c0e147b85be8c52dbfba11848b1e98ba358e32a64/python_calamine-0.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1b2cfb7ced1a7c80befa0cfddfe4aae65663eb4d63c4ae484b9b7a80ebe1b528", size = 1058317, upload-time = "2025-07-04T06:03:45.873Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/33/95/9d7b8fe8b32d99a6c79534df3132cfe40e9df4a0f5204048bf5e66ddbd93/python_calamine-0.4.0-cp311-cp311-win32.whl", hash = "sha256:04f4e32ee16814fc1fafc49300be8eeb280d94878461634768b51497e1444bd6", size = 663934, upload-time = "2025-07-04T06:03:47.407Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7c/e3/1c6cd9fd499083bea6ff1c30033ee8215b9f64e862babf5be170cacae190/python_calamine-0.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:a8543f69afac2213c0257bb56215b03dadd11763064a9d6b19786f27d1bef586", size = 692535, upload-time = "2025-07-04T06:03:48.699Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/94/1c/3105d19fbab6b66874ce8831652caedd73b23b72e88ce18addf8ceca8c12/python_calamine-0.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:54622e35ec7c3b6f07d119da49aa821731c185e951918f152c2dbf3bec1e15d6", size = 671751, upload-time = "2025-07-04T06:03:49.979Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/63/60/f951513aaaa470b3a38a87d65eca45e0a02bc329b47864f5a17db563f746/python_calamine-0.4.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:74bca5d44a73acf3dcfa5370820797fcfd225c8c71abcddea987c5b4f5077e98", size = 826603, upload-time = "2025-07-04T06:03:51.245Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/76/3f/789955bbc77831c639890758f945eb2b25d6358065edf00da6751226cf31/python_calamine-0.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cf80178f5d1b0ee2ccfffb8549c50855f6249e930664adc5807f4d0d6c2b269c", size = 805826, upload-time = "2025-07-04T06:03:52.482Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/00/4c/f87d17d996f647030a40bfd124fe45fe893c002bee35ae6aca9910a923ae/python_calamine-0.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65cfef345386ae86f7720f1be93495a40fd7e7feabb8caa1df5025d7fbc58a1f", size = 874989, upload-time = "2025-07-04T06:03:53.794Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/47/d2/3269367303f6c0488cf1bfebded3f9fe968d118a988222e04c9b2636bf2e/python_calamine-0.4.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f23e6214dbf9b29065a5dcfd6a6c674dd0e251407298c9138611c907d53423ff", size = 877504, upload-time = "2025-07-04T06:03:55.095Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f9/6d/c7ac35f5c7125e8bd07eb36773f300fda20dd2da635eae78a8cebb0b6ab7/python_calamine-0.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d792d304ee232ab01598e1d3ab22e074a32c2511476b5fb4f16f4222d9c2a265", size = 1014171, upload-time = "2025-07-04T06:03:56.777Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f0/81/5ea8792a2e9ab5e2a05872db3a4d3ed3538ad5af1861282c789e2f13a8cf/python_calamine-0.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf813425918fd68f3e991ef7c4b5015be0a1a95fc4a8ab7e73c016ef1b881bb4", size = 926737, upload-time = "2025-07-04T06:03:58.024Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cc/6e/989e56e6f073fc0981a74ba7a393881eb351bb143e5486aa629b5e5d6a8b/python_calamine-0.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbe2a0ccb4d003635888eea83a995ff56b0748c8c76fc71923544f5a4a7d4cd7", size = 887032, upload-time = "2025-07-04T06:03:59.298Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5d/92/2c9bd64277c6fe4be695d7d5a803b38d953ec8565037486be7506642c27c/python_calamine-0.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a7b3bb5f0d910b9b03c240987560f843256626fd443279759df4e91b717826d2", size = 929700, upload-time = "2025-07-04T06:04:01.388Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/64/fa/fc758ca37701d354a6bc7d63118699f1c73788a1f2e1b44d720824992764/python_calamine-0.4.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bd2c0fc2b5eabd08ceac8a2935bffa88dbc6116db971aa8c3f244bad3fd0f644", size = 1053971, upload-time = "2025-07-04T06:04:02.704Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/65/52/40d7e08ae0ddba331cdc9f7fb3e92972f8f38d7afbd00228158ff6d1fceb/python_calamine-0.4.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:85b547cb1c5b692a0c2406678d666dbc1cec65a714046104683fe4f504a1721d", size = 1057057, upload-time = "2025-07-04T06:04:04.014Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/16/de/e8a071c0adfda73285d891898a24f6e99338328c404f497ff5b0e6bc3d45/python_calamine-0.4.0-cp312-cp312-win32.whl", hash = "sha256:4c2a1e3a0db4d6de4587999a21cc35845648c84fba81c03dd6f3072c690888e4", size = 665540, upload-time = "2025-07-04T06:04:05.679Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5e/f2/7fdfada13f80db12356853cf08697ff4e38800a1809c2bdd26ee60962e7a/python_calamine-0.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b193c89ffcc146019475cd121c552b23348411e19c04dedf5c766a20db64399a", size = 695366, upload-time = "2025-07-04T06:04:06.977Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/20/66/d37412ad854480ce32f50d9f74f2a2f88b1b8a6fbc32f70aabf3211ae89e/python_calamine-0.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:43a0f15e0b60c75a71b21a012b911d5d6f5fa052afad2a8edbc728af43af0fcf", size = 670740, upload-time = "2025-07-04T06:04:08.656Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a1/8c/120a1128ff422dba43d6f2d1d19520bca95abf1e5135bbe3b84a782d3927/python_calamine-0.4.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:4f9a44015de9e19a876babf707dc55708881930024220c8ae926ea0255f705fe", size = 835210, upload-time = "2025-07-04T06:05:05.543Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/db/62/6062945b8d5fc73d0a0c44b25ee5f4037cc32d62b57688a0d0ca6763006d/python_calamine-0.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:2c10bb42e0d0810368e78ee9359902f999a1f09bcc2391b060f91f981f75ae21", size = 816045, upload-time = "2025-07-04T06:05:06.86Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9f/7b/597470e5349056a1dd7b0e3a7e838da53cba9186771ca5501a264446f4ad/python_calamine-0.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:494c5dd1dcee25935ff9d7a9eef6b0f629266d1670aef3ee5e0d38370dcb3352", size = 875895, upload-time = "2025-07-04T06:05:08.259Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4b/e9/fabdd376713409e6ae6a8fee41293304798d794a64c9d407ba90f765f3d4/python_calamine-0.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f04fb24e70ab4403fc367b9b779eaa3bf61c140908d9115ddfe1e221372d5d4", size = 889049, upload-time = "2025-07-04T06:05:09.717Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/80/8e/cc09cc14276662cea1f161a20bdce46d8bfd409e84027a0a0515a00b63f5/python_calamine-0.4.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:49dce56dbd1efc024b63b913595f1a9bef6f66a6467aefad7dcd548654fedb5b", size = 931721, upload-time = "2025-07-04T06:05:11.086Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7b/b4/44f560b0ab4dcb49845f5c5361641885f8dc2b7e9b35fbf59242191c2eeb/python_calamine-0.4.0-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:7dc1755cd0b10ce5e2d80e77e9f19c13ed405b354178c3547ba5a11d34fce6ea", size = 1054521, upload-time = "2025-07-04T06:05:12.497Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9b/4b/ee4ac45d500bd2ae189f84adf3338dbd32579fa2198a05ad180666578575/python_calamine-0.4.0-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:9d981efa53ddfd8733555ad4c9368c8c1254bd8d1e162c93b8d341ead6acc5a9", size = 1059692, upload-time = "2025-07-04T06:05:13.857Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/74/99/835a9cd3ed503cb51fd15039b6404c536d201a6f3d16a6e069ce1079c5e4/python_calamine-0.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b370998567de0cd7a36a8ac73acabefea8397ad2d9aad3cf245b5d35f74cb990", size = 694046, upload-time = "2025-07-04T06:05:15.16Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.8.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4c/c4/13b4776ea2d76c115c1d1b84579f3764ee6d57204f6be27119f13a61d0a9/python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", size = 357324, upload-time = "2021-07-14T08:19:19.783Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/36/7a/87837f39d0296e723bb9b62bbb257d0355c7f6128853c78955f57342a56d/python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9", size = 247702, upload-time = "2021-07-14T08:19:18.161Z" }, +] + +[[package]] +name = "python-docx" +version = "1.2.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "lxml" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a9/f7/eddfe33871520adab45aaa1a71f0402a2252050c14c7e3009446c8f4701c/python_docx-1.2.0.tar.gz", hash = "sha256:7bc9d7b7d8a69c9c02ca09216118c86552704edc23bac179283f2e38f86220ce", size = 5723256, upload-time = "2025-06-16T20:46:27.921Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d0/00/1e03a4989fa5795da308cd774f05b704ace555a70f9bf9d3be057b680bcf/python_docx-1.2.0-py3-none-any.whl", hash = "sha256:3fd478f3250fbbbfd3b94fe1e985955737c145627498896a8a6bf81f4baf66c7", size = 252987, upload-time = "2025-06-16T20:46:22.506Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.0.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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-multipart" +version = "0.0.20" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" }, +] + +[[package]] +name = "python-pptx" +version = "1.0.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "lxml" }, + { name = "pillow" }, + { name = "typing-extensions" }, + { name = "xlsxwriter" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/52/a9/0c0db8d37b2b8a645666f7fd8accea4c6224e013c42b1d5c17c93590cd06/python_pptx-1.0.2.tar.gz", hash = "sha256:479a8af0eaf0f0d76b6f00b0887732874ad2e3188230315290cd1f9dd9cc7095", size = 10109297, upload-time = "2024-08-07T17:33:37.772Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d9/4f/00be2196329ebbff56ce564aa94efb0fbc828d00de250b1980de1a34ab49/python_pptx-1.0.2-py3-none-any.whl", hash = "sha256:160838e0b8565a8b1f67947675886e9fea18aa5e795db7ae531606d68e785cba", size = 472788, upload-time = "2024-08-07T17:33:28.192Z" }, +] + +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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 = "pywencai" +version = "0.12.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "fake-useragent" }, + { name = "pandas" }, + { name = "pydash" }, + { name = "pyexecjs" }, + { name = "requests" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/38/4b/bddead56c178dcb3abd3ea39f03d2f2e767828d6687b5221f06e95846df4/pywencai-0.12.2.tar.gz", hash = "sha256:7a916de189588710b94ef85eff9f107e1d1c83a7def0856f43e46d7dae1cf55b", size = 903567, upload-time = "2024-01-09T15:13:47.731Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/77/4b/28d33a6edb8d98402789cbf418063a302a96d04d563163aef07ef5f97f22/pywencai-0.12.2-py3-none-any.whl", hash = "sha256:cd8e87771f057fe6e7019b41e07d9f2cea0600e41bbc445cd0e840d4ac1dde8c", size = 911134, upload-time = "2024-01-09T15:13:45.911Z" }, +] + +[[package]] +name = "pywin32" +version = "311" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7b/40/44efbb0dfbd33aca6a6483191dae0716070ed99e2ecb0c53683f400a0b4f/pywin32-311-cp310-cp310-win32.whl", hash = "sha256:d03ff496d2a0cd4a5893504789d4a15399133fe82517455e78bad62efbb7f0a3", size = 8760432, upload-time = "2025-07-14T20:13:05.9Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5e/bf/360243b1e953bd254a82f12653974be395ba880e7ec23e3731d9f73921cc/pywin32-311-cp310-cp310-win_amd64.whl", hash = "sha256:797c2772017851984b97180b0bebe4b620bb86328e8a884bb626156295a63b3b", size = 9590103, upload-time = "2025-07-14T20:13:07.698Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/57/38/d290720e6f138086fb3d5ffe0b6caa019a791dd57866940c82e4eeaf2012/pywin32-311-cp310-cp310-win_arm64.whl", hash = "sha256:0502d1facf1fed4839a9a51ccbcc63d952cf318f78ffc00a7e78528ac27d7a2b", size = 8778557, upload-time = "2025-07-14T20:13:11.11Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7c/af/449a6a91e5d6db51420875c54f6aff7c97a86a3b13a0b4f1a5c13b988de3/pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151", size = 8697031, upload-time = "2025-07-14T20:13:13.266Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/51/8f/9bb81dd5bb77d22243d33c8397f09377056d5c687aa6d4042bea7fbf8364/pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503", size = 9508308, upload-time = "2025-07-14T20:13:15.147Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/44/7b/9c2ab54f74a138c491aba1b1cd0795ba61f144c711daea84a88b63dc0f6c/pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2", size = 8703930, upload-time = "2025-07-14T20:13:16.945Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199, upload-time = "2024-08-06T20:31:40.178Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758, upload-time = "2024-08-06T20:31:42.173Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463, upload-time = "2024-08-06T20:31:44.263Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280, upload-time = "2024-08-06T20:31:50.199Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239, upload-time = "2024-08-06T20:31:52.292Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802, upload-time = "2024-08-06T20:31:53.836Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527, upload-time = "2024-08-06T20:31:55.565Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052, upload-time = "2024-08-06T20:31:56.914Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774, upload-time = "2024-08-06T20:31:58.304Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612, upload-time = "2024-08-06T20:32:03.408Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040, upload-time = "2024-08-06T20:32:04.926Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829, upload-time = "2024-08-06T20:32:06.459Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167, upload-time = "2024-08-06T20:32:08.338Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952, upload-time = "2024-08-06T20:32:14.124Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301, upload-time = "2024-08-06T20:32:16.17Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638, upload-time = "2024-08-06T20:32:18.555Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850, upload-time = "2024-08-06T20:32:19.889Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980, upload-time = "2024-08-06T20:32:21.273Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload-time = "2024-08-06T20:32:25.131Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload-time = "2024-08-06T20:32:26.511Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload-time = "2024-08-06T20:32:28.363Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload-time = "2024-08-06T20:32:30.058Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload-time = "2024-08-06T20:32:31.881Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload-time = "2024-08-06T20:32:37.083Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload-time = "2024-08-06T20:32:38.898Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload-time = "2024-08-06T20:32:40.241Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload-time = "2024-08-06T20:32:41.93Z" }, +] + +[[package]] +name = "qianfan" +version = "0.4.6" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "aiolimiter" }, + { name = "bce-python-sdk" }, + { name = "cachetools" }, + { name = "diskcache" }, + { name = "multiprocess" }, + { name = "prompt-toolkit" }, + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "rich" }, + { name = "tenacity" }, + { name = "typer" }, + { name = "typing-extensions", marker = "python_full_version <= '3.10'" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3a/10/e06dd2f67a1f5f5a8eefac2e13c8f6d79c76e86a520c8976af33700d9c43/qianfan-0.4.6.tar.gz", hash = "sha256:90c2bf6f5fa1d1ae6ff63d982ce7b5fcb771c73048e81a147bcc24abed7eaefe", size = 312564, upload-time = "2024-08-17T08:43:14.448Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/53/77/0fa3a283114078cd3b34465e4eeef03dae3cd94a63b81ec773d8883267a6/qianfan-0.4.6-py3-none-any.whl", hash = "sha256:7d8746356a2b88b42333e5fbb74c9ef7897d59b732e651a69e38e1be4512f0b1", size = 446480, upload-time = "2024-08-17T08:43:12.686Z" }, +] + +[[package]] +name = "ragflow" +version = "0.21.1" +source = { virtual = "." } +dependencies = [ + { name = "akshare" }, + { name = "anthropic" }, + { name = "arxiv" }, + { name = "aspose-slides", marker = "platform_machine == 'x86_64' or (platform_machine == 'arm64' and sys_platform == 'darwin')" }, + { name = "azure-identity" }, + { name = "azure-storage-blob" }, + { name = "azure-storage-file-datalake" }, + { name = "beartype" }, + { name = "bio" }, + { name = "blinker" }, + { name = "boto3" }, + { name = "botocore" }, + { name = "cachetools" }, + { name = "captcha" }, + { name = "chardet" }, + { name = "click" }, + { name = "cn2an" }, + { name = "cohere" }, + { name = "crawl4ai" }, + { name = "dashscope" }, + { name = "datrie" }, + { name = "debugpy" }, + { name = "deepl" }, + { name = "demjson3" }, + { name = "discord-py" }, + { name = "duckduckgo-search" }, + { name = "editdistance" }, + { name = "elastic-transport" }, + { name = "elasticsearch" }, + { name = "elasticsearch-dsl" }, + { name = "extract-msg" }, + { name = "filelock" }, + { name = "flasgger" }, + { name = "flask" }, + { name = "flask-cors" }, + { name = "flask-login" }, + { name = "flask-mail" }, + { name = "flask-session" }, + { name = "google-genai" }, + { name = "google-generativeai" }, + { name = "google-search-results" }, + { name = "graspologic" }, + { name = "groq" }, + { name = "hanziconv" }, + { name = "html-text" }, + { name = "httpx", extra = ["socks"] }, + { name = "huggingface-hub" }, + { name = "infinity-emb" }, + { name = "infinity-sdk" }, + { name = "itsdangerous" }, + { name = "json-repair" }, + { name = "langfuse" }, + { name = "lark" }, + { name = "litellm" }, + { name = "mammoth" }, + { name = "markdown" }, + { name = "markdown-to-json" }, + { name = "markdownify" }, + { name = "mcp" }, + { name = "mini-racer" }, + { name = "minio" }, + { name = "mistralai" }, + { name = "nltk" }, + { name = "numpy" }, + { name = "ollama" }, + { name = "onnxruntime", marker = "platform_machine != 'x86_64' or sys_platform == 'darwin'" }, + { name = "onnxruntime-gpu", marker = "platform_machine == 'x86_64' and sys_platform != 'darwin'" }, + { name = "openai" }, + { name = "opencv-python" }, + { name = "opencv-python-headless" }, + { name = "opendal" }, + { name = "openpyxl" }, + { name = "opensearch-py" }, + { name = "ormsgpack" }, + { name = "pandas" }, + { name = "pdfplumber" }, + { name = "peewee" }, + { name = "pillow" }, + { name = "pluginlib" }, + { name = "protobuf" }, + { name = "psycopg2-binary" }, + { name = "pyclipper" }, + { name = "pycryptodomex" }, + { name = "pyicu" }, + { name = "pymysql" }, + { name = "pyodbc" }, + { name = "pypdf" }, + { name = "pypdf2" }, + { name = "python-calamine" }, + { name = "python-dateutil" }, + { name = "python-docx" }, + { name = "python-dotenv" }, + { name = "python-pptx" }, + { name = "pywencai" }, + { name = "qianfan" }, + { name = "ranx" }, + { name = "readability-lxml" }, + { name = "replicate" }, + { name = "requests" }, + { name = "roman-numbers" }, + { name = "ruamel-base" }, + { name = "ruamel-yaml" }, + { name = "scholarly" }, + { name = "scikit-learn" }, + { name = "selenium" }, + { name = "selenium-wire" }, + { name = "setuptools" }, + { name = "shapely" }, + { name = "six" }, + { name = "strenum" }, + { name = "tabulate" }, + { name = "tavily-python" }, + { name = "tencentcloud-sdk-python" }, + { name = "tika" }, + { name = "tiktoken" }, + { name = "trio" }, + { name = "umap-learn" }, + { name = "valkey" }, + { name = "vertexai" }, + { name = "volcengine" }, + { name = "voyageai" }, + { name = "webdriver-manager" }, + { name = "werkzeug" }, + { name = "wikipedia" }, + { name = "word2number" }, + { name = "xgboost" }, + { name = "xpinyin" }, + { name = "xxhash" }, + { name = "yfinance" }, + { name = "zhipuai" }, +] + +[package.optional-dependencies] +full = [ + { name = "bcembedding" }, + { name = "fastembed", marker = "platform_machine != 'x86_64' or sys_platform == 'darwin'" }, + { name = "fastembed-gpu", marker = "platform_machine == 'x86_64' and sys_platform != 'darwin'" }, + { name = "flagembedding" }, + { name = "torch" }, + { name = "transformers" }, +] + +[package.dev-dependencies] +test = [ + { name = "hypothesis" }, + { name = "openpyxl" }, + { name = "pillow" }, + { name = "pytest" }, + { name = "python-docx" }, + { name = "python-pptx" }, + { name = "reportlab" }, + { name = "requests" }, + { name = "requests-toolbelt" }, +] + +[package.metadata] +requires-dist = [ + { name = "akshare", specifier = ">=1.15.78,<2.0.0" }, + { name = "anthropic", specifier = "==0.34.1" }, + { name = "arxiv", specifier = "==2.1.3" }, + { name = "aspose-slides", marker = "platform_machine == 'x86_64' or (platform_machine == 'arm64' and sys_platform == 'darwin')", specifier = ">=24.9.0,<25.0.0" }, + { name = "azure-identity", specifier = "==1.17.1" }, + { name = "azure-storage-blob", specifier = "==12.22.0" }, + { name = "azure-storage-file-datalake", specifier = "==12.16.0" }, + { name = "bcembedding", marker = "extra == 'full'", specifier = "==0.1.5" }, + { name = "beartype", specifier = ">=0.18.5,<0.19.0" }, + { name = "bio", specifier = "==1.7.1" }, + { name = "blinker", specifier = "==1.7.0" }, + { name = "boto3", specifier = "==1.34.140" }, + { name = "botocore", specifier = "==1.34.140" }, + { name = "cachetools", specifier = "==5.3.3" }, + { name = "captcha", specifier = ">=0.7.1" }, + { name = "chardet", specifier = "==5.2.0" }, + { name = "click", specifier = ">=8.1.8" }, + { name = "cn2an", specifier = "==0.5.22" }, + { name = "cohere", specifier = "==5.6.2" }, + { name = "crawl4ai", specifier = ">=0.3.8" }, + { name = "dashscope", specifier = "==1.20.11" }, + { name = "datrie", specifier = "==0.8.2" }, + { name = "debugpy", specifier = ">=1.8.13" }, + { name = "deepl", specifier = "==1.18.0" }, + { name = "demjson3", specifier = "==3.0.6" }, + { name = "discord-py", specifier = "==2.3.2" }, + { name = "duckduckgo-search", specifier = ">=7.2.0,<8.0.0" }, + { name = "editdistance", specifier = "==0.8.1" }, + { name = "elastic-transport", specifier = "==8.12.0" }, + { name = "elasticsearch", specifier = "==8.12.1" }, + { name = "elasticsearch-dsl", specifier = "==8.12.0" }, + { name = "extract-msg", specifier = ">=0.39.0" }, + { name = "fastembed", marker = "(platform_machine != 'x86_64' and extra == 'full') or (sys_platform == 'darwin' and extra == 'full')", specifier = ">=0.3.6,<0.4.0" }, + { name = "fastembed-gpu", marker = "platform_machine == 'x86_64' and sys_platform != 'darwin' and extra == 'full'", specifier = ">=0.3.6,<0.4.0" }, + { name = "filelock", specifier = "==3.15.4" }, + { name = "flagembedding", marker = "extra == 'full'", specifier = "==1.2.10" }, + { name = "flasgger", specifier = ">=0.9.7.1,<0.10.0" }, + { name = "flask", specifier = "==3.0.3" }, + { name = "flask-cors", specifier = "==5.0.0" }, + { name = "flask-login", specifier = "==0.6.3" }, + { name = "flask-mail", specifier = ">=0.10.0" }, + { name = "flask-session", specifier = "==0.8.0" }, + { name = "google-genai", specifier = ">=1.41.0,<2.0.0" }, + { name = "google-generativeai", specifier = ">=0.8.1,<0.9.0" }, + { name = "google-search-results", specifier = "==2.4.2" }, + { name = "graspologic", specifier = ">=3.4.1,<4.0.0" }, + { name = "groq", specifier = "==0.9.0" }, + { name = "hanziconv", specifier = "==0.3.2" }, + { name = "html-text", specifier = "==0.6.2" }, + { name = "httpx", extras = ["socks"], specifier = ">=0.28.1,<0.29.0" }, + { name = "huggingface-hub", specifier = ">=0.25.0,<0.26.0" }, + { name = "infinity-emb", specifier = ">=0.0.66,<0.0.67" }, + { name = "infinity-sdk", specifier = "==0.6.1" }, + { name = "itsdangerous", specifier = "==2.1.2" }, + { name = "json-repair", specifier = "==0.35.0" }, + { name = "langfuse", specifier = ">=2.60.0" }, + { name = "lark", specifier = ">=1.2.2" }, + { name = "litellm", specifier = ">=1.74.15.post1" }, + { name = "mammoth", specifier = ">=1.11.0" }, + { name = "markdown", specifier = "==3.6" }, + { name = "markdown-to-json", specifier = "==2.1.1" }, + { name = "markdownify", specifier = ">=1.2.0" }, + { name = "mcp", specifier = ">=1.9.4" }, + { name = "mini-racer", specifier = ">=0.12.4,<0.13.0" }, + { name = "minio", specifier = "==7.2.4" }, + { name = "mistralai", specifier = "==0.4.2" }, + { name = "nltk", specifier = "==3.9.1" }, + { name = "numpy", specifier = ">=1.26.0,<2.0.0" }, + { name = "ollama", specifier = ">=0.5.0" }, + { name = "onnxruntime", marker = "platform_machine != 'x86_64' or sys_platform == 'darwin'", specifier = "==1.19.2" }, + { name = "onnxruntime-gpu", marker = "platform_machine == 'x86_64' and sys_platform != 'darwin'", specifier = "==1.19.2" }, + { name = "openai", specifier = ">=1.45.0" }, + { name = "opencv-python", specifier = "==4.10.0.84" }, + { name = "opencv-python-headless", specifier = "==4.10.0.84" }, + { name = "opendal", specifier = ">=0.45.0,<0.46.0" }, + { name = "openpyxl", specifier = ">=3.1.0,<4.0.0" }, + { name = "opensearch-py", specifier = "==2.7.1" }, + { name = "ormsgpack", specifier = "==1.5.0" }, + { name = "pandas", specifier = ">=2.2.0,<3.0.0" }, + { name = "pdfplumber", specifier = "==0.10.4" }, + { name = "peewee", specifier = "==3.17.1" }, + { name = "pillow", specifier = "==10.4.0" }, + { name = "pluginlib", specifier = "==0.9.4" }, + { name = "protobuf", specifier = "==5.27.2" }, + { name = "psycopg2-binary", specifier = "==2.9.9" }, + { name = "pyclipper", specifier = "==1.3.0.post5" }, + { name = "pycryptodomex", specifier = "==3.20.0" }, + { name = "pyicu", specifier = ">=2.15.3,<3.0.0" }, + { name = "pymysql", specifier = ">=1.1.1,<2.0.0" }, + { name = "pyodbc", specifier = ">=5.2.0,<6.0.0" }, + { name = "pypdf", specifier = "==6.0.0" }, + { name = "pypdf2", specifier = ">=3.0.1,<4.0.0" }, + { name = "python-calamine", specifier = ">=0.4.0" }, + { name = "python-dateutil", specifier = "==2.8.2" }, + { name = "python-docx", specifier = ">=1.1.2,<2.0.0" }, + { name = "python-dotenv", specifier = "==1.0.1" }, + { name = "python-pptx", specifier = ">=1.0.2,<2.0.0" }, + { name = "pywencai", specifier = "==0.12.2" }, + { name = "qianfan", specifier = "==0.4.6" }, + { name = "ranx", specifier = "==0.3.20" }, + { name = "readability-lxml", specifier = "==0.8.1" }, + { name = "replicate", specifier = "==0.31.0" }, + { name = "requests", specifier = "==2.32.2" }, + { name = "roman-numbers", specifier = "==1.0.2" }, + { name = "ruamel-base", specifier = "==1.0.0" }, + { name = "ruamel-yaml", specifier = ">=0.18.6,<0.19.0" }, + { name = "scholarly", specifier = "==1.7.11" }, + { name = "scikit-learn", specifier = "==1.5.0" }, + { name = "selenium", specifier = "==4.22.0" }, + { name = "selenium-wire", specifier = "==5.1.0" }, + { name = "setuptools", specifier = ">=75.2.0,<76.0.0" }, + { name = "shapely", specifier = "==2.0.5" }, + { name = "six", specifier = "==1.16.0" }, + { name = "strenum", specifier = "==0.4.15" }, + { name = "tabulate", specifier = "==0.9.0" }, + { name = "tavily-python", specifier = "==0.5.1" }, + { name = "tencentcloud-sdk-python", specifier = "==3.0.1215" }, + { name = "tika", specifier = "==2.6.0" }, + { name = "tiktoken", specifier = "==0.7.0" }, + { name = "torch", marker = "extra == 'full'", specifier = ">=2.5.0,<3.0.0" }, + { name = "transformers", marker = "extra == 'full'", specifier = ">=4.35.0,<5.0.0" }, + { name = "trio", specifier = ">=0.29.0" }, + { name = "umap-learn", specifier = "==0.5.6" }, + { name = "valkey", specifier = "==6.0.2" }, + { name = "vertexai", specifier = "==1.70.0" }, + { name = "volcengine", specifier = "==1.0.194" }, + { name = "voyageai", specifier = "==0.2.3" }, + { name = "webdriver-manager", specifier = "==4.0.1" }, + { name = "werkzeug", specifier = "==3.0.6" }, + { name = "wikipedia", specifier = "==1.4.0" }, + { name = "word2number", specifier = "==1.1" }, + { name = "xgboost", specifier = "==1.6.0" }, + { name = "xpinyin", specifier = "==0.7.6" }, + { name = "xxhash", specifier = ">=3.5.0,<4.0.0" }, + { name = "yfinance", specifier = "==0.2.65" }, + { name = "zhipuai", specifier = "==2.0.1" }, +] +provides-extras = ["full"] + +[package.metadata.requires-dev] +test = [ + { name = "hypothesis", specifier = ">=6.132.0" }, + { name = "openpyxl", specifier = ">=3.1.5" }, + { name = "pillow", specifier = ">=10.4.0" }, + { name = "pytest", specifier = ">=8.3.5" }, + { name = "python-docx", specifier = ">=1.1.2" }, + { name = "python-pptx", specifier = ">=1.0.2" }, + { name = "reportlab", specifier = ">=4.4.1" }, + { name = "requests", specifier = ">=2.32.2" }, + { name = "requests-toolbelt", specifier = ">=1.0.0" }, +] + +[[package]] +name = "rank-bm25" +version = "0.2.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fc/0a/f9579384aa017d8b4c15613f86954b92a95a93d641cc849182467cf0bb3b/rank_bm25-0.2.2.tar.gz", hash = "sha256:096ccef76f8188563419aaf384a02f0ea459503fdf77901378d4fd9d87e5e51d", size = 8347, upload-time = "2022-02-16T12:10:52.196Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2a/21/f691fb2613100a62b3fa91e9988c991e9ca5b89ea31c0d3152a3210344f9/rank_bm25-0.2.2-py3-none-any.whl", hash = "sha256:7bd4a95571adadfc271746fa146a4bcfd89c0cf731e49c3d1ad863290adbe8ae", size = 8584, upload-time = "2022-02-16T12:10:50.626Z" }, +] + +[[package]] +name = "ranx" +version = "0.3.20" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "cbor2" }, + { name = "fastparquet" }, + { name = "ir-datasets" }, + { name = "lz4" }, + { name = "numba" }, + { name = "numpy" }, + { name = "orjson" }, + { name = "pandas" }, + { name = "rich" }, + { name = "scipy" }, + { name = "seaborn" }, + { name = "tabulate" }, + { name = "tqdm" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/35/fe/4d4e7c69137afdeb5a4a85afcf04b84f087a284b7f22034e2e13e121de83/ranx-0.3.20.tar.gz", hash = "sha256:8afc6f2042c40645e5d1fd80c35ed75a885e18bd2db7e95cc7ec32a0b41e59ea", size = 51526, upload-time = "2024-07-01T17:40:29.448Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1e/30/53f41b7b728a48da8974075f56c57200d7b11e4e9fa93be3cabf8218dc0c/ranx-0.3.20-py3-none-any.whl", hash = "sha256:e056e4d5981b0328b045868cc7064fc57a545f36009fbe9bb602295ec33335de", size = 99318, upload-time = "2024-07-01T17:40:27.095Z" }, +] + +[[package]] +name = "readability-lxml" +version = "0.8.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "chardet" }, + { name = "cssselect" }, + { name = "lxml" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b9/62/6de3a9a8524c1a1ee0f2aee0dfbad13a36ebbca0db402abcf4e790496512/readability-lxml-0.8.1.tar.gz", hash = "sha256:e51fea56b5909aaf886d307d48e79e096293255afa567b7d08bca94d25b1a4e1", size = 15878, upload-time = "2020-07-04T00:45:51.112Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/39/a6/cfe22aaa19ac69b97d127043a76a5bbcb0ef24f3a0b22793c46608190caa/readability_lxml-0.8.1-py3-none-any.whl", hash = "sha256:e0d366a21b1bd6cca17de71a4e6ea16fcfaa8b0a5b4004e39e2c7eff884e6305", size = 20691, upload-time = "2020-07-04T00:45:49.348Z" }, +] + +[[package]] +name = "readerwriterlock" +version = "1.0.9" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a4/b9/6b7c390440ec23bf5fdf33e76d6c3b697a788b983c11cb2739d6541835d6/readerwriterlock-1.0.9.tar.gz", hash = "sha256:b7c4cc003435d7a8ff15b312b0a62a88d9800ba6164af88991f87f8b748f9bea", size = 16595, upload-time = "2021-09-06T03:41:21.75Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c2/5a/2f2e7fc026d5e64b5408aa3fbe0296a6407b8481196cae4daacacb3a3ae0/readerwriterlock-1.0.9-py3-none-any.whl", hash = "sha256:8c4b704e60d15991462081a27ef46762fea49b478aa4426644f2146754759ca7", size = 9999, upload-time = "2021-09-06T03:41:19.435Z" }, +] + +[[package]] +name = "red-black-tree-mod" +version = "1.20" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/34/12/944f61bc67a1e918953741c0b3b75a28f96d8060d08fd3614233309ced3b/red-black-tree-mod-1.20.tar.gz", hash = "sha256:2448e6fc9cbf1be204c753f352c6ee49aa8156dbf1faa57dfc26bd7705077e0a", size = 28589, upload-time = "2013-11-04T16:58:20.788Z" } + +[[package]] +name = "referencing" +version = "0.36.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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.7.31" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a6/36/9995080bbdabd4683dd11ab54edcf4fc0e2e4ce4d3eea8034b49fa5dd6ef/regex-2025.7.31.tar.gz", hash = "sha256:80a1af156ea8670ae63184e5c112b481326ece1879e09447f6fbb49d1b49330b", size = 407354, upload-time = "2025-07-30T00:13:26.283Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/03/30/51cf963b5acdb63f3c8e80c4129db3a997f2508e18bf8afc8696fb7408ab/regex-2025.7.31-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b40a8f8064c3b8032babb2049b7ab40812cbb394179556deb7c40c1e3b28630f", size = 489341, upload-time = "2025-07-30T00:10:50.324Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9d/fc/0fd637c648eb7b14cef655266129428fc23e0ceb0a14f5d816ba5e0b76f8/regex-2025.7.31-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f6aef1895f27875421e6d8047747702d6e512793c6d95614c56479a375541edb", size = 293048, upload-time = "2025-07-30T00:10:53.852Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e0/c7/dfdc769cfa01258f3ded76fd3e34d7aad9e96862513adccbdb2a7d02342f/regex-2025.7.31-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f124ff95b4cbedfd762897d4bd9da2b20b7574df1d60d498f16a42d398d524e9", size = 290094, upload-time = "2025-07-30T00:10:55.428Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/22/61/29f8152131ac78545a4382e14747246b9727ed9467f4f28becb6e97d7c2d/regex-2025.7.31-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ea5b162c50745694606f50170cc7cc84c14193ac5fd6ecb26126e826a7c12bd6", size = 788555, upload-time = "2025-07-30T00:10:57.293Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8b/f3/10f827814b9d130c9f9e41bd4378b2386e6dd25e8a4c69837692f28db829/regex-2025.7.31-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f970a3e058f587988a18ed4ddff6a6363fa72a41dfb29077d0efe8dc4df00da", size = 859934, upload-time = "2025-07-30T00:10:59.139Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/36/f9/b6454bedb3af4ae4aba3fdf54d6476872303b63c3d93d3dfc88b43c43334/regex-2025.7.31-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2dadf5788af5b10a78b996d24263e352e5f99dbfce9db4c48e9c875a9a7d051c", size = 906535, upload-time = "2025-07-30T00:11:00.849Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b9/28/f81b34d07c70dcd988b9bb124d47ae06c591bcabc2b703b601a61d39528c/regex-2025.7.31-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f67f9f8216a8e645c568daf104abc52cd5387127af8e8b17c7bc11b014d88fcb", size = 794549, upload-time = "2025-07-30T00:11:02.503Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/df/e6/3f25e64d853ca18151016329963f9e3a2c9d43f1bf36e172b23bf4e1a6bb/regex-2025.7.31-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:407da7504642830d4211d39dc23b8a9d400913b3f2d242774b8d17ead3487e00", size = 790436, upload-time = "2025-07-30T00:11:04.175Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5c/bc/5b976ca504f2fabb340600bad426e530a25f2c0109c009834af42d5cfd33/regex-2025.7.31-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ff7753bd717a9f2286d2171d758eebf11b3bfb21e6520b201e01169ec9cd5ec0", size = 778553, upload-time = "2025-07-30T00:11:05.863Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/56/a8/4b0db3eb6d4dbbc10777f20831fde816137a2a5737498c4c7f715e29f2f6/regex-2025.7.31-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:de088fe37d4c58a42401bf4ce2328b00a760c7d85473ccf6e489094e13452510", size = 853076, upload-time = "2025-07-30T00:11:07.521Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5a/57/5281baa48d9f25fc7f48f5c216c3825ae5b29c11b0c016535ae3f493b9df/regex-2025.7.31-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:67d708f8bfb89dcd57c3190cb5c343c7f40d3c81319a00c8188982a08c64b977", size = 843760, upload-time = "2025-07-30T00:11:09.032Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/51/82/b193e9a4afdb982d39edbb5c2e245a7af2b2e3963c32265fcf2d960b0008/regex-2025.7.31-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3fe81cd00ef1eaef1ef00d61200bacb55b1a130570cd9be2e793b98981c6cd9c", size = 782475, upload-time = "2025-07-30T00:11:10.721Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5f/fc/aa19a1eba4168922d3e6e66671380b86a3841a2a8f6a54ecdcf217424e88/regex-2025.7.31-cp310-cp310-win32.whl", hash = "sha256:8542ee1fd8c8be4db1c58902956a220bdbe7c38362decec989f57ace0e37f14c", size = 268730, upload-time = "2025-07-30T00:11:12.236Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bb/ae/9b0c128ef88c3068f949025f6671261e504c6e586f9138309705b82524ee/regex-2025.7.31-cp310-cp310-win_amd64.whl", hash = "sha256:77be56e167e2685828ab0abc1bdf38db3ab385e624c3ea2694b0d4ea70a2b7bc", size = 280420, upload-time = "2025-07-30T00:11:13.509Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ba/54/4e598d171a32a67cf7b3912ded0b71dcbab30235f293963c6021359c1233/regex-2025.7.31-cp310-cp310-win_arm64.whl", hash = "sha256:7ddc7ab76d917cb680a3b0fa53fc2971d40cc17415539007e15fa31c829dcf2b", size = 272887, upload-time = "2025-07-30T00:11:15.933Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ed/a7/85a371e31fb4f675593e0f731f89c54423e0b98d02a3e580fde206ba763b/regex-2025.7.31-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:55dc9f4094656d273562718d68cd8363f688e0b813d62696aad346bcd7b1c7d4", size = 489345, upload-time = "2025-07-30T00:11:17.22Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/85/75/2e738c69d43c086514f687f0d73a8cdc089e6823ad83192d2f6b2cf2b0c7/regex-2025.7.31-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c8ff37cac0e1c7ba943bf46f6431b0c86cbe42d42ae862ff7b152b4ccc232bdd", size = 293052, upload-time = "2025-07-30T00:11:18.751Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/81/7a/8b7b102bc524c3bf8eb3389afcdc381f47cff95a05c5370803e6fd5dfe44/regex-2025.7.31-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:622aa4ca90d7cf38433d425a4f00543b08d3b109cca379df8f31827cf5e2ecb3", size = 290098, upload-time = "2025-07-30T00:11:20.32Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f7/db/335b829ae5cde7e5de00c0576d899e180605f8c8befee9d58e014d49d4f3/regex-2025.7.31-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cbd4ee61dddfcff625f8642e940ba61121b28e98d0eca24d79114209e3e8ce1b", size = 798391, upload-time = "2025-07-30T00:11:21.972Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0d/34/418288618d3c2e68bbf4be3138bcfa1dd088b869923ea8f0bdac37576fa1/regex-2025.7.31-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca7c9af8f33540b51f1b76092e732b62211092af947239e5db471323ae39ead4", size = 867849, upload-time = "2025-07-30T00:11:23.703Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f7/b8/b570626227c257725cd7ecfee07f697850fc45d38df710a13b2b6d681943/regex-2025.7.31-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:beda88db2cae5dc82a64cba465f7e8686392d96116f87e664af46c4dfcdd9cbc", size = 915110, upload-time = "2025-07-30T00:11:25.62Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/42/af/d35718c1ccd68bd2e7b6b26a83c4919516d73610ddf124796797a7858749/regex-2025.7.31-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:055baef91bb31474bd919fd245cf154db00cbac449596952d3e6bc1e1b226808", size = 803693, upload-time = "2025-07-30T00:11:27.652Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cd/32/3daed29302ead90198cdcd1367edaa6913899f3d224c41eddcf8750979cc/regex-2025.7.31-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:02e660c2d02854eed41b13f0e2c98d24efce4fb439aa316742f8d32aeda2803b", size = 787634, upload-time = "2025-07-30T00:11:29.436Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ac/30/7f823c11f9b83f3a6c333a37322aa5867d7983447f8a83a07eccd49bd847/regex-2025.7.31-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4372ca5c43d0e255e68a9aa6812d9be3447c4ce7ba7cb1429c7b96d2c63ee9b1", size = 863173, upload-time = "2025-07-30T00:11:31.126Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/80/0e/a1cae70e3b5e44f5ad5672c1a17011c4ae37250987ce7635c2547c2cc570/regex-2025.7.31-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:481f069facacb4f40bf37a51748a88952f5dd5707dd849f216d53bf5522c8add", size = 853953, upload-time = "2025-07-30T00:11:32.692Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/73/ce/c8e3a31a449145572f9a97960e873546f7f842448dd2a0e68d4f667a77a4/regex-2025.7.31-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e8b4896ec5a9d0ae73d04e260ff6e1f366985b46505b2fa36d91501e4a7a98f0", size = 792225, upload-time = "2025-07-30T00:11:34.264Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/05/eb/0429111b7493459d3f765cc921d1e2ce1228b049895f3a1cb8525d51526a/regex-2025.7.31-cp311-cp311-win32.whl", hash = "sha256:47ceaa1e5eb243595306dfd5e5e294e251900aa94a0e2e1037fce125f432d2fb", size = 268744, upload-time = "2025-07-30T00:11:36.115Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ec/4a/5b05e5ff93eb852eec932eb71a7465e4a879a77f9558b2c132af0fcec438/regex-2025.7.31-cp311-cp311-win_amd64.whl", hash = "sha256:c4f6b34f509bb26507509b6f9ba85debcc6ca512d2d4a6fd5e96b9de2c187c83", size = 280437, upload-time = "2025-07-30T00:11:37.892Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fc/0c/a27e089caaef5a393b72cc65f88a6ba23805f72b6c431c19928d2870a3a3/regex-2025.7.31-cp311-cp311-win_arm64.whl", hash = "sha256:75f74892df1593036e83b48ba50d1e1951af650b6fabbfcf7531e7082e3561d4", size = 272887, upload-time = "2025-07-30T00:11:39.685Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/16/2a/6cde7309f9a90a04b43492eef04893dd551b4284cfbde3650bdab1f2d45e/regex-2025.7.31-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1af64eed343f19e1f09da9e9e8cfb82570050c4ed9fec400f9f118aab383da4b", size = 490326, upload-time = "2025-07-30T00:11:41.146Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/19/aa/6ad49cb0e7e5c4ba6219b33aacf1b8217ecd4d8ec9c1173d8166b7b6d7b8/regex-2025.7.31-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:eab98712c0a6d053fb67b021fae43422f7eab8fa2aaa25034f5ef01585112cc7", size = 293732, upload-time = "2025-07-30T00:11:43.003Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b6/99/67826894a43447144e610c76a958db9b31318435b04bc21974ccc75fcfce/regex-2025.7.31-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:34dcb7c4d89b83e7e3cb5a2679595f6f97d253815ed9402edbdfc56780668b89", size = 290272, upload-time = "2025-07-30T00:11:44.485Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2f/22/14dcf80def58a009143ced54665721ff7706440ce93b37bd34a36eebbe24/regex-2025.7.31-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:52f1925d123338835e5b13e5ef8e6a744c02aef8e538e661ad5c76185e6ad87a", size = 801903, upload-time = "2025-07-30T00:11:46.006Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/af/51/f36e6c8fdb62304ed1ba29bbc2d381ecdf37273cc32a4d65a5e64b1fa002/regex-2025.7.31-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:569c2b6812d223ae82a2a13c36362ca5933b88011ba869111eba8fb769ccf492", size = 872405, upload-time = "2025-07-30T00:11:47.669Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/09/4c/29083c291e329df4e883304c6d7a99beb6dba9cc31b4f99737cf0a75269a/regex-2025.7.31-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:27f17ade67d06ce4abff48f2ee99c6419f73e70882fe7ca51960916c75844e1f", size = 920078, upload-time = "2025-07-30T00:11:49.526Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/67/4e/7450d9d63edaa6abf7561fdf8ce540d7140bbd9d493328e3852c4bf9222c/regex-2025.7.31-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:45622fab3a90590a41a541afea739a732bf110dd081c15c84538b115cf5f59f5", size = 804512, upload-time = "2025-07-30T00:11:51.408Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2b/36/57cb185286dc2c63f0e08e68a8f724870a0c7939a9029cb6e14fea159100/regex-2025.7.31-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:defab878ce91944baf2ade775895a097ad7eeeab3618d87b4c29753aad98a5c4", size = 790672, upload-time = "2025-07-30T00:11:53.376Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/88/bd/f38a26e98f4f504dc669cbb79e0df3b3eb09a1bcebf8b9eac91f62afc36a/regex-2025.7.31-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8ae02caf994a0a0d958b9b0fc5aebbdb48fa93491a582dd00db3733d258a6ac4", size = 867146, upload-time = "2025-07-30T00:11:55.476Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c2/b8/96ce23733a03f6c673ed9dd23766bdbf678c340d6c08016ffde04f53508b/regex-2025.7.31-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a7c40ab21112711363d7612f35781c8b2d2d59c27e0a057a6486eea60ee01e54", size = 858552, upload-time = "2025-07-30T00:11:57.5Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/10/4a/f50cd8c4699049513df4d999e1ff61c054304a29e099ed4848de36e52fb7/regex-2025.7.31-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4723c01dd28c1b1de5f463bba8672e3d0dc3d94d5db056e4bbc3cbc84bf23c1c", size = 794274, upload-time = "2025-07-30T00:11:59.074Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a9/05/5b68b3d392f6158f5ea968f193f3f6271609b8197483809bdf4c2081989b/regex-2025.7.31-cp312-cp312-win32.whl", hash = "sha256:3ebf32b2b2f60aecd6f8d375ff310849251946cf953aac69b8b5b10e3ccebaf9", size = 269105, upload-time = "2025-07-30T00:12:00.673Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0d/6c/703c6c8d4bdf771cf65466d09bd300ca663041a53d30555beca43b26d3dc/regex-2025.7.31-cp312-cp312-win_amd64.whl", hash = "sha256:12f9ab65b4cc771dd6d8af806ded7425ca50d2a188d2fc3a5aba3dc49f5684b7", size = 279788, upload-time = "2025-07-30T00:12:02.535Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a1/90/f13eddf7ded8857b51f8d9ebebbc0e862ffb739f2a0f7fcff30a0f95676a/regex-2025.7.31-cp312-cp312-win_arm64.whl", hash = "sha256:fd454ed1fe245f983c2376b6f01948d6ec4a1e5869a8c883e320e1739cc63e57", size = 272991, upload-time = "2025-07-30T00:12:04.373Z" }, +] + +[[package]] +name = "replicate" +version = "0.31.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "httpx" }, + { name = "packaging" }, + { name = "pydantic" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/02/eb/04fbc1787d4d688feafa080b11e6672e819d170d9fda9ae4a2c2ac1e3dc2/replicate-0.31.0.tar.gz", hash = "sha256:6503f5266e08f7bd0f125f735a7dd68a298496b9f057be0f101aa7e8c7280728", size = 49894, upload-time = "2024-07-31T23:27:03.984Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/77/0f/f6067b7076faee22aef6190f703524e8ba8eac490191352c5cb0253c4823/replicate-0.31.0-py3-none-any.whl", hash = "sha256:27ee067ccb4c37d8c2fc5ab87bb312da36447dfcd12527002bbd0b78f6ef195a", size = 42950, upload-time = "2024-07-31T23:27:02.219Z" }, +] + +[[package]] +name = "reportlab" +version = "4.4.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "charset-normalizer" }, + { name = "pillow" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2f/83/3d44b873fa71ddc7d323c577fe4cfb61e05b34d14e64b6a232f9cfbff89d/reportlab-4.4.3.tar.gz", hash = "sha256:073b0975dab69536acd3251858e6b0524ed3e087e71f1d0d1895acb50acf9c7b", size = 3887532, upload-time = "2025-07-23T11:18:23.799Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/52/c8/aaf4e08679e7b1dc896ad30de0d0527f0fd55582c2e6deee4f2cc899bf9f/reportlab-4.4.3-py3-none-any.whl", hash = "sha256:df905dc5ec5ddaae91fc9cb3371af863311271d555236410954961c5ee6ee1b5", size = 1953896, upload-time = "2025-07-23T11:18:20.572Z" }, +] + +[[package]] +name = "requests" +version = "2.32.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/86/ec/535bf6f9bd280de6a4637526602a146a68fde757100ecf8c9333173392db/requests-2.32.2.tar.gz", hash = "sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289", size = 130327, upload-time = "2024-05-21T18:51:32.819Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c3/20/748e38b466e0819491f0ce6e90ebe4184966ee304fe483e2c414b0f4ef07/requests-2.32.2-py3-none-any.whl", hash = "sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c", size = 63902, upload-time = "2024-05-21T18:51:29.562Z" }, +] + +[package.optional-dependencies] +socks = [ + { name = "pysocks" }, +] + +[[package]] +name = "requests-toolbelt" +version = "1.0.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "requests" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888, upload-time = "2023-05-01T04:11:33.229Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" }, +] + +[[package]] +name = "retry" +version = "0.9.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "decorator" }, + { name = "py" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9d/72/75d0b85443fbc8d9f38d08d2b1b67cc184ce35280e4a3813cda2f445f3a4/retry-0.9.2.tar.gz", hash = "sha256:f8bfa8b99b69c4506d6f5bd3b0aabf77f98cdb17f3c9fc3f5ca820033336fba4", size = 6448, upload-time = "2016-05-11T13:58:51.541Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4b/0d/53aea75710af4528a25ed6837d71d117602b01946b307a3912cb3cfcbcba/retry-0.9.2-py2.py3-none-any.whl", hash = "sha256:ccddf89761fa2c726ab29391837d4327f819ea14d244c232a1d24c67a2f98606", size = 7986, upload-time = "2016-05-11T13:58:39.925Z" }, +] + +[[package]] +name = "rich" +version = "14.1.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fe/75/af448d8e52bf1d8fa6a9d089ca6c07ff4453d86c65c145d0a300bb073b9b/rich-14.1.0.tar.gz", hash = "sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8", size = 224441, upload-time = "2025-07-25T07:32:58.125Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f", size = 243368, upload-time = "2025-07-25T07:32:56.73Z" }, +] + +[[package]] +name = "roman-numbers" +version = "1.0.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/95/ce/e9f6b0d260f48713f2d735e0986ee4ead311cd168c217c5f94b0fad6817b/roman_numbers-1.0.2.tar.gz", hash = "sha256:fb84b7755ba972d549e73fac1c100f0eeb9fc247474d43d0f433c0b72152c699", size = 2574, upload-time = "2021-01-11T11:54:59.584Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9a/85/09e9e6bd6cd4cc0ed463d2b6ce3c7741698d45ca157318730a1346df4819/roman_numbers-1.0.2-py3-none-any.whl", hash = "sha256:ffbc00aaf41538208f975d1b1ccfe80372bae1866e7cd632862d8c6b45edf447", size = 3724, upload-time = "2021-01-11T11:54:57.686Z" }, +] + +[[package]] +name = "roman-numerals-py" +version = "3.1.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/30/76/48fd56d17c5bdbdf65609abbc67288728a98ed4c02919428d4f52d23b24b/roman_numerals_py-3.1.0.tar.gz", hash = "sha256:be4bf804f083a4ce001b5eb7e3c0862479d10f94c936f6c4e5f250aa5ff5bd2d", size = 9017, upload-time = "2025-02-22T07:34:54.333Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/53/97/d2cbbaa10c9b826af0e10fdf836e1bf344d9f0abb873ebc34d1f49642d3f/roman_numerals_py-3.1.0-py3-none-any.whl", hash = "sha256:9da2ad2fb670bcf24e81070ceb3be72f6c11c440d73bd579fbeca1e9f330954c", size = 7742, upload-time = "2025-02-22T07:34:52.422Z" }, +] + +[[package]] +name = "rpds-py" +version = "0.26.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a5/aa/4456d84bbb54adc6a916fb10c9b374f78ac840337644e4a5eda229c81275/rpds_py-0.26.0.tar.gz", hash = "sha256:20dae58a859b0906f0685642e591056f1e787f3a8b39c8e8749a45dc7d26bdb0", size = 27385, upload-time = "2025-07-01T15:57:13.958Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b9/31/1459645f036c3dfeacef89e8e5825e430c77dde8489f3b99eaafcd4a60f5/rpds_py-0.26.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:4c70c70f9169692b36307a95f3d8c0a9fcd79f7b4a383aad5eaa0e9718b79b37", size = 372466, upload-time = "2025-07-01T15:53:40.55Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/dd/ff/3d0727f35836cc8773d3eeb9a46c40cc405854e36a8d2e951f3a8391c976/rpds_py-0.26.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:777c62479d12395bfb932944e61e915741e364c843afc3196b694db3d669fcd0", size = 357825, upload-time = "2025-07-01T15:53:42.247Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bf/ce/badc5e06120a54099ae287fa96d82cbb650a5f85cf247ffe19c7b157fd1f/rpds_py-0.26.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec671691e72dff75817386aa02d81e708b5a7ec0dec6669ec05213ff6b77e1bd", size = 381530, upload-time = "2025-07-01T15:53:43.585Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1e/a5/fa5d96a66c95d06c62d7a30707b6a4cfec696ab8ae280ee7be14e961e118/rpds_py-0.26.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6a1cb5d6ce81379401bbb7f6dbe3d56de537fb8235979843f0d53bc2e9815a79", size = 396933, upload-time = "2025-07-01T15:53:45.78Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/00/a7/7049d66750f18605c591a9db47d4a059e112a0c9ff8de8daf8fa0f446bba/rpds_py-0.26.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4f789e32fa1fb6a7bf890e0124e7b42d1e60d28ebff57fe806719abb75f0e9a3", size = 513973, upload-time = "2025-07-01T15:53:47.085Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0e/f1/528d02c7d6b29d29fac8fd784b354d3571cc2153f33f842599ef0cf20dd2/rpds_py-0.26.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c55b0a669976cf258afd718de3d9ad1b7d1fe0a91cd1ab36f38b03d4d4aeaaf", size = 402293, upload-time = "2025-07-01T15:53:48.117Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/15/93/fde36cd6e4685df2cd08508f6c45a841e82f5bb98c8d5ecf05649522acb5/rpds_py-0.26.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c70d9ec912802ecfd6cd390dadb34a9578b04f9bcb8e863d0a7598ba5e9e7ccc", size = 383787, upload-time = "2025-07-01T15:53:50.874Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/69/f2/5007553aaba1dcae5d663143683c3dfd03d9395289f495f0aebc93e90f24/rpds_py-0.26.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3021933c2cb7def39d927b9862292e0f4c75a13d7de70eb0ab06efed4c508c19", size = 416312, upload-time = "2025-07-01T15:53:52.046Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8f/a7/ce52c75c1e624a79e48a69e611f1c08844564e44c85db2b6f711d76d10ce/rpds_py-0.26.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8a7898b6ca3b7d6659e55cdac825a2e58c638cbf335cde41f4619e290dd0ad11", size = 558403, upload-time = "2025-07-01T15:53:53.192Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/79/d5/e119db99341cc75b538bf4cb80504129fa22ce216672fb2c28e4a101f4d9/rpds_py-0.26.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:12bff2ad9447188377f1b2794772f91fe68bb4bbfa5a39d7941fbebdbf8c500f", size = 588323, upload-time = "2025-07-01T15:53:54.336Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/93/94/d28272a0b02f5fe24c78c20e13bbcb95f03dc1451b68e7830ca040c60bd6/rpds_py-0.26.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:191aa858f7d4902e975d4cf2f2d9243816c91e9605070aeb09c0a800d187e323", size = 554541, upload-time = "2025-07-01T15:53:55.469Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/93/e0/8c41166602f1b791da892d976057eba30685486d2e2c061ce234679c922b/rpds_py-0.26.0-cp310-cp310-win32.whl", hash = "sha256:b37a04d9f52cb76b6b78f35109b513f6519efb481d8ca4c321f6a3b9580b3f45", size = 220442, upload-time = "2025-07-01T15:53:56.524Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/87/f0/509736bb752a7ab50fb0270c2a4134d671a7b3038030837e5536c3de0e0b/rpds_py-0.26.0-cp310-cp310-win_amd64.whl", hash = "sha256:38721d4c9edd3eb6670437d8d5e2070063f305bfa2d5aa4278c51cedcd508a84", size = 231314, upload-time = "2025-07-01T15:53:57.842Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/09/4c/4ee8f7e512030ff79fda1df3243c88d70fc874634e2dbe5df13ba4210078/rpds_py-0.26.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:9e8cb77286025bdb21be2941d64ac6ca016130bfdcd228739e8ab137eb4406ed", size = 372610, upload-time = "2025-07-01T15:53:58.844Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fa/9d/3dc16be00f14fc1f03c71b1d67c8df98263ab2710a2fbd65a6193214a527/rpds_py-0.26.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5e09330b21d98adc8ccb2dbb9fc6cb434e8908d4c119aeaa772cb1caab5440a0", size = 358032, upload-time = "2025-07-01T15:53:59.985Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e7/5a/7f1bf8f045da2866324a08ae80af63e64e7bfaf83bd31f865a7b91a58601/rpds_py-0.26.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c9c1b92b774b2e68d11193dc39620d62fd8ab33f0a3c77ecdabe19c179cdbc1", size = 381525, upload-time = "2025-07-01T15:54:01.162Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/45/8a/04479398c755a066ace10e3d158866beb600867cacae194c50ffa783abd0/rpds_py-0.26.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:824e6d3503ab990d7090768e4dfd9e840837bae057f212ff9f4f05ec6d1975e7", size = 397089, upload-time = "2025-07-01T15:54:02.319Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/72/88/9203f47268db488a1b6d469d69c12201ede776bb728b9d9f29dbfd7df406/rpds_py-0.26.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ad7fd2258228bf288f2331f0a6148ad0186b2e3643055ed0db30990e59817a6", size = 514255, upload-time = "2025-07-01T15:54:03.38Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f5/b4/01ce5d1e853ddf81fbbd4311ab1eff0b3cf162d559288d10fd127e2588b5/rpds_py-0.26.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0dc23bbb3e06ec1ea72d515fb572c1fea59695aefbffb106501138762e1e915e", size = 402283, upload-time = "2025-07-01T15:54:04.923Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/34/a2/004c99936997bfc644d590a9defd9e9c93f8286568f9c16cdaf3e14429a7/rpds_py-0.26.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d80bf832ac7b1920ee29a426cdca335f96a2b5caa839811803e999b41ba9030d", size = 383881, upload-time = "2025-07-01T15:54:06.482Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/05/1b/ef5fba4a8f81ce04c427bfd96223f92f05e6cd72291ce9d7523db3b03a6c/rpds_py-0.26.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0919f38f5542c0a87e7b4afcafab6fd2c15386632d249e9a087498571250abe3", size = 415822, upload-time = "2025-07-01T15:54:07.605Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/16/80/5c54195aec456b292f7bd8aa61741c8232964063fd8a75fdde9c1e982328/rpds_py-0.26.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d422b945683e409000c888e384546dbab9009bb92f7c0b456e217988cf316107", size = 558347, upload-time = "2025-07-01T15:54:08.591Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f2/1c/1845c1b1fd6d827187c43afe1841d91678d7241cbdb5420a4c6de180a538/rpds_py-0.26.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:77a7711fa562ba2da1aa757e11024ad6d93bad6ad7ede5afb9af144623e5f76a", size = 587956, upload-time = "2025-07-01T15:54:09.963Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2e/ff/9e979329dd131aa73a438c077252ddabd7df6d1a7ad7b9aacf6261f10faa/rpds_py-0.26.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:238e8c8610cb7c29460e37184f6799547f7e09e6a9bdbdab4e8edb90986a2318", size = 554363, upload-time = "2025-07-01T15:54:11.073Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/00/8b/d78cfe034b71ffbe72873a136e71acc7a831a03e37771cfe59f33f6de8a2/rpds_py-0.26.0-cp311-cp311-win32.whl", hash = "sha256:893b022bfbdf26d7bedb083efeea624e8550ca6eb98bf7fea30211ce95b9201a", size = 220123, upload-time = "2025-07-01T15:54:12.382Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/94/c1/3c8c94c7dd3905dbfde768381ce98778500a80db9924731d87ddcdb117e9/rpds_py-0.26.0-cp311-cp311-win_amd64.whl", hash = "sha256:87a5531de9f71aceb8af041d72fc4cab4943648d91875ed56d2e629bef6d4c03", size = 231732, upload-time = "2025-07-01T15:54:13.434Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/67/93/e936fbed1b734eabf36ccb5d93c6a2e9246fbb13c1da011624b7286fae3e/rpds_py-0.26.0-cp311-cp311-win_arm64.whl", hash = "sha256:de2713f48c1ad57f89ac25b3cb7daed2156d8e822cf0eca9b96a6f990718cc41", size = 221917, upload-time = "2025-07-01T15:54:14.559Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ea/86/90eb87c6f87085868bd077c7a9938006eb1ce19ed4d06944a90d3560fce2/rpds_py-0.26.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:894514d47e012e794f1350f076c427d2347ebf82f9b958d554d12819849a369d", size = 363933, upload-time = "2025-07-01T15:54:15.734Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/63/78/4469f24d34636242c924626082b9586f064ada0b5dbb1e9d096ee7a8e0c6/rpds_py-0.26.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc921b96fa95a097add244da36a1d9e4f3039160d1d30f1b35837bf108c21136", size = 350447, upload-time = "2025-07-01T15:54:16.922Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ad/91/c448ed45efdfdade82348d5e7995e15612754826ea640afc20915119734f/rpds_py-0.26.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e1157659470aa42a75448b6e943c895be8c70531c43cb78b9ba990778955582", size = 384711, upload-time = "2025-07-01T15:54:18.101Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ec/43/e5c86fef4be7f49828bdd4ecc8931f0287b1152c0bb0163049b3218740e7/rpds_py-0.26.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:521ccf56f45bb3a791182dc6b88ae5f8fa079dd705ee42138c76deb1238e554e", size = 400865, upload-time = "2025-07-01T15:54:19.295Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/55/34/e00f726a4d44f22d5c5fe2e5ddd3ac3d7fd3f74a175607781fbdd06fe375/rpds_py-0.26.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9def736773fd56b305c0eef698be5192c77bfa30d55a0e5885f80126c4831a15", size = 517763, upload-time = "2025-07-01T15:54:20.858Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/52/1c/52dc20c31b147af724b16104500fba13e60123ea0334beba7b40e33354b4/rpds_py-0.26.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cdad4ea3b4513b475e027be79e5a0ceac8ee1c113a1a11e5edc3c30c29f964d8", size = 406651, upload-time = "2025-07-01T15:54:22.508Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2e/77/87d7bfabfc4e821caa35481a2ff6ae0b73e6a391bb6b343db2c91c2b9844/rpds_py-0.26.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82b165b07f416bdccf5c84546a484cc8f15137ca38325403864bfdf2b5b72f6a", size = 386079, upload-time = "2025-07-01T15:54:23.987Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e3/d4/7f2200c2d3ee145b65b3cddc4310d51f7da6a26634f3ac87125fd789152a/rpds_py-0.26.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d04cab0a54b9dba4d278fe955a1390da3cf71f57feb78ddc7cb67cbe0bd30323", size = 421379, upload-time = "2025-07-01T15:54:25.073Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ae/13/9fdd428b9c820869924ab62236b8688b122baa22d23efdd1c566938a39ba/rpds_py-0.26.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:79061ba1a11b6a12743a2b0f72a46aa2758613d454aa6ba4f5a265cc48850158", size = 562033, upload-time = "2025-07-01T15:54:26.225Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f3/e1/b69686c3bcbe775abac3a4c1c30a164a2076d28df7926041f6c0eb5e8d28/rpds_py-0.26.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f405c93675d8d4c5ac87364bb38d06c988e11028a64b52a47158a355079661f3", size = 591639, upload-time = "2025-07-01T15:54:27.424Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5c/c9/1e3d8c8863c84a90197ac577bbc3d796a92502124c27092413426f670990/rpds_py-0.26.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dafd4c44b74aa4bed4b250f1aed165b8ef5de743bcca3b88fc9619b6087093d2", size = 557105, upload-time = "2025-07-01T15:54:29.93Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9f/c5/90c569649057622959f6dcc40f7b516539608a414dfd54b8d77e3b201ac0/rpds_py-0.26.0-cp312-cp312-win32.whl", hash = "sha256:3da5852aad63fa0c6f836f3359647870e21ea96cf433eb393ffa45263a170d44", size = 223272, upload-time = "2025-07-01T15:54:31.128Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7d/16/19f5d9f2a556cfed454eebe4d354c38d51c20f3db69e7b4ce6cff904905d/rpds_py-0.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:cf47cfdabc2194a669dcf7a8dbba62e37a04c5041d2125fae0233b720da6f05c", size = 234995, upload-time = "2025-07-01T15:54:32.195Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/83/f0/7935e40b529c0e752dfaa7880224771b51175fce08b41ab4a92eb2fbdc7f/rpds_py-0.26.0-cp312-cp312-win_arm64.whl", hash = "sha256:20ab1ae4fa534f73647aad289003f1104092890849e0266271351922ed5574f8", size = 223198, upload-time = "2025-07-01T15:54:33.271Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ef/9a/1f033b0b31253d03d785b0cd905bc127e555ab496ea6b4c7c2e1f951f2fd/rpds_py-0.26.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3c0909c5234543ada2515c05dc08595b08d621ba919629e94427e8e03539c958", size = 373226, upload-time = "2025-07-01T15:56:16.578Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/58/29/5f88023fd6aaaa8ca3c4a6357ebb23f6f07da6079093ccf27c99efce87db/rpds_py-0.26.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:c1fb0cda2abcc0ac62f64e2ea4b4e64c57dfd6b885e693095460c61bde7bb18e", size = 359230, upload-time = "2025-07-01T15:56:17.978Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6c/6c/13eaebd28b439da6964dde22712b52e53fe2824af0223b8e403249d10405/rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84d142d2d6cf9b31c12aa4878d82ed3b2324226270b89b676ac62ccd7df52d08", size = 382363, upload-time = "2025-07-01T15:56:19.977Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/55/fc/3bb9c486b06da19448646f96147796de23c5811ef77cbfc26f17307b6a9d/rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a547e21c5610b7e9093d870be50682a6a6cf180d6da0f42c47c306073bfdbbf6", size = 397146, upload-time = "2025-07-01T15:56:21.39Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/15/18/9d1b79eb4d18e64ba8bba9e7dec6f9d6920b639f22f07ee9368ca35d4673/rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:35e9a70a0f335371275cdcd08bc5b8051ac494dd58bff3bbfb421038220dc871", size = 514804, upload-time = "2025-07-01T15:56:22.78Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4f/5a/175ad7191bdbcd28785204621b225ad70e85cdfd1e09cc414cb554633b21/rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0dfa6115c6def37905344d56fb54c03afc49104e2ca473d5dedec0f6606913b4", size = 402820, upload-time = "2025-07-01T15:56:24.584Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/11/45/6a67ecf6d61c4d4aff4bc056e864eec4b2447787e11d1c2c9a0242c6e92a/rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:313cfcd6af1a55a286a3c9a25f64af6d0e46cf60bc5798f1db152d97a216ff6f", size = 384567, upload-time = "2025-07-01T15:56:26.064Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a1/ba/16589da828732b46454c61858950a78fe4c931ea4bf95f17432ffe64b241/rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f7bf2496fa563c046d05e4d232d7b7fd61346e2402052064b773e5c378bf6f73", size = 416520, upload-time = "2025-07-01T15:56:27.608Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/81/4b/00092999fc7c0c266045e984d56b7314734cc400a6c6dc4d61a35f135a9d/rpds_py-0.26.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:aa81873e2c8c5aa616ab8e017a481a96742fdf9313c40f14338ca7dbf50cb55f", size = 559362, upload-time = "2025-07-01T15:56:29.078Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/96/0c/43737053cde1f93ac4945157f7be1428724ab943e2132a0d235a7e161d4e/rpds_py-0.26.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:68ffcf982715f5b5b7686bdd349ff75d422e8f22551000c24b30eaa1b7f7ae84", size = 588113, upload-time = "2025-07-01T15:56:30.485Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/46/46/8e38f6161466e60a997ed7e9951ae5de131dedc3cf778ad35994b4af823d/rpds_py-0.26.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:6188de70e190847bb6db3dc3981cbadff87d27d6fe9b4f0e18726d55795cee9b", size = 555429, upload-time = "2025-07-01T15:56:31.956Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2c/ac/65da605e9f1dd643ebe615d5bbd11b6efa1d69644fc4bf623ea5ae385a82/rpds_py-0.26.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1c962145c7473723df9722ba4c058de12eb5ebedcb4e27e7d902920aa3831ee8", size = 231950, upload-time = "2025-07-01T15:56:33.337Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/51/f2/b5c85b758a00c513bb0389f8fc8e61eb5423050c91c958cdd21843faa3e6/rpds_py-0.26.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f61a9326f80ca59214d1cceb0a09bb2ece5b2563d4e0cd37bfd5515c28510674", size = 373505, upload-time = "2025-07-01T15:56:34.716Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/23/e0/25db45e391251118e915e541995bb5f5ac5691a3b98fb233020ba53afc9b/rpds_py-0.26.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:183f857a53bcf4b1b42ef0f57ca553ab56bdd170e49d8091e96c51c3d69ca696", size = 359468, upload-time = "2025-07-01T15:56:36.219Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0b/73/dd5ee6075bb6491be3a646b301dfd814f9486d924137a5098e61f0487e16/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:941c1cfdf4799d623cf3aa1d326a6b4fdb7a5799ee2687f3516738216d2262fb", size = 382680, upload-time = "2025-07-01T15:56:37.644Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2f/10/84b522ff58763a5c443f5bcedc1820240e454ce4e620e88520f04589e2ea/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72a8d9564a717ee291f554eeb4bfeafe2309d5ec0aa6c475170bdab0f9ee8e88", size = 397035, upload-time = "2025-07-01T15:56:39.241Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/06/ea/8667604229a10a520fcbf78b30ccc278977dcc0627beb7ea2c96b3becef0/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:511d15193cbe013619dd05414c35a7dedf2088fcee93c6bbb7c77859765bd4e8", size = 514922, upload-time = "2025-07-01T15:56:40.645Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/24/e6/9ed5b625c0661c4882fc8cdf302bf8e96c73c40de99c31e0b95ed37d508c/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aea1f9741b603a8d8fedb0ed5502c2bc0accbc51f43e2ad1337fe7259c2b77a5", size = 402822, upload-time = "2025-07-01T15:56:42.137Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8a/58/212c7b6fd51946047fb45d3733da27e2fa8f7384a13457c874186af691b1/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4019a9d473c708cf2f16415688ef0b4639e07abaa569d72f74745bbeffafa2c7", size = 384336, upload-time = "2025-07-01T15:56:44.239Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/aa/f5/a40ba78748ae8ebf4934d4b88e77b98497378bc2c24ba55ebe87a4e87057/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:093d63b4b0f52d98ebae33b8c50900d3d67e0666094b1be7a12fffd7f65de74b", size = 416871, upload-time = "2025-07-01T15:56:46.284Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d5/a6/33b1fc0c9f7dcfcfc4a4353daa6308b3ece22496ceece348b3e7a7559a09/rpds_py-0.26.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:2abe21d8ba64cded53a2a677e149ceb76dcf44284202d737178afe7ba540c1eb", size = 559439, upload-time = "2025-07-01T15:56:48.549Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/71/2d/ceb3f9c12f8cfa56d34995097f6cd99da1325642c60d1b6680dd9df03ed8/rpds_py-0.26.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:4feb7511c29f8442cbbc28149a92093d32e815a28aa2c50d333826ad2a20fdf0", size = 588380, upload-time = "2025-07-01T15:56:50.086Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c8/ed/9de62c2150ca8e2e5858acf3f4f4d0d180a38feef9fdab4078bea63d8dba/rpds_py-0.26.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:e99685fc95d386da368013e7fb4269dd39c30d99f812a8372d62f244f662709c", size = 555334, upload-time = "2025-07-01T15:56:51.703Z" }, +] + +[[package]] +name = "rsa" +version = "4.9.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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 = "rtfde" +version = "0.0.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "lark-parser" }, + { name = "oletools" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/81/ea/28f5ab6b46a072887c8c8fd8c8a1f7b54025fc4bb2e09024668ea6686044/RTFDE-0.0.2.tar.gz", hash = "sha256:b86b5d734950fe8745a5b89133f50554252dbd67c6d1b9265e23ee140e7ea8a2", size = 18891, upload-time = "2020-12-28T15:15:35.981Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5d/3f/39ba5a72620c43656bc80cb1f7afe0d498df4a48947d75ea0ca0752ffbf4/RTFDE-0.0.2-py3-none-any.whl", hash = "sha256:18386e4f060cee12a2a8035b0acf0cc99689f5dff1bf347bab7e92351860a21d", size = 34626, upload-time = "2020-12-28T15:15:35Z" }, +] + +[[package]] +name = "ruamel-base" +version = "1.0.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ea/77/60a0945f4b4eac4b6bd74d1b8e103ae58d0f07b934f962bb4c49e6ec205e/ruamel.base-1.0.0.tar.gz", hash = "sha256:c041333a0f0f00cd6593eb36aa83abb1a9e7544e83ba7a42aa7ac7476cee5cf3", size = 5219, upload-time = "2015-08-27T15:26:52.744Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1f/e1/5257f5d1636a26fdb50cdcc0e7e5e65d230b88c2dd5090ac797b9e45d1d3/ruamel.base-1.0.0-py3-none-any.whl", hash = "sha256:3613a90afcf0735540804af2a693f630a0bccebefec9b4023a39e88950bb294e", size = 4385, upload-time = "2015-08-27T17:22:13.538Z" }, +] + +[[package]] +name = "ruamel-yaml" +version = "0.18.14" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "ruamel-yaml-clib", marker = "platform_python_implementation == 'CPython'" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/39/87/6da0df742a4684263261c253f00edd5829e6aca970fff69e75028cccc547/ruamel.yaml-0.18.14.tar.gz", hash = "sha256:7227b76aaec364df15936730efbf7d72b30c0b79b1d578bbb8e3dcb2d81f52b7", size = 145511, upload-time = "2025-06-09T08:51:09.828Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/af/6d/6fe4805235e193aad4aaf979160dd1f3c487c57d48b810c816e6e842171b/ruamel.yaml-0.18.14-py3-none-any.whl", hash = "sha256:710ff198bb53da66718c7db27eec4fbcc9aa6ca7204e4c1df2f282b6fe5eb6b2", size = 118570, upload-time = "2025-06-09T08:51:06.348Z" }, +] + +[[package]] +name = "ruamel-yaml-clib" +version = "0.2.12" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/20/84/80203abff8ea4993a87d823a5f632e4d92831ef75d404c9fc78d0176d2b5/ruamel.yaml.clib-0.2.12.tar.gz", hash = "sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f", size = 225315, upload-time = "2024-10-20T10:10:56.22Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/70/57/40a958e863e299f0c74ef32a3bde9f2d1ea8d69669368c0c502a0997f57f/ruamel.yaml.clib-0.2.12-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:11f891336688faf5156a36293a9c362bdc7c88f03a8a027c2c1d8e0bcde998e5", size = 131301, upload-time = "2024-10-20T10:12:35.876Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/98/a8/29a3eb437b12b95f50a6bcc3d7d7214301c6c529d8fdc227247fa84162b5/ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:a606ef75a60ecf3d924613892cc603b154178ee25abb3055db5062da811fd969", size = 633728, upload-time = "2024-10-20T10:12:37.858Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/35/6d/ae05a87a3ad540259c3ad88d71275cbd1c0f2d30ae04c65dcbfb6dcd4b9f/ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd5415dded15c3822597455bc02bcd66e81ef8b7a48cb71a33628fc9fdde39df", size = 722230, upload-time = "2024-10-20T10:12:39.457Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7f/b7/20c6f3c0b656fe609675d69bc135c03aac9e3865912444be6339207b6648/ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f66efbc1caa63c088dead1c4170d148eabc9b80d95fb75b6c92ac0aad2437d76", size = 686712, upload-time = "2024-10-20T10:12:41.119Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cd/11/d12dbf683471f888d354dac59593873c2b45feb193c5e3e0f2ebf85e68b9/ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22353049ba4181685023b25b5b51a574bce33e7f51c759371a7422dcae5402a6", size = 663936, upload-time = "2024-10-21T11:26:37.419Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/72/14/4c268f5077db5c83f743ee1daeb236269fa8577133a5cfa49f8b382baf13/ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:932205970b9f9991b34f55136be327501903f7c66830e9760a8ffb15b07f05cd", size = 696580, upload-time = "2024-10-21T11:26:39.503Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/30/fc/8cd12f189c6405a4c1cf37bd633aa740a9538c8e40497c231072d0fef5cf/ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a52d48f4e7bf9005e8f0a89209bf9a73f7190ddf0489eee5eb51377385f59f2a", size = 663393, upload-time = "2024-12-11T19:58:13.873Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/80/29/c0a017b704aaf3cbf704989785cd9c5d5b8ccec2dae6ac0c53833c84e677/ruamel.yaml.clib-0.2.12-cp310-cp310-win32.whl", hash = "sha256:3eac5a91891ceb88138c113f9db04f3cebdae277f5d44eaa3651a4f573e6a5da", size = 100326, upload-time = "2024-10-20T10:12:42.967Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3a/65/fa39d74db4e2d0cd252355732d966a460a41cd01c6353b820a0952432839/ruamel.yaml.clib-0.2.12-cp310-cp310-win_amd64.whl", hash = "sha256:ab007f2f5a87bd08ab1499bdf96f3d5c6ad4dcfa364884cb4549aa0154b13a28", size = 118079, upload-time = "2024-10-20T10:12:44.117Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fb/8f/683c6ad562f558cbc4f7c029abcd9599148c51c54b5ef0f24f2638da9fbb/ruamel.yaml.clib-0.2.12-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:4a6679521a58256a90b0d89e03992c15144c5f3858f40d7c18886023d7943db6", size = 132224, upload-time = "2024-10-20T10:12:45.162Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3c/d2/b79b7d695e2f21da020bd44c782490578f300dd44f0a4c57a92575758a76/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:d84318609196d6bd6da0edfa25cedfbabd8dbde5140a0a23af29ad4b8f91fb1e", size = 641480, upload-time = "2024-10-20T10:12:46.758Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/68/6e/264c50ce2a31473a9fdbf4fa66ca9b2b17c7455b31ef585462343818bd6c/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb43a269eb827806502c7c8efb7ae7e9e9d0573257a46e8e952f4d4caba4f31e", size = 739068, upload-time = "2024-10-20T10:12:48.605Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/86/29/88c2567bc893c84d88b4c48027367c3562ae69121d568e8a3f3a8d363f4d/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52", size = 703012, upload-time = "2024-10-20T10:12:51.124Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/11/46/879763c619b5470820f0cd6ca97d134771e502776bc2b844d2adb6e37753/ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642", size = 704352, upload-time = "2024-10-21T11:26:41.438Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/02/80/ece7e6034256a4186bbe50dee28cd032d816974941a6abf6a9d65e4228a7/ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2", size = 737344, upload-time = "2024-10-21T11:26:43.62Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f0/ca/e4106ac7e80efbabdf4bf91d3d32fc424e41418458251712f5672eada9ce/ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1492a6051dab8d912fc2adeef0e8c72216b24d57bd896ea607cb90bb0c4981d3", size = 714498, upload-time = "2024-12-11T19:58:15.592Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/67/58/b1f60a1d591b771298ffa0428237afb092c7f29ae23bad93420b1eb10703/ruamel.yaml.clib-0.2.12-cp311-cp311-win32.whl", hash = "sha256:bd0a08f0bab19093c54e18a14a10b4322e1eacc5217056f3c063bd2f59853ce4", size = 100205, upload-time = "2024-10-20T10:12:52.865Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b4/4f/b52f634c9548a9291a70dfce26ca7ebce388235c93588a1068028ea23fcc/ruamel.yaml.clib-0.2.12-cp311-cp311-win_amd64.whl", hash = "sha256:a274fb2cb086c7a3dea4322ec27f4cb5cc4b6298adb583ab0e211a4682f241eb", size = 118185, upload-time = "2024-10-20T10:12:54.652Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/48/41/e7a405afbdc26af961678474a55373e1b323605a4f5e2ddd4a80ea80f628/ruamel.yaml.clib-0.2.12-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:20b0f8dc160ba83b6dcc0e256846e1a02d044e13f7ea74a3d1d56ede4e48c632", size = 133433, upload-time = "2024-10-20T10:12:55.657Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ec/b0/b850385604334c2ce90e3ee1013bd911aedf058a934905863a6ea95e9eb4/ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:943f32bc9dedb3abff9879edc134901df92cfce2c3d5c9348f172f62eb2d771d", size = 647362, upload-time = "2024-10-20T10:12:57.155Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/44/d0/3f68a86e006448fb6c005aee66565b9eb89014a70c491d70c08de597f8e4/ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95c3829bb364fdb8e0332c9931ecf57d9be3519241323c5274bd82f709cebc0c", size = 754118, upload-time = "2024-10-20T10:12:58.501Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/52/a9/d39f3c5ada0a3bb2870d7db41901125dbe2434fa4f12ca8c5b83a42d7c53/ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:749c16fcc4a2b09f28843cda5a193e0283e47454b63ec4b81eaa2242f50e4ccd", size = 706497, upload-time = "2024-10-20T10:13:00.211Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b0/fa/097e38135dadd9ac25aecf2a54be17ddf6e4c23e43d538492a90ab3d71c6/ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bf165fef1f223beae7333275156ab2022cffe255dcc51c27f066b4370da81e31", size = 698042, upload-time = "2024-10-21T11:26:46.038Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ec/d5/a659ca6f503b9379b930f13bc6b130c9f176469b73b9834296822a83a132/ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:32621c177bbf782ca5a18ba4d7af0f1082a3f6e517ac2a18b3974d4edf349680", size = 745831, upload-time = "2024-10-21T11:26:47.487Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/db/5d/36619b61ffa2429eeaefaab4f3374666adf36ad8ac6330d855848d7d36fd/ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b82a7c94a498853aa0b272fd5bc67f29008da798d4f93a2f9f289feb8426a58d", size = 715692, upload-time = "2024-12-11T19:58:17.252Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b1/82/85cb92f15a4231c89b95dfe08b09eb6adca929ef7df7e17ab59902b6f589/ruamel.yaml.clib-0.2.12-cp312-cp312-win32.whl", hash = "sha256:e8c4ebfcfd57177b572e2040777b8abc537cdef58a2120e830124946aa9b42c5", size = 98777, upload-time = "2024-10-20T10:13:01.395Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d7/8f/c3654f6f1ddb75daf3922c3d8fc6005b1ab56671ad56ffb874d908bfa668/ruamel.yaml.clib-0.2.12-cp312-cp312-win_amd64.whl", hash = "sha256:0467c5965282c62203273b838ae77c0d29d7638c8a4e3a1c8bdd3602c10904e4", size = 115523, upload-time = "2024-10-20T10:13:02.768Z" }, +] + +[[package]] +name = "s3transfer" +version = "0.10.4" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "botocore" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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 = "safetensors" +version = "0.5.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/71/7e/2d5d6ee7b40c0682315367ec7475693d110f512922d582fef1bd4a63adc3/safetensors-0.5.3.tar.gz", hash = "sha256:b6b0d6ecacec39a4fdd99cc19f4576f5219ce858e6fd8dbe7609df0b8dc56965", size = 67210, upload-time = "2025-02-26T09:15:13.155Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/18/ae/88f6c49dbd0cc4da0e08610019a3c78a7d390879a919411a410a1876d03a/safetensors-0.5.3-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:bd20eb133db8ed15b40110b7c00c6df51655a2998132193de2f75f72d99c7073", size = 436917, upload-time = "2025-02-26T09:15:03.702Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b8/3b/11f1b4a2f5d2ab7da34ecc062b0bc301f2be024d110a6466726bec8c055c/safetensors-0.5.3-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:21d01c14ff6c415c485616b8b0bf961c46b3b343ca59110d38d744e577f9cce7", size = 418419, upload-time = "2025-02-26T09:15:01.765Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5d/9a/add3e6fef267658075c5a41573c26d42d80c935cdc992384dfae435feaef/safetensors-0.5.3-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11bce6164887cd491ca75c2326a113ba934be596e22b28b1742ce27b1d076467", size = 459493, upload-time = "2025-02-26T09:14:51.812Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/df/5c/bf2cae92222513cc23b3ff85c4a1bb2811a2c3583ac0f8e8d502751de934/safetensors-0.5.3-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4a243be3590bc3301c821da7a18d87224ef35cbd3e5f5727e4e0728b8172411e", size = 472400, upload-time = "2025-02-26T09:14:53.549Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/58/11/7456afb740bd45782d0f4c8e8e1bb9e572f1bf82899fb6ace58af47b4282/safetensors-0.5.3-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8bd84b12b1670a6f8e50f01e28156422a2bc07fb16fc4e98bded13039d688a0d", size = 522891, upload-time = "2025-02-26T09:14:55.717Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/57/3d/fe73a9d2ace487e7285f6e157afee2383bd1ddb911b7cb44a55cf812eae3/safetensors-0.5.3-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:391ac8cab7c829452175f871fcaf414aa1e292b5448bd02620f675a7f3e7abb9", size = 537694, upload-time = "2025-02-26T09:14:57.036Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a6/f8/dae3421624fcc87a89d42e1898a798bc7ff72c61f38973a65d60df8f124c/safetensors-0.5.3-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cead1fa41fc54b1e61089fa57452e8834f798cb1dc7a09ba3524f1eb08e0317a", size = 471642, upload-time = "2025-02-26T09:15:00.544Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ce/20/1fbe16f9b815f6c5a672f5b760951e20e17e43f67f231428f871909a37f6/safetensors-0.5.3-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1077f3e94182d72618357b04b5ced540ceb71c8a813d3319f1aba448e68a770d", size = 502241, upload-time = "2025-02-26T09:14:58.303Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5f/18/8e108846b506487aa4629fe4116b27db65c3dde922de2c8e0cc1133f3f29/safetensors-0.5.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:799021e78287bac619c7b3f3606730a22da4cda27759ddf55d37c8db7511c74b", size = 638001, upload-time = "2025-02-26T09:15:05.79Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/82/5a/c116111d8291af6c8c8a8b40628fe833b9db97d8141c2a82359d14d9e078/safetensors-0.5.3-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:df26da01aaac504334644e1b7642fa000bfec820e7cef83aeac4e355e03195ff", size = 734013, upload-time = "2025-02-26T09:15:07.892Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7d/ff/41fcc4d3b7de837963622e8610d998710705bbde9a8a17221d85e5d0baad/safetensors-0.5.3-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:32c3ef2d7af8b9f52ff685ed0bc43913cdcde135089ae322ee576de93eae5135", size = 670687, upload-time = "2025-02-26T09:15:09.979Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/40/ad/2b113098e69c985a3d8fbda4b902778eae4a35b7d5188859b4a63d30c161/safetensors-0.5.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:37f1521be045e56fc2b54c606d4455573e717b2d887c579ee1dbba5f868ece04", size = 643147, upload-time = "2025-02-26T09:15:11.185Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0a/0c/95aeb51d4246bd9a3242d3d8349c1112b4ee7611a4b40f0c5c93b05f001d/safetensors-0.5.3-cp38-abi3-win32.whl", hash = "sha256:cfc0ec0846dcf6763b0ed3d1846ff36008c6e7290683b61616c4b040f6a54ace", size = 296677, upload-time = "2025-02-26T09:15:16.554Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/69/e2/b011c38e5394c4c18fb5500778a55ec43ad6106126e74723ffaee246f56e/safetensors-0.5.3-cp38-abi3-win_amd64.whl", hash = "sha256:836cbbc320b47e80acd40e44c8682db0e8ad7123209f69b093def21ec7cafd11", size = 308878, upload-time = "2025-02-26T09:15:14.99Z" }, +] + +[[package]] +name = "scholarly" +version = "1.7.11" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "arrow" }, + { name = "beautifulsoup4" }, + { name = "bibtexparser" }, + { name = "deprecated" }, + { name = "fake-useragent" }, + { name = "free-proxy" }, + { name = "httpx" }, + { name = "python-dotenv" }, + { name = "requests", extra = ["socks"] }, + { name = "selenium" }, + { name = "sphinx-rtd-theme" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/62/2d/38f22adc8abc1166d2c039e477bd8d7782fe32a72f5c80aed94b23348ac1/scholarly-1.7.11.tar.gz", hash = "sha256:2c983dd44d9d9398a6f2605102ae6e5586023b41ebbaec1461917ee48eb153f0", size = 38819, upload-time = "2023-01-16T22:01:00.087Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b6/23/4340a9068b451b7bb03ff02243bd7aea4c1869781f41e2387c9348629edd/scholarly-1.7.11-py3-none-any.whl", hash = "sha256:be404853e0d020254de32d2050c54dc201f1f36efa4a9d3f8e740d3be4361b20", size = 39380, upload-time = "2023-01-16T22:00:57.549Z" }, +] + +[[package]] +name = "scikit-learn" +version = "1.5.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "joblib" }, + { name = "numpy" }, + { name = "scipy" }, + { name = "threadpoolctl" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bf/8a/06e499bca463905000f50e461c9445e949aafdd33ea3b62024aa2238b83d/scikit_learn-1.5.0.tar.gz", hash = "sha256:789e3db01c750ed6d496fa2db7d50637857b451e57bcae863bff707c1247bef7", size = 7820839, upload-time = "2024-05-21T16:34:07.711Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/87/d1/900698985c526e4c06c03028b9272993f248ae43a739a2f30a91d8f1a5af/scikit_learn-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:12e40ac48555e6b551f0a0a5743cc94cc5a765c9513fe708e01f0aa001da2801", size = 12096286, upload-time = "2024-05-21T16:33:03.119Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/91/e5/42e5bf73aeadc0f32152de33593f3f97ae8a59bb4c46d7725e7d3d76f4c4/scikit_learn-1.5.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:f405c4dae288f5f6553b10c4ac9ea7754d5180ec11e296464adb5d6ac68b6ef5", size = 10969691, upload-time = "2024-05-21T16:33:07.004Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/48/12/a31235236e7c62f5f7b68ea471fb82bac818be5b40f608a51a44fa8388bf/scikit_learn-1.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df8ccabbf583315f13160a4bb06037bde99ea7d8211a69787a6b7c5d4ebb6fc3", size = 12490175, upload-time = "2024-05-21T16:33:10.22Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1e/7d/1a2ea8eb5b4df373c30c7418cf26305a4a05e2a0e56c80a8043b791595f3/scikit_learn-1.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c75ea812cd83b1385bbfa94ae971f0d80adb338a9523f6bbcb5e0b0381151d4", size = 13343697, upload-time = "2024-05-21T16:33:13.68Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c9/1a/7313c0d70ec8bfcf83cdd49696679d54d9d1a062a60fba270e7b4fc457f2/scikit_learn-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:a90c5da84829a0b9b4bf00daf62754b2be741e66b5946911f5bdfaa869fcedd6", size = 10962967, upload-time = "2024-05-21T16:33:16.646Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/50/d4/70a9393ab88862c070a263a464042ab4e572a1353b4c3c308bc72a5b68cf/scikit_learn-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2a65af2d8a6cce4e163a7951a4cfbfa7fceb2d5c013a4b593686c7f16445cf9d", size = 12081437, upload-time = "2024-05-21T16:33:20.16Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6c/97/dfc635bd435655c1216756b543e0427579df144914a055a188d3c0ffd52f/scikit_learn-1.5.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:4c0c56c3005f2ec1db3787aeaabefa96256580678cec783986836fc64f8ff622", size = 10973963, upload-time = "2024-05-21T16:33:22.801Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/61/f5/18dc69d22ec950225237d42b61d3338affc46e5ea63c27c6915f3678f5f2/scikit_learn-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f77547165c00625551e5c250cefa3f03f2fc92c5e18668abd90bfc4be2e0bff", size = 12480861, upload-time = "2024-05-21T16:33:26.131Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/46/c0/63d3a8da39a2ee051df229111aa93f6dca2b56f8080abd34993938166455/scikit_learn-1.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:118a8d229a41158c9f90093e46b3737120a165181a1b58c03461447aa4657415", size = 13328661, upload-time = "2024-05-21T16:33:29.627Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ae/20/6d1a0a61d468b37a142fd90bb93c73bc1c2205db4a69ac630ed218c31612/scikit_learn-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:a03b09f9f7f09ffe8c5efffe2e9de1196c696d811be6798ad5eddf323c6f4d40", size = 10972963, upload-time = "2024-05-21T16:33:32.98Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1e/21/fe8e90eb7dc796ed384daaf45a83e729a41fa7a9bf14bc1a0b69fd05b39a/scikit_learn-1.5.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:460806030c666addee1f074788b3978329a5bfdc9b7d63e7aad3f6d45c67a210", size = 12096541, upload-time = "2024-05-21T16:33:36.475Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f9/4b/c035ce6771dd56283cd587e941054ebb38a14868729e28a0f7c6c9ff9ebd/scikit_learn-1.5.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:1b94d6440603752b27842eda97f6395f570941857456c606eb1d638efdb38184", size = 11031507, upload-time = "2024-05-21T16:33:39.896Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/66/a1/e64f125382f2fc46dd1f3a3c2d390f02db896e3803a3e7898c4ca48390e0/scikit_learn-1.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d82c2e573f0f2f2f0be897e7a31fcf4e73869247738ab8c3ce7245549af58ab8", size = 12082985, upload-time = "2024-05-21T16:33:42.807Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ae/54/e70102a9c12d27d985ba659f336851732415e5a02864bef2ead36afaf15d/scikit_learn-1.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3a10e1d9e834e84d05e468ec501a356226338778769317ee0b84043c0d8fb06", size = 13065320, upload-time = "2024-05-21T16:33:45.65Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/57/ed/f607ebf69f87bcce2e3fa329bd78da8cafd3d51190a19d58012d2d7f2252/scikit_learn-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:855fc5fa8ed9e4f08291203af3d3e5fbdc4737bd617a371559aaa2088166046e", size = 10938084, upload-time = "2024-05-21T16:33:49.011Z" }, +] + +[[package]] +name = "scipy" +version = "1.12.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/30/85/cdbf2c3c460fe5aae812917866392068a88d02f07de0fe31ce738734c477/scipy-1.12.0.tar.gz", hash = "sha256:4bf5abab8a36d20193c698b0f1fc282c1d083c94723902c447e5d2f1780936a3", size = 56811768, upload-time = "2024-01-20T21:13:43.442Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c7/d9/214971dae573bd7e9303b56d2612dae439decbfc0dae0f539a591c0562ce/scipy-1.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:78e4402e140879387187f7f25d91cc592b3501a2e51dfb320f48dfb73565f10b", size = 38900384, upload-time = "2024-01-20T21:10:31.498Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/dd/14/549fd7066a112c4bdf1cc11228d11284bc784ea09124fc4d663f28815564/scipy-1.12.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:f5f00ebaf8de24d14b8449981a2842d404152774c1a1d880c901bf454cb8e2a1", size = 31357553, upload-time = "2024-01-20T21:10:38.509Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/69/1d/0582401b6d77865e080c90f39e52f65ca2bdc94e668e0bfbed8977dae3f4/scipy-1.12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e53958531a7c695ff66c2e7bb7b79560ffdc562e2051644c5576c39ff8efb563", size = 34789974, upload-time = "2024-01-20T21:10:45.054Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f5/aa/8e6071a5e4dca4ec68b5b22e4991ee74c59c5d372112b9c236ec1faff57d/scipy-1.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e32847e08da8d895ce09d108a494d9eb78974cf6de23063f93306a3e419960c", size = 38441046, upload-time = "2024-01-20T21:10:51.285Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/65/9e/43b86ec57ecdc9931b43aaf727f9d71743bfd06bdddfd441165bd3d8c6be/scipy-1.12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4c1020cad92772bf44b8e4cdabc1df5d87376cb219742549ef69fc9fd86282dd", size = 38630107, upload-time = "2024-01-20T21:10:58.406Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fd/a7/5f829b100d208c85163aecba93faf01d088d944fc91585338751d812f1e4/scipy-1.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:75ea2a144096b5e39402e2ff53a36fecfd3b960d786b7efd3c180e29c39e53f2", size = 46191228, upload-time = "2024-01-20T21:11:05.92Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c3/32/7915195ca4643508fe9730691eaed57b879646279572b10b02bdadf165c5/scipy-1.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:408c68423f9de16cb9e602528be4ce0d6312b05001f3de61fe9ec8b1263cad08", size = 38908720, upload-time = "2024-01-20T21:11:13.467Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/21/d4/e6c57acc61e59cd46acca27af1f400094d5dee218e372cc604b8162b97cb/scipy-1.12.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:5adfad5dbf0163397beb4aca679187d24aec085343755fcdbdeb32b3679f254c", size = 31392892, upload-time = "2024-01-20T21:11:18.947Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e3/c5/d40abc1a857c1c6519e1a4e096d6aee86861eddac019fb736b6af8a58d25/scipy-1.12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3003652496f6e7c387b1cf63f4bb720951cfa18907e998ea551e6de51a04467", size = 34733860, upload-time = "2024-01-20T21:11:26.666Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d4/b8/7169935f9a2ea9e274ad8c21d6133d492079e6ebc3fc69a915c2375616b0/scipy-1.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b8066bce124ee5531d12a74b617d9ac0ea59245246410e19bca549656d9a40a", size = 38418720, upload-time = "2024-01-20T21:11:33.479Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/64/e7/4dbb779d09d1cb757ddbe42cae7c4fe8270497566bb902138d637b04d88c/scipy-1.12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8bee4993817e204d761dba10dbab0774ba5a8612e57e81319ea04d84945375ba", size = 38652247, upload-time = "2024-01-20T21:11:40.229Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9a/25/5b30cb3efc9566f0ebeaeca1976150316353c17031ad7868ef46de5ab8dc/scipy-1.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:a24024d45ce9a675c1fb8494e8e5244efea1c7a09c60beb1eeb80373d0fecc70", size = 46162940, upload-time = "2024-01-20T21:11:47.726Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0d/4a/b2b2cae0c5dfd46361245a67102886ed7188805bdf7044e36fe838bbcf26/scipy-1.12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e7e76cc48638228212c747ada851ef355c2bb5e7f939e10952bc504c11f4e372", size = 38911995, upload-time = "2024-01-20T21:11:54.759Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/71/ba/744bbdd65eb3fce1412dd4633fc425ad39e6b4068b5b158aee1cd3afeb54/scipy-1.12.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:f7ce148dffcd64ade37b2df9315541f9adad6efcaa86866ee7dd5db0c8f041c3", size = 31433326, upload-time = "2024-01-20T21:12:00.295Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/db/fd/81feac476e1ae495b51b8c3636aee1f50a1c5ca2a3557f5b0043d4e2fb02/scipy-1.12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c39f92041f490422924dfdb782527a4abddf4707616e07b021de33467f917bc", size = 34165749, upload-time = "2024-01-20T21:12:06.38Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/11/7d/850bfe9462fff393130519eb54f97d43ad9c280ec4297b4cb98b7c2e96cd/scipy-1.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7ebda398f86e56178c2fa94cad15bf457a218a54a35c2a7b4490b9f9cb2676c", size = 37790844, upload-time = "2024-01-20T21:12:12.826Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7e/7f/504b7b3834d8c9229831c6c58a44943e29a34004eeb34c7ff150add4e001/scipy-1.12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:95e5c750d55cf518c398a8240571b0e0782c2d5a703250872f36eaf737751338", size = 38026369, upload-time = "2024-01-20T21:12:19.69Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f3/31/91a2a3c5eb85d2bfa86d7c98f2df5d77dcdefb3d80ca9f9037ad04393acf/scipy-1.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:e646d8571804a304e1da01040d21577685ce8e2db08ac58e543eaca063453e1c", size = 45816713, upload-time = "2024-01-20T21:12:26.619Z" }, +] + +[[package]] +name = "seaborn" +version = "0.13.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "matplotlib" }, + { name = "numpy" }, + { name = "pandas" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/86/59/a451d7420a77ab0b98f7affa3a1d78a313d2f7281a57afb1a34bae8ab412/seaborn-0.13.2.tar.gz", hash = "sha256:93e60a40988f4d65e9f4885df477e2fdaff6b73a9ded434c1ab356dd57eefff7", size = 1457696, upload-time = "2024-01-25T13:21:52.551Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/83/11/00d3c3dfc25ad54e731d91449895a79e4bf2384dc3ac01809010ba88f6d5/seaborn-0.13.2-py3-none-any.whl", hash = "sha256:636f8336facf092165e27924f223d3c62ca560b1f2bb5dff7ab7fad265361987", size = 294914, upload-time = "2024-01-25T13:21:49.598Z" }, +] + +[[package]] +name = "selenium" +version = "4.22.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "certifi" }, + { name = "trio" }, + { name = "trio-websocket" }, + { name = "typing-extensions" }, + { name = "urllib3", extra = ["socks"] }, + { name = "websocket-client" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/04/93/fe0473c381dddce4db9527cf442d5949460fab4a92713fb5984386054323/selenium-4.22.0.tar.gz", hash = "sha256:903c8c9d61b3eea6fcc9809dc7d9377e04e2ac87709876542cc8f863e482c4ce", size = 9242392, upload-time = "2024-06-20T20:48:05.959Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f2/7d/3a0b9c229d87a189b64c3b8e6d87a970a1ef7875995dc31bd18e65fa1c17/selenium-4.22.0-py3-none-any.whl", hash = "sha256:e424991196e9857e19bf04fe5c1c0a4aac076794ff5e74615b1124e729d93104", size = 9437133, upload-time = "2024-06-20T20:48:01.936Z" }, +] + +[[package]] +name = "selenium-wire" +version = "5.1.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "blinker" }, + { name = "brotli" }, + { name = "certifi" }, + { name = "h2" }, + { name = "hyperframe" }, + { name = "kaitaistruct" }, + { name = "pyasn1" }, + { name = "pydivert", marker = "sys_platform == 'win32'" }, + { name = "pyopenssl" }, + { name = "pyparsing" }, + { name = "pysocks" }, + { name = "selenium" }, + { name = "wsproto" }, + { name = "zstandard" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9f/00/60b39e8a1efe6919d1390f07d84a3eeba4aeae5b829f2f848344c798f783/selenium-wire-5.1.0.tar.gz", hash = "sha256:b1cd4eae44d9959381abe3bb186472520d063c658e279f98555def3d4e6dd29b", size = 62145825, upload-time = "2022-10-15T14:31:11.057Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/eb/7e/9548b365eab16730b6a8da25c6e1f83f3b84fb6092ecd2d4d69933d08a45/selenium_wire-5.1.0-py3-none-any.whl", hash = "sha256:fbf930d9992f8b6d24bb16b3e6221bab596a41f6ae7548270b7d5a92f3402622", size = 239589, upload-time = "2022-10-15T14:31:06.068Z" }, +] + +[[package]] +name = "sentence-transformers" +version = "3.0.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "huggingface-hub" }, + { name = "numpy" }, + { name = "pillow" }, + { name = "scikit-learn" }, + { name = "scipy" }, + { name = "torch" }, + { name = "tqdm" }, + { name = "transformers" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/41/fb/2368f84127920d86330b533792e66b26264e92b729b5c1998aaa33d2e22f/sentence_transformers-3.0.1.tar.gz", hash = "sha256:8a3d2c537cc4d1014ccc20ac92be3d6135420a3bc60ae29a3a8a9b4bb35fbff6", size = 177258, upload-time = "2024-06-07T13:01:12.32Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/58/4b/922436953394e1bfda05e4bf1fe0e80f609770f256c59a9df7a9254f3e0d/sentence_transformers-3.0.1-py3-none-any.whl", hash = "sha256:01050cc4053c49b9f5b78f6980b5a72db3fd3a0abb9169b1792ac83875505ee6", size = 227071, upload-time = "2024-06-07T13:01:10.063Z" }, +] + +[[package]] +name = "setuptools" +version = "75.2.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/07/37/b31be7e4b9f13b59cde9dcaeff112d401d49e0dc5b37ed4a9fc8fb12f409/setuptools-75.2.0.tar.gz", hash = "sha256:753bb6ebf1f465a1912e19ed1d41f403a79173a9acf66a42e7e6aec45c3c16ec", size = 1350308, upload-time = "2024-10-16T10:21:56.437Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/31/2d/90165d51ecd38f9a02c6832198c13a4e48652485e2ccf863ebb942c531b6/setuptools-75.2.0-py3-none-any.whl", hash = "sha256:a7fcb66f68b4d9e8e66b42f9876150a3371558f98fa32222ffaa5bced76406f8", size = 1249825, upload-time = "2024-10-16T10:21:53.64Z" }, +] + +[[package]] +name = "sgmllib3k" +version = "1.0.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9e/bd/3704a8c3e0942d711c1299ebf7b9091930adae6675d7c8f476a7ce48653c/sgmllib3k-1.0.0.tar.gz", hash = "sha256:7868fb1c8bfa764c1ac563d3cf369c381d1325d36124933a726f29fcdaa812e9", size = 5750, upload-time = "2010-08-24T14:33:52.445Z" } + +[[package]] +name = "shapely" +version = "2.0.5" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ad/99/c47247f4d688bbb5346df5ff1de5d9792b6d95cbbb2fd7b71f45901c1878/shapely-2.0.5.tar.gz", hash = "sha256:bff2366bc786bfa6cb353d6b47d0443c570c32776612e527ee47b6df63fcfe32", size = 282188, upload-time = "2024-07-13T10:52:59.762Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e1/7a/06aafb341a411c594d3f8f2733f2f644814facf0e6e947a829840f562d36/shapely-2.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:89d34787c44f77a7d37d55ae821f3a784fa33592b9d217a45053a93ade899375", size = 1448988, upload-time = "2024-07-13T10:51:57.088Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4b/ad/8a9fe3b987058ff4f4ba8a7d7d19894f9952add4b1ba7649597cbba715d6/shapely-2.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:798090b426142df2c5258779c1d8d5734ec6942f778dab6c6c30cfe7f3bf64ff", size = 1282276, upload-time = "2024-07-13T10:52:00.504Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/83/94/bd391727c29dd014d4a24b92c54717de202e2aa6a0d9b5e9320215b11683/shapely-2.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45211276900c4790d6bfc6105cbf1030742da67594ea4161a9ce6812a6721e68", size = 2382885, upload-time = "2024-07-13T19:44:01.741Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a0/b6/8cbd3674fdbd235ef9b82e055b884034ae9526a26a119dd4b7636303cd39/shapely-2.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e119444bc27ca33e786772b81760f2028d930ac55dafe9bc50ef538b794a8e1", size = 2468813, upload-time = "2024-07-13T10:52:02.942Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/79/bb/965b1dba79a63912f5a9deb183bab4f725d5c2e39c5ede1b36041b9547e8/shapely-2.0.5-cp310-cp310-win32.whl", hash = "sha256:9a4492a2b2ccbeaebf181e7310d2dfff4fdd505aef59d6cb0f217607cb042fb3", size = 1294921, upload-time = "2024-07-13T10:52:05.028Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/70/a5/6d171586eb850c7582d2aeab027e1027ace4eb3a6c2e5f05746b8ab039e0/shapely-2.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:1e5cb5ee72f1bc7ace737c9ecd30dc174a5295fae412972d3879bac2e82c8fae", size = 1441040, upload-time = "2024-07-13T10:52:06.856Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/29/3d/0d3ab80860cda6afbce9736fa1f091f452092d344fdd4e3c65e5fe7b1111/shapely-2.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5bbfb048a74cf273db9091ff3155d373020852805a37dfc846ab71dde4be93ec", size = 1448893, upload-time = "2024-07-13T10:52:08.6Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/80/68/6b51b7587547f6bbd0965cf957505a0ebec93510e840572a983003b3a0a9/shapely-2.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93be600cbe2fbaa86c8eb70656369f2f7104cd231f0d6585c7d0aa555d6878b8", size = 1282344, upload-time = "2024-07-13T10:52:10.403Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7e/4e/4e83b9f3d7f0ce523c92bdf3dfe0292738d8ad2b589971390d6205bc843e/shapely-2.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f8e71bb9a46814019f6644c4e2560a09d44b80100e46e371578f35eaaa9da1c", size = 2444118, upload-time = "2024-07-13T19:44:08.876Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ed/a8/c8b0f1a165e161247caf0fc265d61de3c4ea27d7c313c7ebfb1c4f6ddea4/shapely-2.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5251c28a29012e92de01d2e84f11637eb1d48184ee8f22e2df6c8c578d26760", size = 2528510, upload-time = "2024-07-13T10:52:12.592Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/76/04/111f8d42ad50fb60b2fa725c64a6988d2a49ea513c4feb4e02f93dc353bd/shapely-2.0.5-cp311-cp311-win32.whl", hash = "sha256:35110e80070d664781ec7955c7de557456b25727a0257b354830abb759bf8311", size = 1294608, upload-time = "2024-07-13T10:52:14.644Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ec/1b/092fff53cbeced411eed2717592e31cadd3e52f0ebaba5f2df3f34913f96/shapely-2.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c6b78c0007a34ce7144f98b7418800e0a6a5d9a762f2244b00ea560525290c9", size = 1441974, upload-time = "2024-07-13T10:52:16.986Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/04/df/8062f14cb7aa502b8bda358103facedc80b87eec41e3391182655ff40615/shapely-2.0.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:03bd7b5fa5deb44795cc0a503999d10ae9d8a22df54ae8d4a4cd2e8a93466195", size = 1449608, upload-time = "2024-07-13T10:52:19.011Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5d/e7/719f384857c39aa51aa19d09d7cac84aeab1b25a7d0dab62433bf7b419e9/shapely-2.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ff9521991ed9e201c2e923da014e766c1aa04771bc93e6fe97c27dcf0d40ace", size = 1284057, upload-time = "2024-07-13T10:52:21.008Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a4/77/c05e794a65263deb020d7e25623234975dd96881f9e8cde341810ca683e7/shapely-2.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b65365cfbf657604e50d15161ffcc68de5cdb22a601bbf7823540ab4918a98d", size = 2440805, upload-time = "2024-07-13T19:44:15.317Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f0/32/b7687654b6e747ceae8f9fa4cc7489a8ebf275c64caf811f949d87e89f5d/shapely-2.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21f64e647a025b61b19585d2247137b3a38a35314ea68c66aaf507a1c03ef6fe", size = 2524570, upload-time = "2024-07-13T10:52:23.25Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f9/9c/5b68b3cd484065c7d33d83168d2ecfebfeeaa6d88bc9cfd830de2df490ac/shapely-2.0.5-cp312-cp312-win32.whl", hash = "sha256:3ac7dc1350700c139c956b03d9c3df49a5b34aaf91d024d1510a09717ea39199", size = 1295383, upload-time = "2024-07-13T10:52:25.72Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d4/c3/e98e3eb9f06def32b8e2454ab718cafb99149f023dff023e257125132d6e/shapely-2.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:30e8737983c9d954cd17feb49eb169f02f1da49e24e5171122cf2c2b62d65c95", size = 1442365, upload-time = "2024-07-13T10:52:27.433Z" }, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + +[[package]] +name = "six" +version = "1.16.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", size = 34041, upload-time = "2021-05-05T14:18:18.379Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", size = 11053, upload-time = "2021-05-05T14:18:17.237Z" }, +] + +[[package]] +name = "smart-open" +version = "7.3.0.post1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "wrapt" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/18/2b/5e7234c68ed5bc872ad6ae77b8a421c2ed70dcb1190b44dc1abdeed5e347/smart_open-7.3.0.post1.tar.gz", hash = "sha256:ce6a3d9bc1afbf6234ad13c010b77f8cd36d24636811e3c52c3b5160f5214d1e", size = 51557, upload-time = "2025-07-03T10:06:31.271Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/08/5b/a2a3d4514c64818925f4e886d39981f1926eeb5288a4549c6b3c17ed66bb/smart_open-7.3.0.post1-py3-none-any.whl", hash = "sha256:c73661a2c24bf045c1e04e08fffc585b59af023fe783d57896f590489db66fb4", size = 61946, upload-time = "2025-07-03T10:06:29.599Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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 = "snowballstemmer" +version = "2.2.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/44/7b/af302bebf22c749c56c9c3e8ae13190b5b5db37a33d9068652e8f73b7089/snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1", size = 86699, upload-time = "2021-11-16T18:38:38.009Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ed/dc/c02e01294f7265e63a7315fe086dd1df7dacb9f840a804da846b96d01b96/snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a", size = 93002, upload-time = "2021-11-16T18:38:34.792Z" }, +] + +[[package]] +name = "socksio" +version = "1.0.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f8/5c/48a7d9495be3d1c651198fd99dbb6ce190e2274d0f28b9051307bdec6b85/socksio-1.0.0.tar.gz", hash = "sha256:f88beb3da5b5c38b9890469de67d0cb0f9d494b78b106ca1845f96c10b91c4ac", size = 19055, upload-time = "2020-04-17T15:50:34.664Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/37/c3/6eeb6034408dac0fa653d126c9204ade96b819c936e136c5e8a6897eee9c/socksio-1.0.0-py3-none-any.whl", hash = "sha256:95dc1f15f9b34e8d7b16f06d74b8ccf48f609af32ab33c608d08761c5dcbb1f3", size = 12763, upload-time = "2020-04-17T15:50:31.878Z" }, +] + +[[package]] +name = "sortedcontainers" +version = "2.4.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" }, +] + +[[package]] +name = "soupsieve" +version = "2.7" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3f/f4/4a80cd6ef364b2e8b65b15816a843c0980f7a5a2b4dc701fc574952aa19f/soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a", size = 103418, upload-time = "2025-04-20T18:50:08.518Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e7/9c/0e6afc12c269578be5c0c1c9f4b49a8d32770a080260c333ac04cc1c832d/soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4", size = 36677, upload-time = "2025-04-20T18:50:07.196Z" }, +] + +[[package]] +name = "sphinx" +version = "8.1.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +resolution-markers = [ + "python_full_version < '3.11' and sys_platform == 'darwin'", + "python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "(python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux')", +] +dependencies = [ + { name = "alabaster", marker = "python_full_version < '3.11'" }, + { name = "babel", marker = "python_full_version < '3.11'" }, + { name = "colorama", marker = "python_full_version < '3.11' and sys_platform == 'win32'" }, + { name = "docutils", marker = "python_full_version < '3.11'" }, + { name = "imagesize", marker = "python_full_version < '3.11'" }, + { name = "jinja2", marker = "python_full_version < '3.11'" }, + { name = "packaging", marker = "python_full_version < '3.11'" }, + { name = "pygments", marker = "python_full_version < '3.11'" }, + { name = "requests", marker = "python_full_version < '3.11'" }, + { name = "snowballstemmer", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-applehelp", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-devhelp", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-htmlhelp", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-jsmath", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-qthelp", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-serializinghtml", marker = "python_full_version < '3.11'" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6f/6d/be0b61178fe2cdcb67e2a92fc9ebb488e3c51c4f74a36a7824c0adf23425/sphinx-8.1.3.tar.gz", hash = "sha256:43c1911eecb0d3e161ad78611bc905d1ad0e523e4ddc202a58a821773dc4c927", size = 8184611, upload-time = "2024-10-13T20:27:13.93Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/26/60/1ddff83a56d33aaf6f10ec8ce84b4c007d9368b21008876fceda7e7381ef/sphinx-8.1.3-py3-none-any.whl", hash = "sha256:09719015511837b76bf6e03e42eb7595ac8c2e41eeb9c29c5b755c6b677992a2", size = 3487125, upload-time = "2024-10-13T20:27:10.448Z" }, +] + +[[package]] +name = "sphinx" +version = "8.2.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +resolution-markers = [ + "python_full_version >= '3.12' and sys_platform == 'darwin'", + "python_full_version >= '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "(python_full_version >= '3.12' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.12' and sys_platform != 'darwin' and sys_platform != 'linux')", + "python_full_version == '3.11.*' and sys_platform == 'darwin'", + "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "(python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'linux')", +] +dependencies = [ + { name = "alabaster", marker = "python_full_version >= '3.11'" }, + { name = "babel", marker = "python_full_version >= '3.11'" }, + { name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" }, + { name = "docutils", marker = "python_full_version >= '3.11'" }, + { name = "imagesize", marker = "python_full_version >= '3.11'" }, + { name = "jinja2", marker = "python_full_version >= '3.11'" }, + { name = "packaging", marker = "python_full_version >= '3.11'" }, + { name = "pygments", marker = "python_full_version >= '3.11'" }, + { name = "requests", marker = "python_full_version >= '3.11'" }, + { name = "roman-numerals-py", marker = "python_full_version >= '3.11'" }, + { name = "snowballstemmer", marker = "python_full_version >= '3.11'" }, + { name = "sphinxcontrib-applehelp", marker = "python_full_version >= '3.11'" }, + { name = "sphinxcontrib-devhelp", marker = "python_full_version >= '3.11'" }, + { name = "sphinxcontrib-htmlhelp", marker = "python_full_version >= '3.11'" }, + { name = "sphinxcontrib-jsmath", marker = "python_full_version >= '3.11'" }, + { name = "sphinxcontrib-qthelp", marker = "python_full_version >= '3.11'" }, + { name = "sphinxcontrib-serializinghtml", marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/38/ad/4360e50ed56cb483667b8e6dadf2d3fda62359593faabbe749a27c4eaca6/sphinx-8.2.3.tar.gz", hash = "sha256:398ad29dee7f63a75888314e9424d40f52ce5a6a87ae88e7071e80af296ec348", size = 8321876, upload-time = "2025-03-02T22:31:59.658Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/31/53/136e9eca6e0b9dc0e1962e2c908fbea2e5ac000c2a2fbd9a35797958c48b/sphinx-8.2.3-py3-none-any.whl", hash = "sha256:4405915165f13521d875a8c29c8970800a0141c14cc5416a38feca4ea5d9b9c3", size = 3589741, upload-time = "2025-03-02T22:31:56.836Z" }, +] + +[[package]] +name = "sphinx-rtd-theme" +version = "3.0.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "docutils" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "sphinxcontrib-jquery" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/91/44/c97faec644d29a5ceddd3020ae2edffa69e7d00054a8c7a6021e82f20335/sphinx_rtd_theme-3.0.2.tar.gz", hash = "sha256:b7457bc25dda723b20b086a670b9953c859eab60a2a03ee8eb2bb23e176e5f85", size = 7620463, upload-time = "2024-11-13T11:06:04.545Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/85/77/46e3bac77b82b4df5bb5b61f2de98637724f246b4966cfc34bc5895d852a/sphinx_rtd_theme-3.0.2-py2.py3-none-any.whl", hash = "sha256:422ccc750c3a3a311de4ae327e82affdaf59eb695ba4936538552f3b00f4ee13", size = 7655561, upload-time = "2024-11-13T11:06:02.094Z" }, +] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" }, +] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z" }, +] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" }, +] + +[[package]] +name = "sphinxcontrib-jquery" +version = "4.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/de/f3/aa67467e051df70a6330fe7770894b3e4f09436dea6881ae0b4f3d87cad8/sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/76/85/749bd22d1a68db7291c89e2ebca53f4306c3f205853cf31e9de279034c3c/sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae" }, +] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" }, +] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z" }, +] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" }, +] + +[[package]] +name = "sqlglot" +version = "27.25.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/36/d9/edb44720d2146f2db727fbb95862821f706390b4237a28c5a453684bc3d3/sqlglot-27.25.2.tar.gz", hash = "sha256:987a91e2057ab8d52392586722b86d77b6e8911c503e72f02eb456a7356d7891", size = 5489830, upload-time = "2025-10-09T13:49:14.015Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/19/23/f5916711e661bc454093796f04b3e4d93eb11d466ce0ebae12a9ebbe2202/sqlglot-27.25.2-py3-none-any.whl", hash = "sha256:38ff8a4c02ad74dc20fd487ce9113499e4f9790eaf338ae61862c963e5a1b87d", size = 523058, upload-time = "2025-10-09T13:49:11.33Z" }, +] + +[package.optional-dependencies] +rs = [ + { name = "sqlglotrs" }, +] + +[[package]] +name = "sqlglotrs" +version = "0.7.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1d/6c/d354837398376f19468ee59b86812d13e9faa64848af295a2af8ea0c635a/sqlglotrs-0.7.2.tar.gz", hash = "sha256:7c7b5f422a54307de0311b574dc631c099b0fdfc4b5d624b92b11b8df0b5fa16", size = 15879, upload-time = "2025-10-08T13:41:44.992Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ca/15/1aacfa3db8e30417e24af7fb1643faa6e91ba06a151947167e3c920ce898/sqlglotrs-0.7.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:d5099a417f2b3765283cd19e461554dbad4e7532181ed45be1f1a4a936947d9b", size = 314650, upload-time = "2025-10-08T13:41:37.854Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ed/8a/43d0e84258e8c6fe1abfd290d75aa80bf354e0ea9d380dda24f026e2fdfb/sqlglotrs-0.7.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:98d4b5c2a3b42a4d263d70bc645ad8b54ff47f96e26d12f9e9d01e68b192f7ee", size = 300287, upload-time = "2025-10-08T13:41:31.083Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f4/27/7569cef7d950acfec2f513e5c0d8277bfae6f416250d3169735059554264/sqlglotrs-0.7.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:314272be1f683fc906b9b0ab51a0481135b1f7bfd17490352101c55cf48c65e4", size = 332757, upload-time = "2025-10-08T13:40:43.592Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a9/ec/64600c99985859849dcb900d04658606e6492b1184a7ed7818b237febd21/sqlglotrs-0.7.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b75e593f632ca268543630e8162625234063a71f257fa91cf43d57084d91e35", size = 342769, upload-time = "2025-10-08T13:40:52.147Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0f/f2/a89b6ab69668ca4a8c2aeac9504c3c200fd392ec1345dd687b4b8210fad5/sqlglotrs-0.7.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fd1bc10aa211ae36032ad40b9edee9798bcc2fc9ba923ad96aa124bf551dd778", size = 487336, upload-time = "2025-10-08T13:41:08.868Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d3/d3/0e36fa7a0c8586e503fb6d00f69979f0f0e12e8bf4f2b69f676b4d78220e/sqlglotrs-0.7.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:00185011defe3e0b34ef95b5167f429be78e14900c64fe157875c1a36db3375c", size = 365907, upload-time = "2025-10-08T13:41:16.944Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f5/67/470b946d56a559eb32f89905665f98331e9a88f44cbe7933639edf5f91c5/sqlglotrs-0.7.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40ee2afa455a58f0122e850579b137eb75194f1cbf53fe14058c9afabe035326", size = 343593, upload-time = "2025-10-08T13:41:24.582Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/20/51/aeec0876ce7f0162f8a9483e8081e0611c2fb526209b2328dc943430d667/sqlglotrs-0.7.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6e56828fe2424e0e6cb7c712daacb1490f0c79e002473896823e6e5179273e3a", size = 363451, upload-time = "2025-10-08T13:41:00.938Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0e/4b/750cb6c510b8791f02d9bdb088d6c4bad59ed7ba8c1a1d31923eed0a8966/sqlglotrs-0.7.2-cp310-cp310-win32.whl", hash = "sha256:12611a0fcd39d1df6f49ab4cd0dbd489e5ceb5cc130d72d957edb5e697fa2d8d", size = 184067, upload-time = "2025-10-08T13:41:45.858Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1e/dc/3b369f8cd141075b8368b07fca21ceb932445766c6bdd77a123c4588d9f3/sqlglotrs-0.7.2-cp310-cp310-win_amd64.whl", hash = "sha256:f0b811637d233c94039e8f9b8794733fa2cf33df73e879fdf4bef6849ba3ab2f", size = 195991, upload-time = "2025-10-08T13:41:53.089Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a3/7b/c2c3b529cee5a17668b1d2882586e555e4aa32b4a1e943cc4f35c6e2bb3c/sqlglotrs-0.7.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:3d14c7e2d17fd9ca0b89f9336f02de75b4db925ad4115484540223708b48363d", size = 314528, upload-time = "2025-10-08T13:41:39.244Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a2/c9/d9ad655f38ccaae368eb4b8383ab67e8fa2a36bf8e70c6a89b660f827878/sqlglotrs-0.7.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cf0f4263159f14d9c4af009cff4ad65eaf9c337307a07c60b9aeeeb8d2996aff", size = 300230, upload-time = "2025-10-08T13:41:32.404Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cd/8b/fe513853b230e2160d47342ab82422882443f0c7b9a1870d78060081606b/sqlglotrs-0.7.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:852964242dd88b69f39f2294aff9cf64e9f8d40d674d8c48d97c7ec14114048a", size = 332764, upload-time = "2025-10-08T13:40:45.802Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0d/a7/486e56f294f11042260c58634f9b9f7d8f81b25618eccd49fae43b97054b/sqlglotrs-0.7.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4cf10c01062188985d7edb7833224326414466c4a4e7f26305c4c5ceed3fcde8", size = 342698, upload-time = "2025-10-08T13:40:53.912Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/41/ec/5a8bc583d0fb6f9889de0e7577350d4d65d83a7f45798cd03d822616cf1b/sqlglotrs-0.7.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d31db3c81a8655e019f000f041393fea304c60748d17fba1a4c4804a20082e89", size = 487464, upload-time = "2025-10-08T13:41:10.45Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d4/f2/1b6c95b27d4cf976232de97f101fedb8f06355a3a7fd68cc6af040fef6c6/sqlglotrs-0.7.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:109961c34895ad6a83632e99fe1f78edf4172075a4aac7547a95950b1dca3362", size = 366035, upload-time = "2025-10-08T13:41:18.181Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0f/69/44f34b7a1ad02f9f718736860f146922ed658f2e33d636e1dcc2b7da7a59/sqlglotrs-0.7.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:92eb6383e44876b8f68510c52c71b6ffe410f18937cb48583be0e19d4e97036e", size = 343529, upload-time = "2025-10-08T13:41:25.971Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fa/c5/e171d0d30f33a6622156c8d3c3ffa46de66b8b443c487646aa2d18976774/sqlglotrs-0.7.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:082e5f5363e7601c78eee16f717adedb5ebb90941cc8e919dfc121b60f6810cb", size = 363781, upload-time = "2025-10-08T13:41:02.274Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0a/e1/117a6dd8af4f1d80fc2ca5bdc4edd12e67875b292b4428886fa24592564b/sqlglotrs-0.7.2-cp311-cp311-win32.whl", hash = "sha256:2655d8424066fc9c4a90ea33edf51aaf88a71f9b762c3d6f079c90b56e574420", size = 183670, upload-time = "2025-10-08T13:41:47.484Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f1/0c/033771f2b877a8c3d95eeeb242dd538c46be5fe233575d300eb994d54c1f/sqlglotrs-0.7.2-cp311-cp311-win_amd64.whl", hash = "sha256:f8db2f8b10bbfca510bd6d3f5b07702945ea9e3081c6ea8ae9358ce5d13f3786", size = 196113, upload-time = "2025-10-08T13:41:54.314Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2e/c7/2a38e9df27c2d7b1636685f32b68b4c4a8f4c3da699a7aad0bede6d9a195/sqlglotrs-0.7.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:e04fe42ca43d11d4b832d44673abb3f2269016d9e5af31474cc46862a00e79fd", size = 311037, upload-time = "2025-10-08T13:41:40.68Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/14/9a/123c12d7b164555bf91b1d6e7c8e95d69a92db742ddd138b179004012424/sqlglotrs-0.7.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7dcc177db4190aff89017e97da710028ec08083498446acd76042f92bdf17e92", size = 296873, upload-time = "2025-10-08T13:41:33.682Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/88/a3/4220fee40542739ce2055f5c4f3b611472f93dc52552bd5acf637c9484d1/sqlglotrs-0.7.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7e5c741d86c08d88fc7fd1eb41055a264649fbf8fa252f3b09b62ffe826d88bf", size = 332201, upload-time = "2025-10-08T13:40:47.757Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1e/70/a5c3f2bd82c89bebc798f503b77761b64ea48424503467fd666c54382a36/sqlglotrs-0.7.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:54a008319bb47531fe87f79f40b0ad3275f619cda3215d33526276285f2ef1db", size = 342400, upload-time = "2025-10-08T13:40:56.086Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1b/30/b58c09fbb7d316044333e0a999c686a15be36e8f0d28879166f7a555edb9/sqlglotrs-0.7.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a3334b5d0639f5b55bb0cbac410719966889157747f816a71a962ca3c2c5e31", size = 485041, upload-time = "2025-10-08T13:41:12.315Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c2/7f/a8c59adad7bdbc069f49dac9d0485f591d05c6060c69c995f30ecdf584e1/sqlglotrs-0.7.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7470963a61f82f894e6821fa59054e03390786e19f1afc25a334a336ac2cec7", size = 366928, upload-time = "2025-10-08T13:41:19.585Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c4/d8/a133cf8a257d7e254f3668c6bfe63910e9320a4e3b1db18d60bc0832587d/sqlglotrs-0.7.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef6cacc278c5ae98e6dab19384da381636af19b76af524f55ad5a0b7a790c49b", size = 342893, upload-time = "2025-10-08T13:41:27.268Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2d/33/e097fa8874a271b28b0798ddc16db7e839bf7eb2fe23a154a31e74583071/sqlglotrs-0.7.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d891a2c70091e4f2a2e8162d9fe4b5df24ef34bb5b4bb2ea55f61089b2f466df", size = 362581, upload-time = "2025-10-08T13:41:03.579Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/66/27/5654c974c1fc2eda40b114f0381b06b9f60c67ce02b126f3a006c527756d/sqlglotrs-0.7.2-cp312-cp312-win32.whl", hash = "sha256:ea58effa219f88c60f30cab2aaa1975c4301ddecdc5ee2eb2c8fa28caaee0566", size = 183206, upload-time = "2025-10-08T13:41:49.065Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/dc/3c/f7cd40d70ffa0cb39785196e0ca863d34aa39282c3a9ffcd0416f144f670/sqlglotrs-0.7.2-cp312-cp312-win_amd64.whl", hash = "sha256:ee8956eafb7232d73b0736fc65e0994385bae701c98660ff83af73ab42097cf5", size = 195805, upload-time = "2025-10-08T13:41:55.559Z" }, +] + +[[package]] +name = "sse-starlette" +version = "3.0.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/42/6f/22ed6e33f8a9e76ca0a412405f31abb844b779d52c5f96660766edcd737c/sse_starlette-3.0.2.tar.gz", hash = "sha256:ccd60b5765ebb3584d0de2d7a6e4f745672581de4f5005ab31c3a25d10b52b3a", size = 20985, upload-time = "2025-07-27T09:07:44.565Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ef/10/c78f463b4ef22eef8491f218f692be838282cd65480f6e423d7730dfd1fb/sse_starlette-3.0.2-py3-none-any.whl", hash = "sha256:16b7cbfddbcd4eaca11f7b586f3b8a080f1afe952c15813455b162edea619e5a", size = 11297, upload-time = "2025-07-27T09:07:43.268Z" }, +] + +[[package]] +name = "starlette" +version = "0.47.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "anyio" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/04/57/d062573f391d062710d4088fa1369428c38d51460ab6fedff920efef932e/starlette-0.47.2.tar.gz", hash = "sha256:6ae9aa5db235e4846decc1e7b79c4f346adf41e9777aebeb49dfd09bbd7023d8", size = 2583948, upload-time = "2025-07-20T17:31:58.522Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f7/1f/b876b1f83aef204198a42dc101613fefccb32258e5428b5f9259677864b4/starlette-0.47.2-py3-none-any.whl", hash = "sha256:c5847e96134e5c5371ee9fac6fdf1a67336d5815e09eb2a01fdb57a351ef915b", size = 72984, upload-time = "2025-07-20T17:31:56.738Z" }, +] + +[[package]] +name = "statsmodels" +version = "0.14.5" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "numpy" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "patsy" }, + { name = "scipy" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/64/cc/8c1bf59bf8203dea1bf2ea811cfe667d7bcc6909c83d8afb02b08e30f50b/statsmodels-0.14.5.tar.gz", hash = "sha256:de260e58cccfd2ceddf835b55a357233d6ca853a1aa4f90f7553a52cc71c6ddf", size = 20525016, upload-time = "2025-07-07T12:14:23.195Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3a/2c/55b2a5d10c1a211ecab3f792021d2581bbe1c5ca0a1059f6715dddc6899d/statsmodels-0.14.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9fc2b5cdc0c95cba894849651fec1fa1511d365e3eb72b0cc75caac44077cd48", size = 10058241, upload-time = "2025-07-07T12:13:16.286Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/66/d9/6967475805de06691e951072d05e40e3f1c71b6221bb92401193ee19bd2a/statsmodels-0.14.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b8d96b0bbaeabd3a557c35cc7249baa9cfbc6dd305c32a9f2cbdd7f46c037e7f", size = 9734017, upload-time = "2025-07-07T12:05:08.498Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/df/a8/803c280419a7312e2472969fe72cf461c1210a27770a662cbe3b5cd7c6fe/statsmodels-0.14.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:145bc39b2cb201efb6c83cc3f2163c269e63b0d4809801853dec6f440bd3bc37", size = 10459677, upload-time = "2025-07-07T14:21:51.809Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a1/25/edf20acbd670934b02cd9344e29c9a03ce040122324b3491bb075ae76b2d/statsmodels-0.14.5-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7c14fb2617bb819fb2532e1424e1da2b98a3419a80e95f33365a72d437d474e", size = 10678631, upload-time = "2025-07-07T14:22:05.496Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/64/22/8b1e38310272e766abd6093607000a81827420a3348f09eff08a9e54cbaf/statsmodels-0.14.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1e9742d8a5ac38a3bfc4b7f4b0681903920f20cbbf466d72b1fd642033846108", size = 10699273, upload-time = "2025-07-07T14:22:19.487Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d1/6f/6de51f1077b7cef34611f1d6721392ea170153251b4d977efcf6d100f779/statsmodels-0.14.5-cp310-cp310-win_amd64.whl", hash = "sha256:1cab9e6fce97caf4239cdb2df375806937da5d0b7ba2699b13af33a07f438464", size = 9644785, upload-time = "2025-07-07T12:05:20.927Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/14/30/fd49902b30416b828de763e161c0d6e2cc04d119ae4fbdd3f3b43dc8f1be/statsmodels-0.14.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4b7091a8442076c708c926de3603653a160955e80a2b6d931475b7bb8ddc02e5", size = 10053330, upload-time = "2025-07-07T12:07:39.689Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ca/c1/2654541ff6f5790d01d1e5ba36405fde873f4a854f473e90b4fe56b37333/statsmodels-0.14.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:128872be8f3208f4446d91ea9e4261823902fc7997fee7e1a983eb62fd3b7c6e", size = 9735555, upload-time = "2025-07-07T12:13:28.935Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ce/da/6ebb64d0db4e86c0d2d9cde89e03247702da0ab191789f7813d4f9a348da/statsmodels-0.14.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f2ad5aee04ae7196c429df2174df232c057e478c5fa63193d01c8ec9aae04d31", size = 10307522, upload-time = "2025-07-07T14:22:32.853Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/67/49/ac803ca093ec3845184a752a91cd84511245e1f97103b15cfe32794a3bb0/statsmodels-0.14.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f402fc793458dd6d96e099acb44cd1de1428565bf7ef3030878a8daff091f08a", size = 10474665, upload-time = "2025-07-07T14:22:46.011Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f0/c8/ae82feb00582f4814fac5d2cb3ec32f93866b413cf5878b2fe93688ec63c/statsmodels-0.14.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:26c028832730aebfbfd4e7501694e1f9ad31ec8536e776716673f4e7afd4059a", size = 10713120, upload-time = "2025-07-07T14:23:00.067Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/05/ac/4276459ea71aa46e2967ea283fc88ee5631c11f29a06787e16cf4aece1b8/statsmodels-0.14.5-cp311-cp311-win_amd64.whl", hash = "sha256:ec56f771d9529cdc17ed2fb2a950d100b6e83a7c5372aae8ac5bb065c474b856", size = 9640980, upload-time = "2025-07-07T12:05:33.085Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5f/a5/fcc4f5f16355660ce7a1742e28a43e3a9391b492fc4ff29fdd6893e81c05/statsmodels-0.14.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:37e7364a39f9aa3b51d15a208c2868b90aadb8412f868530f5cba9197cb00eaa", size = 10042891, upload-time = "2025-07-07T12:13:41.671Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1c/6f/db0cf5efa48277ac6218d9b981c8fd5e63c4c43e0d9d65015fdc38eed0ef/statsmodels-0.14.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4263d7f4d0f1d5ac6eb4db22e1ee34264a14d634b9332c975c9d9109b6b46e12", size = 9698912, upload-time = "2025-07-07T12:07:54.674Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4a/93/4ddc3bc4a59c51e6a57c49df1b889882c40d9e141e855b3517f6a8de3232/statsmodels-0.14.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:86224f6e36f38486e471e75759d241fe2912d8bc25ab157d54ee074c6aedbf45", size = 10237801, upload-time = "2025-07-07T14:23:12.593Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/66/de/dc6bf2f6e8c8eb4c5815560ebdbdf2d69a767bc0f65fde34bc086cf5b36d/statsmodels-0.14.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c3dd760a6fa80cd5e0371685c697bb9c2c0e6e1f394d975e596a1e6d0bbb9372", size = 10424154, upload-time = "2025-07-07T14:23:25.365Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/16/4f/2d5a8d14bebdf2b03b3ea89b8c6a2c837bb406ba5b7a41add8bd303bce29/statsmodels-0.14.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6264fb00e02f858b86bd01ef2dc05055a71d4a0cc7551b9976b07b0f0e6cf24f", size = 10652915, upload-time = "2025-07-07T14:23:39.337Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/df/4c/2feda3a9f0e17444a84ba5398ada6a4d2e1b8f832760048f04e2b8ea0c41/statsmodels-0.14.5-cp312-cp312-win_amd64.whl", hash = "sha256:b2ed065bfbaf8bb214c7201656df840457c2c8c65e1689e3eb09dc7440f9c61c", size = 9611236, upload-time = "2025-07-07T12:08:06.794Z" }, +] + +[[package]] +name = "strenum" +version = "0.4.15" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/85/ad/430fb60d90e1d112a62ff57bdd1f286ec73a2a0331272febfddd21f330e1/StrEnum-0.4.15.tar.gz", hash = "sha256:878fb5ab705442070e4dd1929bb5e2249511c0bcf2b0eeacf3bcd80875c82eff" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/81/69/297302c5f5f59c862faa31e6cb9a4cd74721cd1e052b38e464c5b402df8b/StrEnum-0.4.15-py3-none-any.whl", hash = "sha256:a30cda4af7cc6b5bf52c8055bc4bf4b2b6b14a93b574626da33df53cf7740659" }, +] + +[[package]] +name = "sympy" +version = "1.14.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "mpmath" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" }, +] + +[[package]] +name = "tabulate" +version = "0.9.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ec/fe/802052aecb21e3797b8f7902564ab6ea0d60ff8ca23952079064155d1ae1/tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", size = 81090, upload-time = "2022-10-06T17:21:48.54Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252, upload-time = "2022-10-06T17:21:44.262Z" }, +] + +[[package]] +name = "tavily-python" +version = "0.5.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "httpx" }, + { name = "requests" }, + { name = "tiktoken" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/db/ff/ba1a3769c34d022aeba544ff7b18cbcd0d23a6358fc3566b2101c6bf2817/tavily_python-0.5.1.tar.gz", hash = "sha256:44b0eefe79a057cd11d3cd03780b63b4913400122350e38285acfb502c2fffc1", size = 107503, upload-time = "2025-02-07T00:22:06.99Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a5/cd/71088461d7720128c78802289b3b36298f42745e5f8c334b0ffc157b881e/tavily_python-0.5.1-py3-none-any.whl", hash = "sha256:169601f703c55cf338758dcacfa7102473b479a9271d65a3af6fc3668990f757", size = 43767, upload-time = "2025-02-07T00:22:04.99Z" }, +] + +[[package]] +name = "tenacity" +version = "8.5.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a3/4d/6a19536c50b849338fcbe9290d562b52cbdcf30d8963d3588a68a4107df1/tenacity-8.5.0.tar.gz", hash = "sha256:8bc6c0c8a09b31e6cad13c47afbed1a567518250a9a171418582ed8d9c20ca78", size = 47309, upload-time = "2024-07-05T07:25:31.836Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d2/3f/8ba87d9e287b9d385a02a7114ddcef61b26f86411e121c9003eb509a1773/tenacity-8.5.0-py3-none-any.whl", hash = "sha256:b594c2a5945830c267ce6b79a166228323ed52718f30302c1359836112346687", size = 28165, upload-time = "2024-07-05T07:25:29.591Z" }, +] + +[[package]] +name = "tencentcloud-sdk-python" +version = "3.0.1215" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "requests" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fd/4c/7a320c65d605e817bedd1205c77a612be7d4dde621182cc7c00e334207ce/tencentcloud-sdk-python-3.0.1215.tar.gz", hash = "sha256:24441e69d418301d50be0279cb148a747fc272b836e41d18e213750093f490c6", size = 9566281, upload-time = "2024-08-19T20:24:26.541Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/55/08/98090d1a139e8995053ed22e099b43aa4dea8cffe056f8f0bc5178aeecbd/tencentcloud_sdk_python-3.0.1215-py2.py3-none-any.whl", hash = "sha256:899ced749baf74846f1eabf452f74aa0e48d1965f0ca7828a8b73b446f76f5f2", size = 10265517, upload-time = "2024-08-19T20:24:19.52Z" }, +] + +[[package]] +name = "tf-playwright-stealth" +version = "1.2.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "fake-http-header" }, + { name = "playwright" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d6/6b/32bb58c65991f91aeaaf7473b650175d9d4af5dd383983d177d49ccba08d/tf_playwright_stealth-1.2.0.tar.gz", hash = "sha256:7bb8d32d3e60324fbf6b9eeae540b8cd9f3b9e07baeb33b025dbc98ad47658ba", size = 23362, upload-time = "2025-06-13T04:51:04.97Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/11/3d/2653f4cf49660bb44eeac8270617cc4c0287d61716f249f55053f0af0724/tf_playwright_stealth-1.2.0-py3-none-any.whl", hash = "sha256:26ee47ee89fa0f43c606fe37c188ea3ccd36f96ea90c01d167b768df457e7886", size = 33151, upload-time = "2025-06-13T04:51:03.769Z" }, +] + +[[package]] +name = "threadpoolctl" +version = "3.6.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e", size = 21274, upload-time = "2025-03-13T13:49:23.031Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638, upload-time = "2025-03-13T13:49:21.846Z" }, +] + +[[package]] +name = "thrift" +version = "0.20.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3c/2d/8946864f716ac82dcc88d290ed613cba7a80ec75df4f553ec3ff275f486e/thrift-0.20.0.tar.gz", hash = "sha256:4dd662eadf6b8aebe8a41729527bd69adf6ceaa2a8681cbef64d1273b3e8feba", size = 62295, upload-time = "2024-03-22T22:53:08.228Z" } + +[[package]] +name = "tika" +version = "2.6.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "requests" }, + { name = "setuptools" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fd/b8/055ed37d6413fef4e4af99cd7e0edc4ddfb8fc167b730b25005d212e2049/tika-2.6.0.tar.gz", hash = "sha256:56670eb812944eb25ed73f1b3b075aa41e7a135b74b240822f28b819e5b373da", size = 27452, upload-time = "2023-01-01T22:56:31.397Z" } + +[[package]] +name = "tiktoken" +version = "0.7.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "regex" }, + { name = "requests" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c4/4a/abaec53e93e3ef37224a4dd9e2fc6bb871e7a538c2b6b9d2a6397271daf4/tiktoken-0.7.0.tar.gz", hash = "sha256:1077266e949c24e0291f6c350433c6f0971365ece2b173a23bc3b9f9defef6b6", size = 33437, upload-time = "2024-05-13T18:03:28.793Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/96/10/28d59d43d72a0ebd4211371d0bf10c935cdecbb62b812ae04c58bfc37d96/tiktoken-0.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:485f3cc6aba7c6b6ce388ba634fbba656d9ee27f766216f45146beb4ac18b25f", size = 961465, upload-time = "2024-05-13T18:02:31.978Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f8/0c/d4125348dedd1f8f38e3f85245e7fc38858ffc77c9b7edfb762a8191ba0b/tiktoken-0.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e54be9a2cd2f6d6ffa3517b064983fb695c9a9d8aa7d574d1ef3c3f931a99225", size = 906849, upload-time = "2024-05-13T18:02:33.535Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b9/ab/f9c7675747f259d133d66065106cf732a7c2bef6043062fbca8e011f7f4d/tiktoken-0.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79383a6e2c654c6040e5f8506f3750db9ddd71b550c724e673203b4f6b4b4590", size = 1048795, upload-time = "2024-05-13T18:02:35.411Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e7/8c/7d1007557b343d5cf18349802e94d3a14397121e9105b4661f8cd753f9bf/tiktoken-0.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d4511c52caacf3c4981d1ae2df85908bd31853f33d30b345c8b6830763f769c", size = 1080866, upload-time = "2024-05-13T18:02:37.583Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/72/40/61d6354cb64a563fce475a2907039be9fe809ca5f801213856353b01a35b/tiktoken-0.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:13c94efacdd3de9aff824a788353aa5749c0faee1fbe3816df365ea450b82311", size = 1092776, upload-time = "2024-05-13T18:02:39.51Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f2/6c/83ca40527d072739f0704b9f59b325786c444ca63672a77cb69adc8181f7/tiktoken-0.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8e58c7eb29d2ab35a7a8929cbeea60216a4ccdf42efa8974d8e176d50c9a3df5", size = 1142591, upload-time = "2024-05-13T18:02:40.793Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ec/1f/a5d72755118e9e1b62cdf3ef9138eb83d49088f3cb37a9540025c81c0e75/tiktoken-0.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:21a20c3bd1dd3e55b91c1331bf25f4af522c525e771691adbc9a69336fa7f702", size = 798864, upload-time = "2024-05-13T18:02:42.567Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/22/eb/57492b2568eea1d546da5cc1ae7559d924275280db80ba07e6f9b89a914b/tiktoken-0.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:10c7674f81e6e350fcbed7c09a65bca9356eaab27fb2dac65a1e440f2bcfe30f", size = 961468, upload-time = "2024-05-13T18:02:43.788Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/30/ef/e07dbfcb2f85c84abaa1b035a9279575a8da0236305491dc22ae099327f7/tiktoken-0.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:084cec29713bc9d4189a937f8a35dbdfa785bd1235a34c1124fe2323821ee93f", size = 907005, upload-time = "2024-05-13T18:02:45.327Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ea/9b/f36db825b1e9904c3a2646439cb9923fc1e09208e2e071c6d9dd64ead131/tiktoken-0.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:811229fde1652fedcca7c6dfe76724d0908775b353556d8a71ed74d866f73f7b", size = 1049183, upload-time = "2024-05-13T18:02:46.574Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/61/b4/b80d1fe33015e782074e96bbbf4108ccd283b8deea86fb43c15d18b7c351/tiktoken-0.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86b6e7dc2e7ad1b3757e8a24597415bafcfb454cebf9a33a01f2e6ba2e663992", size = 1080830, upload-time = "2024-05-13T18:02:48.444Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2a/40/c66ff3a21af6d62a7e0ff428d12002c4e0389f776d3ff96dcaa0bb354eee/tiktoken-0.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1063c5748be36344c7e18c7913c53e2cca116764c2080177e57d62c7ad4576d1", size = 1092967, upload-time = "2024-05-13T18:02:50.006Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2e/80/f4c9e255ff236e6a69ce44b927629cefc1b63d3a00e2d1c9ed540c9492d2/tiktoken-0.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:20295d21419bfcca092644f7e2f2138ff947a6eb8cfc732c09cc7d76988d4a89", size = 1142682, upload-time = "2024-05-13T18:02:51.814Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b1/10/c04b4ff592a5f46b28ebf4c2353f735c02ae7f0ce1b165d00748ced6467e/tiktoken-0.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:959d993749b083acc57a317cbc643fb85c014d055b2119b739487288f4e5d1cb", size = 799009, upload-time = "2024-05-13T18:02:53.057Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1d/46/4cdda4186ce900608f522da34acf442363346688c71b938a90a52d7b84cc/tiktoken-0.7.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:71c55d066388c55a9c00f61d2c456a6086673ab7dec22dd739c23f77195b1908", size = 960446, upload-time = "2024-05-13T18:02:54.409Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b6/30/09ced367d280072d7a3e21f34263dfbbf6378661e7a0f6414e7c18971083/tiktoken-0.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:09ed925bccaa8043e34c519fbb2f99110bd07c6fd67714793c21ac298e449410", size = 906652, upload-time = "2024-05-13T18:02:56.25Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e6/7b/c949e4954441a879a67626963dff69096e3c774758b9f2bb0853f7b4e1e7/tiktoken-0.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03c6c40ff1db0f48a7b4d2dafeae73a5607aacb472fa11f125e7baf9dce73704", size = 1047904, upload-time = "2024-05-13T18:02:57.707Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/50/81/1842a22f15586072280364c2ab1e40835adaf64e42fe80e52aff921ee021/tiktoken-0.7.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d20b5c6af30e621b4aca094ee61777a44118f52d886dbe4f02b70dfe05c15350", size = 1079836, upload-time = "2024-05-13T18:02:59.009Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6d/87/51a133a3d5307cf7ae3754249b0faaa91d3414b85c3d36f80b54d6817aa6/tiktoken-0.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d427614c3e074004efa2f2411e16c826f9df427d3c70a54725cae860f09e4bf4", size = 1092472, upload-time = "2024-05-13T18:03:00.597Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a5/1f/c93517dc6d3b2c9e988b8e24f87a8b2d4a4ab28920a3a3f3ea338397ae0c/tiktoken-0.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8c46d7af7b8c6987fac9b9f61041b452afe92eb087d29c9ce54951280f899a97", size = 1141881, upload-time = "2024-05-13T18:03:02.743Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bf/4b/48ca098cb580c099b5058bf62c4cb5e90ca6130fa43ef4df27088536245b/tiktoken-0.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:0bc603c30b9e371e7c4c7935aba02af5994a909fc3c0fe66e7004070858d3f8f", size = 799281, upload-time = "2024-05-13T18:03:04.036Z" }, +] + +[[package]] +name = "tokenizers" +version = "0.15.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "huggingface-hub" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c0/44/625db94e91c6196b6574359fa70bfe28e8eabf57a1b894f8f0ec69727fd1/tokenizers-0.15.2.tar.gz", hash = "sha256:e6e9c6e019dd5484be5beafc775ae6c925f4c69a3487040ed09b45e13df2cb91", size = 320256, upload-time = "2024-02-12T02:28:50.62Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a8/6e/489419d98730b3d02381f10a8b97c5bf55b45742d1b347cdd0ffe267b827/tokenizers-0.15.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:52f6130c9cbf70544287575a985bf44ae1bda2da7e8c24e97716080593638012", size = 2578411, upload-time = "2024-02-12T02:24:30.127Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/01/04/45d88b8bddc09bf56ae1631721393255b75798af515c65c26389713a2072/tokenizers-0.15.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:054c1cc9c6d68f7ffa4e810b3d5131e0ba511b6e4be34157aa08ee54c2f8d9ee", size = 2412452, upload-time = "2024-02-12T02:24:33.265Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cc/bf/819bf4445ed68ffaf73b0f6245bcbd21a5cd58e86dabbef315a6d0b707b3/tokenizers-0.15.2-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a9b9b070fdad06e347563b88c278995735292ded1132f8657084989a4c84a6d5", size = 3643451, upload-time = "2024-02-12T02:24:35.713Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/31/b3/70d3fe0ad25e065322cd902624cad4ff2647484fe823360f58af6927b48c/tokenizers-0.15.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea621a7eef4b70e1f7a4e84dd989ae3f0eeb50fc8690254eacc08acb623e82f1", size = 3534105, upload-time = "2024-02-12T02:24:38.176Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5f/1b/58e77f2b57651e8c1b4f1b7144a1250509f2e7a1f55073d12620968ae4bb/tokenizers-0.15.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cf7fd9a5141634fa3aa8d6b7be362e6ae1b4cda60da81388fa533e0b552c98fd", size = 3398034, upload-time = "2024-02-12T02:24:41.635Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/dc/d5/45dd421f45b3c1a446ffd9486cef29ed568b5978f66a1803fa46a44aa9be/tokenizers-0.15.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44f2a832cd0825295f7179eaf173381dc45230f9227ec4b44378322d900447c9", size = 3926740, upload-time = "2024-02-12T02:24:44.264Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fa/6b/6b757cf6f7c30009a6759d3f7b833d974b3cd50d24d5824c695e077cb1bf/tokenizers-0.15.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8b9ec69247a23747669ec4b0ca10f8e3dfb3545d550258129bd62291aabe8605", size = 4032027, upload-time = "2024-02-12T02:24:46.246Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1c/5d/cf5e122ce4f1a29f165b2a69dc33d1ff30bce303343d58a54775ddba5d51/tokenizers-0.15.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40b6a4c78da863ff26dbd5ad9a8ecc33d8a8d97b535172601cf00aee9d7ce9ce", size = 3577319, upload-time = "2024-02-12T02:24:47.995Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/aa/0b/dd9e5124fe73a01f36f5c7554ac97b9612af5e0bd401d6a606a3f52a060a/tokenizers-0.15.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5ab2a4d21dcf76af60e05af8063138849eb1d6553a0d059f6534357bce8ba364", size = 9682328, upload-time = "2024-02-12T02:24:50.604Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6a/0c/3435e3d54f825d4fa363a7ab2680b243314377eb2ed28e87ade70b861e7b/tokenizers-0.15.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a47acfac7e511f6bbfcf2d3fb8c26979c780a91e06fb5b9a43831b2c0153d024", size = 9995619, upload-time = "2024-02-12T02:24:52.953Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ab/bf/a804747020f1b221131b74b5f29c24b47a5d2cee4b1311ce394ca9ce242a/tokenizers-0.15.2-cp310-none-win32.whl", hash = "sha256:064ff87bb6acdbd693666de9a4b692add41308a2c0ec0770d6385737117215f2", size = 2013446, upload-time = "2024-02-12T02:24:55.494Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c9/87/0bf37626c5f1ea2462e0398be88c287f3d40c696c255ba478bf525bdc852/tokenizers-0.15.2-cp310-none-win_amd64.whl", hash = "sha256:3b919afe4df7eb6ac7cafd2bd14fb507d3f408db7a68c43117f579c984a73843", size = 2192649, upload-time = "2024-02-12T02:24:57.898Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/73/11/933d68d395f5486d935e1c15da80bc96bf3f48595652069d19e0e9894386/tokenizers-0.15.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:89cd1cb93e4b12ff39bb2d626ad77e35209de9309a71e4d3d4672667b4b256e7", size = 2578922, upload-time = "2024-02-12T02:25:00.049Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5f/4f/a4c12cc058a899c1caaa1e689c3df9a698e20e891d4005aa6ec2174a9339/tokenizers-0.15.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cfed5c64e5be23d7ee0f0e98081a25c2a46b0b77ce99a4f0605b1ec43dd481fa", size = 2412317, upload-time = "2024-02-12T02:25:02.21Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e9/13/b86ea87b7e3b4a2ca154220dc4eb19a56a3864ec03e9630d15d1bac10da1/tokenizers-0.15.2-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a907d76dcfda37023ba203ab4ceeb21bc5683436ebefbd895a0841fd52f6f6f2", size = 3643051, upload-time = "2024-02-12T02:25:04.296Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0f/23/e4985657ea42ad432d6dc2100b2687e70a6bae730f1f8c52f81d9e6ccf3a/tokenizers-0.15.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20ea60479de6fc7b8ae756b4b097572372d7e4032e2521c1bbf3d90c90a99ff0", size = 3534327, upload-time = "2024-02-12T02:25:06.752Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/34/d5/e1ad46939d6de48d41bbd8b302f87ecde79847855210e75517a832b29490/tokenizers-0.15.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:48e2b9335be2bc0171df9281385c2ed06a15f5cf121c44094338306ab7b33f2c", size = 3398296, upload-time = "2024-02-12T02:25:09.378Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e7/d1/4d319a035f819af3290ec5a09482ad659d9d2a0aea33890fb5720ce81841/tokenizers-0.15.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:112a1dd436d2cc06e6ffdc0b06d55ac019a35a63afd26475205cb4b1bf0bfbff", size = 3927353, upload-time = "2024-02-12T02:25:11.791Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e5/39/facfca8e598126a0001d4295e6b1ee670d241aa6f4fcdd97016065b43c5d/tokenizers-0.15.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4620cca5c2817177ee8706f860364cc3a8845bc1e291aaf661fb899e5d1c45b0", size = 4030091, upload-time = "2024-02-12T02:25:13.548Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/15/0b/c09b2c0dc688c82adadaa0d5080983de3ce920f4a5cbadb7eaa5302ad251/tokenizers-0.15.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ccd73a82751c523b3fc31ff8194702e4af4db21dc20e55b30ecc2079c5d43cb7", size = 3577167, upload-time = "2024-02-12T02:25:16.03Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/07/3b/d8e60712e509a6f5d01bf0eb4470452b72277be4883656206d4ccd7e02de/tokenizers-0.15.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:107089f135b4ae7817affe6264f8c7a5c5b4fd9a90f9439ed495f54fcea56fb4", size = 9683503, upload-time = "2024-02-12T02:25:17.774Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c0/61/1c26c8e54af9bab32743e0484601a60738f33797f91040da2a4104f07e70/tokenizers-0.15.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0ff110ecc57b7aa4a594396525a3451ad70988e517237fe91c540997c4e50e29", size = 9996038, upload-time = "2024-02-12T02:25:20.299Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d1/54/451e96d8514b1afbef955f7420e1180e015c3f4eb085ad38189c0e83ee87/tokenizers-0.15.2-cp311-none-win32.whl", hash = "sha256:6d76f00f5c32da36c61f41c58346a4fa7f0a61be02f4301fd30ad59834977cc3", size = 2013591, upload-time = "2024-02-12T02:25:23.323Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c1/02/40725eebedea8175918bd59ab80b2174d6ef3b3ef9ac8ec996e84c38d3ca/tokenizers-0.15.2-cp311-none-win_amd64.whl", hash = "sha256:cc90102ed17271cf0a1262babe5939e0134b3890345d11a19c3145184b706055", size = 2192797, upload-time = "2024-02-12T02:25:25.021Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ae/ca/ea4b5aa70d4d26f2d05620c265b07b5a249157767c1673f5753b8bfc7db1/tokenizers-0.15.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f86593c18d2e6248e72fb91c77d413a815153b8ea4e31f7cd443bdf28e467670", size = 2574444, upload-time = "2024-02-12T02:25:27.417Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f9/99/5a55a9b6e2db274c0969ad57d989d02efae90f9e558983a561c9b2b7ea1a/tokenizers-0.15.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0774bccc6608eca23eb9d620196687c8b2360624619623cf4ba9dc9bd53e8b51", size = 2411608, upload-time = "2024-02-12T02:25:29.74Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/82/cc/29bb3a25c06b90ce82bb20ef074011481de5c44413a1e1eb10cfd93080fb/tokenizers-0.15.2-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:d0222c5b7c9b26c0b4822a82f6a7011de0a9d3060e1da176f66274b70f846b98", size = 3652367, upload-time = "2024-02-12T02:25:32.079Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c0/ae/f6a974be9b2e1615f3de3cc9e4fc2897a86357400801c58143c67cbbad2e/tokenizers-0.15.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3835738be1de66624fff2f4f6f6684775da4e9c00bde053be7564cbf3545cc66", size = 3529509, upload-time = "2024-02-12T02:25:34.042Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d6/42/340b91f675b494c4ecc0a256c5dd88b4003dbfde05afff90b970738fdfb4/tokenizers-0.15.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0143e7d9dcd811855c1ce1ab9bf5d96d29bf5e528fd6c7824d0465741e8c10fd", size = 3396516, upload-time = "2024-02-12T02:25:35.884Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6f/b2/8a965abc17fff309eb06e98ce429a19a5e04f731a669a6113b9e182f8a79/tokenizers-0.15.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db35825f6d54215f6b6009a7ff3eedee0848c99a6271c870d2826fbbedf31a38", size = 3918811, upload-time = "2024-02-12T02:25:37.85Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6c/16/dad7b4aa6e34a395aef7ae7b010d8b5ebefdf3df81510de53d7f17d2f0fc/tokenizers-0.15.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3f5e64b0389a2be47091d8cc53c87859783b837ea1a06edd9d8e04004df55a5c", size = 4025494, upload-time = "2024-02-12T02:25:40.247Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f6/de/3707df0c1d7bf55e6a4dba724700353bfee8e292fdd8ccfe93416549124d/tokenizers-0.15.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e0480c452217edd35eca56fafe2029fb4d368b7c0475f8dfa3c5c9c400a7456", size = 3575314, upload-time = "2024-02-12T02:25:42.212Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2e/dd/7b8da304d152bb46f13bc2ba5bd545480ab6ce39d94a53eef07f7624d235/tokenizers-0.15.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a33ab881c8fe70474980577e033d0bc9a27b7ab8272896e500708b212995d834", size = 9682779, upload-time = "2024-02-12T02:25:44.027Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/07/aa/66e8a81e07a791ca6ee9d74ee6de1ffbcd3985149f13aeb530bd409baba0/tokenizers-0.15.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a308a607ca9de2c64c1b9ba79ec9a403969715a1b8ba5f998a676826f1a7039d", size = 9995614, upload-time = "2024-02-12T02:25:46.804Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bf/e1/aed3bc98785c54bd26bf6dd3d2f54cc00de33e8b1f922a23131372eedec8/tokenizers-0.15.2-cp312-none-win32.whl", hash = "sha256:b8fcfa81bcb9447df582c5bc96a031e6df4da2a774b8080d4f02c0c16b42be0b", size = 2011030, upload-time = "2024-02-12T02:25:49.829Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c9/ea/5800f4941a713b2feed955b6a256aacc1ca68a6699916d2668622c075d38/tokenizers-0.15.2-cp312-none-win_amd64.whl", hash = "sha256:38d7ab43c6825abfc0b661d95f39c7f8af2449364f01d331f3b51c94dcff7221", size = 2180523, upload-time = "2024-02-12T02:25:51.542Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a5/2c/9c2f7a0601cccc8cf169006873ed7775ad76804e98b7236d1f345faf69f8/tokenizers-0.15.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:6a9b648a58281c4672212fab04e60648fde574877d0139cd4b4f93fe28ca8944", size = 2576142, upload-time = "2024-02-12T02:27:38.631Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2e/4f/93ccada67079065f892a2c4e7159caf0ce65084fdf60253815ca964403af/tokenizers-0.15.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:7c7d18b733be6bbca8a55084027f7be428c947ddf871c500ee603e375013ffba", size = 2412714, upload-time = "2024-02-12T02:27:40.439Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fd/8f/1dbeaf8b2a2c00e5172d8ed000fba94edb1d424fd50dcbdcc755fbf3c0aa/tokenizers-0.15.2-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:13ca3611de8d9ddfbc4dc39ef54ab1d2d4aaa114ac8727dfdc6a6ec4be017378", size = 3646249, upload-time = "2024-02-12T02:27:42.447Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/89/ed/b055d12637754471e4344f4e85c6268ef76801b0113ce1f789c5d84eaae9/tokenizers-0.15.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:237d1bf3361cf2e6463e6c140628e6406766e8b27274f5fcc62c747ae3c6f094", size = 3534330, upload-time = "2024-02-12T02:27:44.578Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ad/e1/d0b441575a3ac0262c2c73773f79dd50c94e13c9dfda0d953f1c79d47ef5/tokenizers-0.15.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67a0fe1e49e60c664915e9fb6b0cb19bac082ab1f309188230e4b2920230edb3", size = 3579864, upload-time = "2024-02-12T02:27:47.452Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a8/cd/6fe89c549d3aad886295cb9875105a75fa0d82ce80e4721cb43e6eb0830e/tokenizers-0.15.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4e022fe65e99230b8fd89ebdfea138c24421f91c1a4f4781a8f5016fd5cdfb4d", size = 9684097, upload-time = "2024-02-12T02:27:49.776Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9f/4d/29e5052a11d1a9f8eb156e48c123731e6219e4f3d72cd6d7787fdf4eff7a/tokenizers-0.15.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:d857be2df69763362ac699f8b251a8cd3fac9d21893de129bc788f8baaef2693", size = 9997782, upload-time = "2024-02-12T02:27:52.987Z" }, +] + +[[package]] +name = "tomli" +version = "2.2.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, +] + +[[package]] +name = "torch" +version = "2.7.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "filelock" }, + { name = "fsspec" }, + { name = "jinja2" }, + { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version < '3.11'" }, + { name = "networkx", version = "3.5", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "nvidia-cublas-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-cupti-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-nvrtc-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-runtime-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cudnn-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cufft-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cufile-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-curand-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cusolver-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cusparse-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cusparselt-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-nccl-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-nvjitlink-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-nvtx-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "setuptools", marker = "python_full_version >= '3.12'" }, + { name = "sympy" }, + { name = "triton", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "typing-extensions" }, +] +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6a/27/2e06cb52adf89fe6e020963529d17ed51532fc73c1e6d1b18420ef03338c/torch-2.7.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:a103b5d782af5bd119b81dbcc7ffc6fa09904c423ff8db397a1e6ea8fd71508f", size = 99089441, upload-time = "2025-06-04T17:38:48.268Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0a/7c/0a5b3aee977596459ec45be2220370fde8e017f651fecc40522fd478cb1e/torch-2.7.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:fe955951bdf32d182ee8ead6c3186ad54781492bf03d547d31771a01b3d6fb7d", size = 821154516, upload-time = "2025-06-04T17:36:28.556Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f9/91/3d709cfc5e15995fb3fe7a6b564ce42280d3a55676dad672205e94f34ac9/torch-2.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:885453d6fba67d9991132143bf7fa06b79b24352f4506fd4d10b309f53454162", size = 216093147, upload-time = "2025-06-04T17:39:38.132Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/92/f6/5da3918414e07da9866ecb9330fe6ffdebe15cb9a4c5ada7d4b6e0a6654d/torch-2.7.1-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:d72acfdb86cee2a32c0ce0101606f3758f0d8bb5f8f31e7920dc2809e963aa7c", size = 68630914, upload-time = "2025-06-04T17:39:31.162Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/11/56/2eae3494e3d375533034a8e8cf0ba163363e996d85f0629441fa9d9843fe/torch-2.7.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:236f501f2e383f1cb861337bdf057712182f910f10aeaf509065d54d339e49b2", size = 99093039, upload-time = "2025-06-04T17:39:06.963Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e5/94/34b80bd172d0072c9979708ccd279c2da2f55c3ef318eceec276ab9544a4/torch-2.7.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:06eea61f859436622e78dd0cdd51dbc8f8c6d76917a9cf0555a333f9eac31ec1", size = 821174704, upload-time = "2025-06-04T17:37:03.799Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/50/9e/acf04ff375b0b49a45511c55d188bcea5c942da2aaf293096676110086d1/torch-2.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:8273145a2e0a3c6f9fd2ac36762d6ee89c26d430e612b95a99885df083b04e52", size = 216095937, upload-time = "2025-06-04T17:39:24.83Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5b/2b/d36d57c66ff031f93b4fa432e86802f84991477e522adcdffd314454326b/torch-2.7.1-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:aea4fc1bf433d12843eb2c6b2204861f43d8364597697074c8d38ae2507f8730", size = 68640034, upload-time = "2025-06-04T17:39:17.989Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/87/93/fb505a5022a2e908d81fe9a5e0aa84c86c0d5f408173be71c6018836f34e/torch-2.7.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:27ea1e518df4c9de73af7e8a720770f3628e7f667280bce2be7a16292697e3fa", size = 98948276, upload-time = "2025-06-04T17:39:12.852Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/56/7e/67c3fe2b8c33f40af06326a3d6ae7776b3e3a01daa8f71d125d78594d874/torch-2.7.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:c33360cfc2edd976c2633b3b66c769bdcbbf0e0b6550606d188431c81e7dd1fc", size = 821025792, upload-time = "2025-06-04T17:34:58.747Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a1/37/a37495502bc7a23bf34f89584fa5a78e25bae7b8da513bc1b8f97afb7009/torch-2.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:d8bf6e1856ddd1807e79dc57e54d3335f2b62e6f316ed13ed3ecfe1fc1df3d8b", size = 216050349, upload-time = "2025-06-04T17:38:59.709Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3a/60/04b77281c730bb13460628e518c52721257814ac6c298acd25757f6a175c/torch-2.7.1-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:787687087412c4bd68d315e39bc1223f08aae1d16a9e9771d95eabbb04ae98fb", size = 68645146, upload-time = "2025-06-04T17:38:52.97Z" }, +] + +[[package]] +name = "tqdm" +version = "4.67.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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 = "transformers" +version = "4.36.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "filelock" }, + { name = "huggingface-hub" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "regex" }, + { name = "requests" }, + { name = "safetensors" }, + { name = "tokenizers" }, + { name = "tqdm" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f5/49/eb90797a0efaa0ba66ae0cb6047f8b9daca9f8764cff3bd1068d8be5e997/transformers-4.36.2.tar.gz", hash = "sha256:d8068e897e47793281501e547d2bbdfc5b8556409c2cb6c3d9e2ca77d4c0b4ec", size = 7112436, upload-time = "2023-12-18T18:24:45.132Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/20/0a/739426a81f7635b422fbe6cb8d1d99d1235579a6ac8024c13d743efa6847/transformers-4.36.2-py3-none-any.whl", hash = "sha256:462066c4f74ee52516f12890dcc9ec71d1a5e97998db621668455117a54330f6", size = 8218893, upload-time = "2023-12-18T18:24:40.021Z" }, +] + +[[package]] +name = "trec-car-tools" +version = "2.6" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "cbor" }, + { name = "numpy" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d4/71/7b62e2e56de6cdf0c648f0033a9faa41b8f712bacd71968af96277185400/trec-car-tools-2.6.tar.gz", hash = "sha256:2fce2de120224fd569b151d5bed358a4ed334e643889b9e3dfe3e5a3d15d21c8", size = 7513, upload-time = "2022-02-01T16:37:20.451Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/36/75/661b406371f96622975eb25f9e70945d97fbe6b8e5af40342c59191962a3/trec_car_tools-2.6-py3-none-any.whl", hash = "sha256:e6f0373259e1c234222da7270ab54ca7af7a6f8d0dd32b13e158c1659d3991cf", size = 8414, upload-time = "2022-02-01T16:37:22.102Z" }, +] + +[[package]] +name = "trio" +version = "0.30.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "attrs" }, + { name = "cffi", marker = "(implementation_name != 'pypy' and os_name == 'nt' and platform_machine != 'aarch64' and sys_platform == 'linux') or (implementation_name != 'pypy' and os_name == 'nt' and sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "idna" }, + { name = "outcome" }, + { name = "sniffio" }, + { name = "sortedcontainers" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/01/c1/68d582b4d3a1c1f8118e18042464bb12a7c1b75d64d75111b297687041e3/trio-0.30.0.tar.gz", hash = "sha256:0781c857c0c81f8f51e0089929a26b5bb63d57f927728a5586f7e36171f064df", size = 593776, upload-time = "2025-04-21T00:48:19.507Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/69/8e/3f6dfda475ecd940e786defe6df6c500734e686c9cd0a0f8ef6821e9b2f2/trio-0.30.0-py3-none-any.whl", hash = "sha256:3bf4f06b8decf8d3cf00af85f40a89824669e2d033bb32469d34840edcfc22a5", size = 499194, upload-time = "2025-04-21T00:48:17.167Z" }, +] + +[[package]] +name = "trio-websocket" +version = "0.12.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "outcome" }, + { name = "trio" }, + { name = "wsproto" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d1/3c/8b4358e81f2f2cfe71b66a267f023a91db20a817b9425dd964873796980a/trio_websocket-0.12.2.tar.gz", hash = "sha256:22c72c436f3d1e264d0910a3951934798dcc5b00ae56fc4ee079d46c7cf20fae", size = 33549, upload-time = "2025-02-25T05:16:58.947Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c7/19/eb640a397bba49ba49ef9dbe2e7e5c04202ba045b6ce2ec36e9cadc51e04/trio_websocket-0.12.2-py3-none-any.whl", hash = "sha256:df605665f1db533f4a386c94525870851096a223adcb97f72a07e8b4beba45b6", size = 21221, upload-time = "2025-02-25T05:16:57.545Z" }, +] + +[[package]] +name = "triton" +version = "3.3.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "setuptools", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, +] +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8d/a9/549e51e9b1b2c9b854fd761a1d23df0ba2fbc60bd0c13b489ffa518cfcb7/triton-3.3.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b74db445b1c562844d3cfad6e9679c72e93fdfb1a90a24052b03bb5c49d1242e", size = 155600257, upload-time = "2025-05-29T23:39:36.085Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/21/2f/3e56ea7b58f80ff68899b1dbe810ff257c9d177d288c6b0f55bf2fe4eb50/triton-3.3.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b31e3aa26f8cb3cc5bf4e187bf737cbacf17311e1112b781d4a059353dfd731b", size = 155689937, upload-time = "2025-05-29T23:39:44.182Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/24/5f/950fb373bf9c01ad4eb5a8cd5eaf32cdf9e238c02f9293557a2129b9c4ac/triton-3.3.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9999e83aba21e1a78c1f36f21bce621b77bcaa530277a50484a7cb4a822f6e43", size = 155669138, upload-time = "2025-05-29T23:39:51.771Z" }, +] + +[[package]] +name = "typer" +version = "0.16.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "click" }, + { name = "rich" }, + { name = "shellingham" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c5/8c/7d682431efca5fd290017663ea4588bf6f2c6aad085c7f108c5dbc316e70/typer-0.16.0.tar.gz", hash = "sha256:af377ffaee1dbe37ae9440cb4e8f11686ea5ce4e9bae01b84ae7c63b87f1dd3b", size = 102625, upload-time = "2025-05-26T14:30:31.824Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/76/42/3efaf858001d2c2913de7f354563e3a3a2f0decae3efe98427125a8f441e/typer-0.16.0-py3-none-any.whl", hash = "sha256:1f79bed11d4d02d4310e3c1b7ba594183bcedb0ac73b27a9e5f28f6fb5b98855", size = 46317, upload-time = "2025-05-26T14:30:30.523Z" }, +] + +[[package]] +name = "types-python-dateutil" +version = "2.9.0.20250708" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c9/95/6bdde7607da2e1e99ec1c1672a759d42f26644bbacf939916e086db34870/types_python_dateutil-2.9.0.20250708.tar.gz", hash = "sha256:ccdbd75dab2d6c9696c350579f34cffe2c281e4c5f27a585b2a2438dd1d5c8ab", size = 15834, upload-time = "2025-07-08T03:14:03.382Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/72/52/43e70a8e57fefb172c22a21000b03ebcc15e47e97f5cb8495b9c2832efb4/types_python_dateutil-2.9.0.20250708-py3-none-any.whl", hash = "sha256:4d6d0cc1cc4d24a2dc3816024e502564094497b713f7befda4d5bc7a8e3fd21f", size = 17724, upload-time = "2025-07-08T03:14:02.593Z" }, +] + +[[package]] +name = "types-requests" +version = "2.32.4.20250611" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "urllib3" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6d/7f/73b3a04a53b0fd2a911d4ec517940ecd6600630b559e4505cc7b68beb5a0/types_requests-2.32.4.20250611.tar.gz", hash = "sha256:741c8777ed6425830bf51e54d6abe245f79b4dcb9019f1622b773463946bf826", size = 23118, upload-time = "2025-06-11T03:11:41.272Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3d/ea/0be9258c5a4fa1ba2300111aa5a0767ee6d18eb3fd20e91616c12082284d/types_requests-2.32.4.20250611-py3-none-any.whl", hash = "sha256:ad2fe5d3b0cb3c2c902c8815a70e7fb2302c4b8c1f77bdcd738192cdb3878072", size = 20643, upload-time = "2025-06-11T03:11:40.186Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.14.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" }, +] + +[[package]] +name = "tzdata" +version = "2025.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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 = "tzlocal" +version = "5.3.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8b/2e/c14812d3d4d9cd1773c6be938f89e5735a1f11a9f184ac3639b93cef35d5/tzlocal-5.3.1.tar.gz", hash = "sha256:cceffc7edecefea1f595541dbd6e990cb1ea3d19bf01b2809f362a03dd7921fd", size = 30761, upload-time = "2025-03-05T21:17:41.549Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-py3-none-any.whl", hash = "sha256:eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d", size = 18026, upload-time = "2025-03-05T21:17:39.857Z" }, +] + +[[package]] +name = "umap-learn" +version = "0.5.6" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "numba" }, + { name = "numpy" }, + { name = "pynndescent" }, + { name = "scikit-learn" }, + { name = "scipy" }, + { name = "tqdm" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/36/c0/a21f7e83dc471cb4bdb7bfb10244eb63a0c0b68ee2939b6698add0377eee/umap-learn-0.5.6.tar.gz", hash = "sha256:5b3917a862c23ba0fc83bfcd67a7b719dec85b3d9c01fdc7d894cce455df4e03", size = 89627, upload-time = "2024-04-03T16:53:18.592Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d1/1b/46802a050b1c55d10c4f59fc6afd2b45ac9b4f62b2e12092d3f599286f14/umap_learn-0.5.6-py3-none-any.whl", hash = "sha256:881cc0c2ee845b790bf0455aa1664f9f68b838d9d0fe12a1291b85c5a559c913", size = 85712, upload-time = "2024-04-03T16:53:16.834Z" }, +] + +[[package]] +name = "unlzw3" +version = "0.2.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/08/f1/72b313366285263aaba21a17714fbef597d7662a8737a928b2b4784eb46e/unlzw3-0.2.3.tar.gz", hash = "sha256:ede5d928c792fff9da406f20334f9739693327f448f383ae1df1774627197bbb", size = 5426, upload-time = "2024-12-20T16:05:55.889Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4d/fb/617af9b317ac75f5663285d3a3cc38903a76d63c6e7397768307545f4ff4/unlzw3-0.2.3-py3-none-any.whl", hash = "sha256:7760fb4f3afa1225623944c061991d89a061f7fb78665dbc4cddfdb562bb4a8b", size = 6729, upload-time = "2024-12-20T16:05:53.278Z" }, +] + +[[package]] +name = "uritemplate" +version = "4.2.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/98/60/f174043244c5306c9988380d2cb10009f91563fc4b31293d27e17201af56/uritemplate-4.2.0.tar.gz", hash = "sha256:480c2ed180878955863323eea31b0ede668795de182617fef9c6ca09e6ec9d0e", size = 33267, upload-time = "2025-06-02T15:12:06.318Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a9/99/3ae339466c9183ea5b8ae87b34c0b897eda475d2aec2307cae60e5cd4f29/uritemplate-4.2.0-py3-none-any.whl", hash = "sha256:962201ba1c4edcab02e60f9a0d3821e82dfc5d2d6662a21abd533879bdb8a686", size = 11488, upload-time = "2025-06-02T15:12:03.405Z" }, +] + +[[package]] +name = "urllib3" +version = "2.5.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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.optional-dependencies] +socks = [ + { name = "pysocks" }, +] + +[[package]] +name = "uvicorn" +version = "0.35.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5e/42/e0e305207bb88c6b8d3061399c6a961ffe5fbb7e2aa63c9234df7259e9cd/uvicorn-0.35.0.tar.gz", hash = "sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01", size = 78473, upload-time = "2025-06-28T16:15:46.058Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d2/e2/dc81b1bd1dcfe91735810265e9d26bc8ec5da45b4c0f6237e286819194c3/uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a", size = 66406, upload-time = "2025-06-28T16:15:44.816Z" }, +] + +[[package]] +name = "valkey" +version = "6.0.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "async-timeout", marker = "python_full_version < '3.12'" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/45/f7/b552b7a67017e6233cd8a3b783ce8c4b548e29df98daedd7fb4c4c2cc8f8/valkey-6.0.2.tar.gz", hash = "sha256:dc2e91512b82d1da0b91ab0cdbd8c97c0c0250281728cb32f9398760df9caeae", size = 4602149, upload-time = "2024-09-11T11:54:05.014Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d7/cb/b1eac0fe9cbdbba0a5cf189f5778fe54ba7d7c9f26c2f62ca8d759b38f52/valkey-6.0.2-py3-none-any.whl", hash = "sha256:dbbdd65439ee0dc5689502c54f1899504cc7268e85cb7fe8935f062178ff5805", size = 260101, upload-time = "2024-09-11T11:54:02.963Z" }, +] + +[[package]] +name = "vertexai" +version = "1.70.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "google-cloud-aiplatform" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/01/17/04958e273962f420cb89573c6423f231e34a684769ef49c6fed2b12cd7b1/vertexai-1.70.0.tar.gz", hash = "sha256:3af16f63c462dfc77600773fba366a99575b9fe4303fc080bd1cf823066c66fa", size = 9294, upload-time = "2024-10-09T04:28:23.814Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4a/e4/ec11c62ba6e17457b68e089b740075c23b894e801545979c0f9d01208a81/vertexai-1.70.0-py3-none-any.whl", hash = "sha256:9e0c85013efa5cad41e37e23e9fcca7e959b409288ca22832a1b7b9ae6abc393", size = 7268, upload-time = "2024-10-09T04:28:21.864Z" }, +] + +[[package]] +name = "volcengine" +version = "1.0.194" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "google" }, + { name = "protobuf" }, + { name = "pycryptodome" }, + { name = "pytz" }, + { name = "requests" }, + { name = "retry" }, + { name = "six" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/21/6d/0b29d9bb3895990391ec1e3722f153c24f94a4f1bea2d2d4f418050fae89/volcengine-1.0.194.tar.gz", hash = "sha256:cab0ea38291ca7b2bbffe130a7b173cf6fdc4a1af61cf7792c35296d5498766c", size = 356685, upload-time = "2025-07-17T12:23:39.106Z" } + +[[package]] +name = "voyageai" +version = "0.2.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "aiolimiter" }, + { name = "numpy" }, + { name = "requests" }, + { name = "tenacity" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7e/8c/23240073e306e6f49f6d2a33de28ca74fe36ebcd34bca3cfbcedcdd0ce63/voyageai-0.2.3.tar.gz", hash = "sha256:28322aa7a64cdaa774be6fcf3e4fd6a08694ea25acd5fadd1eff1b8ef8dab68a", size = 15374, upload-time = "2024-05-29T08:12:46.798Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bf/7c/43fb4689fe287eceb701f389863aab35211835d63bbb9a798cfefa80d7de/voyageai-0.2.3-py3-none-any.whl", hash = "sha256:59c4958bd991e83cedb5a82d5e14ac698ce67e42713ea10467631a48ee272b15", size = 19748, upload-time = "2024-05-29T08:12:44.968Z" }, +] + +[[package]] +name = "warc3-wet" +version = "0.2.5" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/21/c6/24c9b4a2b2b1741b57d7f44ff9790eb4ef28de898c17c2b1ca1efabf8c96/warc3_wet-0.2.5.tar.gz", hash = "sha256:15e50402dabaa1e95307f1e2a6169cfd5f137b70761d9f0b16a10aa6de227970", size = 17937, upload-time = "2024-07-17T08:33:51.765Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f4/99/0a5582a106679fd9439af51631b6c6cb627fd96cbc85a02927e6812605b8/warc3_wet-0.2.5-py3-none-any.whl", hash = "sha256:5a9a525383fb1af159734baa75f349a7c4ec7bccd1b938681b5748515d2bf624", size = 18657, upload-time = "2024-07-17T08:33:50.086Z" }, +] + +[[package]] +name = "warc3-wet-clueweb09" +version = "0.2.5" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9f/c1/dd817bf57e0274dacb10e0ac868cb6cd70876950cf361c41879c030a2b8b/warc3-wet-clueweb09-0.2.5.tar.gz", hash = "sha256:3054bfc07da525d5967df8ca3175f78fa3f78514c82643f8c81fbca96300b836", size = 17853, upload-time = "2020-12-07T23:59:04.599Z" } + +[[package]] +name = "wcwidth" +version = "0.2.13" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301, upload-time = "2024-01-06T02:10:57.829Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166, upload-time = "2024-01-06T02:10:55.763Z" }, +] + +[[package]] +name = "webdriver-manager" +version = "4.0.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "packaging" }, + { name = "python-dotenv" }, + { name = "requests" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e5/50/2958aa25647e86334b30b4f8c819cc4fd5f15d3d0115042a4c924ec6e94d/webdriver_manager-4.0.1.tar.gz", hash = "sha256:25ec177c6a2ce9c02fb8046f1b2732701a9418d6a977967bb065d840a3175d87", size = 25708, upload-time = "2023-09-25T06:34:54.614Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b1/51/b5c11cf739ac4eecde611794a0ec9df420d0239d51e73bc19eb44f02b48b/webdriver_manager-4.0.1-py2.py3-none-any.whl", hash = "sha256:d7970052295bb9cda2c1a24cf0b872dd2c41ababcc78f7b6b8dc37a41e979a7e", size = 27665, upload-time = "2023-09-25T06:34:53.307Z" }, +] + +[[package]] +name = "webencodings" +version = "0.5.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721, upload-time = "2017-04-05T20:21:34.189Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774, upload-time = "2017-04-05T20:21:32.581Z" }, +] + +[[package]] +name = "websocket-client" +version = "1.8.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e6/30/fba0d96b4b5fbf5948ed3f4681f7da2f9f64512e1d303f94b4cc174c24a5/websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da", size = 54648, upload-time = "2024-04-23T22:16:16.976Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5a/84/44687a29792a70e111c5c477230a72c4b957d88d16141199bf9acb7537a3/websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526", size = 58826, upload-time = "2024-04-23T22:16:14.422Z" }, +] + +[[package]] +name = "websockets" +version = "15.0.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1e/da/6462a9f510c0c49837bbc9345aca92d767a56c1fb2939e1579df1e1cdcf7/websockets-15.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d63efaa0cd96cf0c5fe4d581521d9fa87744540d4bc999ae6e08595a1014b45b", size = 175423, upload-time = "2025-03-05T20:01:35.363Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1c/9f/9d11c1a4eb046a9e106483b9ff69bce7ac880443f00e5ce64261b47b07e7/websockets-15.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac60e3b188ec7574cb761b08d50fcedf9d77f1530352db4eef1707fe9dee7205", size = 173080, upload-time = "2025-03-05T20:01:37.304Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d5/4f/b462242432d93ea45f297b6179c7333dd0402b855a912a04e7fc61c0d71f/websockets-15.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5756779642579d902eed757b21b0164cd6fe338506a8083eb58af5c372e39d9a", size = 173329, upload-time = "2025-03-05T20:01:39.668Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6e/0c/6afa1f4644d7ed50284ac59cc70ef8abd44ccf7d45850d989ea7310538d0/websockets-15.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdfe3e2a29e4db3659dbd5bbf04560cea53dd9610273917799f1cde46aa725e", size = 182312, upload-time = "2025-03-05T20:01:41.815Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/dd/d4/ffc8bd1350b229ca7a4db2a3e1c482cf87cea1baccd0ef3e72bc720caeec/websockets-15.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c2529b320eb9e35af0fa3016c187dffb84a3ecc572bcee7c3ce302bfeba52bf", size = 181319, upload-time = "2025-03-05T20:01:43.967Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/97/3a/5323a6bb94917af13bbb34009fac01e55c51dfde354f63692bf2533ffbc2/websockets-15.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac1e5c9054fe23226fb11e05a6e630837f074174c4c2f0fe442996112a6de4fb", size = 181631, upload-time = "2025-03-05T20:01:46.104Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a6/cc/1aeb0f7cee59ef065724041bb7ed667b6ab1eeffe5141696cccec2687b66/websockets-15.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5df592cd503496351d6dc14f7cdad49f268d8e618f80dce0cd5a36b93c3fc08d", size = 182016, upload-time = "2025-03-05T20:01:47.603Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/79/f9/c86f8f7af208e4161a7f7e02774e9d0a81c632ae76db2ff22549e1718a51/websockets-15.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0a34631031a8f05657e8e90903e656959234f3a04552259458aac0b0f9ae6fd9", size = 181426, upload-time = "2025-03-05T20:01:48.949Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c7/b9/828b0bc6753db905b91df6ae477c0b14a141090df64fb17f8a9d7e3516cf/websockets-15.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d00075aa65772e7ce9e990cab3ff1de702aa09be3940d1dc88d5abf1ab8a09c", size = 181360, upload-time = "2025-03-05T20:01:50.938Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/89/fb/250f5533ec468ba6327055b7d98b9df056fb1ce623b8b6aaafb30b55d02e/websockets-15.0.1-cp310-cp310-win32.whl", hash = "sha256:1234d4ef35db82f5446dca8e35a7da7964d02c127b095e172e54397fb6a6c256", size = 176388, upload-time = "2025-03-05T20:01:52.213Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1c/46/aca7082012768bb98e5608f01658ff3ac8437e563eca41cf068bd5849a5e/websockets-15.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:39c1fec2c11dc8d89bba6b2bf1556af381611a173ac2b511cf7231622058af41", size = 176830, upload-time = "2025-03-05T20:01:53.922Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9f/32/18fcd5919c293a398db67443acd33fde142f283853076049824fc58e6f75/websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431", size = 175423, upload-time = "2025-03-05T20:01:56.276Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/76/70/ba1ad96b07869275ef42e2ce21f07a5b0148936688c2baf7e4a1f60d5058/websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57", size = 173082, upload-time = "2025-03-05T20:01:57.563Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/86/f2/10b55821dd40eb696ce4704a87d57774696f9451108cff0d2824c97e0f97/websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905", size = 173330, upload-time = "2025-03-05T20:01:59.063Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a5/90/1c37ae8b8a113d3daf1065222b6af61cc44102da95388ac0018fcb7d93d9/websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562", size = 182878, upload-time = "2025-03-05T20:02:00.305Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8e/8d/96e8e288b2a41dffafb78e8904ea7367ee4f891dafc2ab8d87e2124cb3d3/websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792", size = 181883, upload-time = "2025-03-05T20:02:03.148Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/93/1f/5d6dbf551766308f6f50f8baf8e9860be6182911e8106da7a7f73785f4c4/websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413", size = 182252, upload-time = "2025-03-05T20:02:05.29Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d4/78/2d4fed9123e6620cbf1706c0de8a1632e1a28e7774d94346d7de1bba2ca3/websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8", size = 182521, upload-time = "2025-03-05T20:02:07.458Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e7/3b/66d4c1b444dd1a9823c4a81f50231b921bab54eee2f69e70319b4e21f1ca/websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3", size = 181958, upload-time = "2025-03-05T20:02:09.842Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/08/ff/e9eed2ee5fed6f76fdd6032ca5cd38c57ca9661430bb3d5fb2872dc8703c/websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf", size = 181918, upload-time = "2025-03-05T20:02:11.968Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d8/75/994634a49b7e12532be6a42103597b71098fd25900f7437d6055ed39930a/websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85", size = 176388, upload-time = "2025-03-05T20:02:13.32Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/98/93/e36c73f78400a65f5e236cd376713c34182e6663f6889cd45a4a04d8f203/websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065", size = 176828, upload-time = "2025-03-05T20:02:14.585Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437, upload-time = "2025-03-05T20:02:16.706Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096, upload-time = "2025-03-05T20:02:18.832Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332, upload-time = "2025-03-05T20:02:20.187Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152, upload-time = "2025-03-05T20:02:22.286Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096, upload-time = "2025-03-05T20:02:24.368Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523, upload-time = "2025-03-05T20:02:25.669Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790, upload-time = "2025-03-05T20:02:26.99Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165, upload-time = "2025-03-05T20:02:30.291Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160, upload-time = "2025-03-05T20:02:31.634Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395, upload-time = "2025-03-05T20:02:33.017Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841, upload-time = "2025-03-05T20:02:34.498Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/02/9e/d40f779fa16f74d3468357197af8d6ad07e7c5a27ea1ca74ceb38986f77a/websockets-15.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0c9e74d766f2818bb95f84c25be4dea09841ac0f734d1966f415e4edfc4ef1c3", size = 173109, upload-time = "2025-03-05T20:03:17.769Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bc/cd/5b887b8585a593073fd92f7c23ecd3985cd2c3175025a91b0d69b0551372/websockets-15.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1009ee0c7739c08a0cd59de430d6de452a55e42d6b522de7aa15e6f67db0b8e1", size = 173343, upload-time = "2025-03-05T20:03:19.094Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fe/ae/d34f7556890341e900a95acf4886833646306269f899d58ad62f588bf410/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76d1f20b1c7a2fa82367e04982e708723ba0e7b8d43aa643d3dcd404d74f1475", size = 174599, upload-time = "2025-03-05T20:03:21.1Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/71/e6/5fd43993a87db364ec60fc1d608273a1a465c0caba69176dd160e197ce42/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f29d80eb9a9263b8d109135351caf568cc3f80b9928bccde535c235de55c22d9", size = 174207, upload-time = "2025-03-05T20:03:23.221Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2b/fb/c492d6daa5ec067c2988ac80c61359ace5c4c674c532985ac5a123436cec/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b359ed09954d7c18bbc1680f380c7301f92c60bf924171629c5db97febb12f04", size = 174155, upload-time = "2025-03-05T20:03:25.321Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/68/a1/dcb68430b1d00b698ae7a7e0194433bce4f07ded185f0ee5fb21e2a2e91e/websockets-15.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:cad21560da69f4ce7658ca2cb83138fb4cf695a2ba3e475e0559e05991aa8122", size = 176884, upload-time = "2025-03-05T20:03:27.934Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, +] + +[[package]] +name = "werkzeug" +version = "3.0.6" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d4/f9/0ba83eaa0df9b9e9d1efeb2ea351d0677c37d41ee5d0f91e98423c7281c9/werkzeug-3.0.6.tar.gz", hash = "sha256:a8dd59d4de28ca70471a34cba79bed5f7ef2e036a76b3ab0835474246eb41f8d", size = 805170, upload-time = "2024-10-25T18:52:31.688Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6c/69/05837f91dfe42109203ffa3e488214ff86a6d68b2ed6c167da6cdc42349b/werkzeug-3.0.6-py3-none-any.whl", hash = "sha256:1bc0c2310d2fbb07b1dd1105eba2f7af72f322e1e455f2f93c993bee8c8a5f17", size = 227979, upload-time = "2024-10-25T18:52:30.129Z" }, +] + +[[package]] +name = "wikipedia" +version = "1.4.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "requests" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/67/35/25e68fbc99e672127cc6fbb14b8ec1ba3dfef035bf1e4c90f78f24a80b7d/wikipedia-1.4.0.tar.gz", hash = "sha256:db0fad1829fdd441b1852306e9856398204dc0786d2996dd2e0c8bb8e26133b2", size = 27748, upload-time = "2014-11-15T15:59:49.808Z" } + +[[package]] +name = "win-unicode-console" +version = "0.5" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/89/8d/7aad74930380c8972ab282304a2ff45f3d4927108bb6693cabcc9fc6a099/win_unicode_console-0.5.zip", hash = "sha256:d4142d4d56d46f449d6f00536a73625a871cba040f0bc1a2e305a04578f07d1e", size = 31420, upload-time = "2016-06-25T19:48:54.05Z" } + +[[package]] +name = "win32-setctime" +version = "1.2.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b3/8f/705086c9d734d3b663af0e9bb3d4de6578d08f46b1b101c2442fd9aecaa2/win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0", size = 4867, upload-time = "2024-12-07T15:28:28.314Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390", size = 4083, upload-time = "2024-12-07T15:28:26.465Z" }, +] + +[[package]] +name = "word2number" +version = "1.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4a/29/a31940c848521f0725f0df6b25dca8917f13a2025b0e8fcbe5d0457e45e6/word2number-1.1.zip", hash = "sha256:70e27a5d387f67b04c71fbb7621c05930b19bfd26efd6851e6e0f9969dcde7d0", size = 9723, upload-time = "2017-06-02T15:45:14.488Z" } + +[[package]] +name = "wrapt" +version = "1.17.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c3/fc/e91cc220803d7bc4db93fb02facd8461c37364151b8494762cc88b0fbcef/wrapt-1.17.2.tar.gz", hash = "sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3", size = 55531, upload-time = "2025-01-14T10:35:45.465Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5a/d1/1daec934997e8b160040c78d7b31789f19b122110a75eca3d4e8da0049e1/wrapt-1.17.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3d57c572081fed831ad2d26fd430d565b76aa277ed1d30ff4d40670b1c0dd984", size = 53307, upload-time = "2025-01-14T10:33:13.616Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1b/7b/13369d42651b809389c1a7153baa01d9700430576c81a2f5c5e460df0ed9/wrapt-1.17.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5e251054542ae57ac7f3fba5d10bfff615b6c2fb09abeb37d2f1463f841ae22", size = 38486, upload-time = "2025-01-14T10:33:15.947Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/62/bf/e0105016f907c30b4bd9e377867c48c34dc9c6c0c104556c9c9126bd89ed/wrapt-1.17.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80dd7db6a7cb57ffbc279c4394246414ec99537ae81ffd702443335a61dbf3a7", size = 38777, upload-time = "2025-01-14T10:33:17.462Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/27/70/0f6e0679845cbf8b165e027d43402a55494779295c4b08414097b258ac87/wrapt-1.17.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a6e821770cf99cc586d33833b2ff32faebdbe886bd6322395606cf55153246c", size = 83314, upload-time = "2025-01-14T10:33:21.282Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0f/77/0576d841bf84af8579124a93d216f55d6f74374e4445264cb378a6ed33eb/wrapt-1.17.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b60fb58b90c6d63779cb0c0c54eeb38941bae3ecf7a73c764c52c88c2dcb9d72", size = 74947, upload-time = "2025-01-14T10:33:24.414Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/90/ec/00759565518f268ed707dcc40f7eeec38637d46b098a1f5143bff488fe97/wrapt-1.17.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b870b5df5b71d8c3359d21be8f0d6c485fa0ebdb6477dda51a1ea54a9b558061", size = 82778, upload-time = "2025-01-14T10:33:26.152Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f8/5a/7cffd26b1c607b0b0c8a9ca9d75757ad7620c9c0a9b4a25d3f8a1480fafc/wrapt-1.17.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4011d137b9955791f9084749cba9a367c68d50ab8d11d64c50ba1688c9b457f2", size = 81716, upload-time = "2025-01-14T10:33:27.372Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7e/09/dccf68fa98e862df7e6a60a61d43d644b7d095a5fc36dbb591bbd4a1c7b2/wrapt-1.17.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1473400e5b2733e58b396a04eb7f35f541e1fb976d0c0724d0223dd607e0f74c", size = 74548, upload-time = "2025-01-14T10:33:28.52Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b7/8e/067021fa3c8814952c5e228d916963c1115b983e21393289de15128e867e/wrapt-1.17.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3cedbfa9c940fdad3e6e941db7138e26ce8aad38ab5fe9dcfadfed9db7a54e62", size = 81334, upload-time = "2025-01-14T10:33:29.643Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4b/0d/9d4b5219ae4393f718699ca1c05f5ebc0c40d076f7e65fd48f5f693294fb/wrapt-1.17.2-cp310-cp310-win32.whl", hash = "sha256:582530701bff1dec6779efa00c516496968edd851fba224fbd86e46cc6b73563", size = 36427, upload-time = "2025-01-14T10:33:30.832Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/72/6a/c5a83e8f61aec1e1aeef939807602fb880e5872371e95df2137142f5c58e/wrapt-1.17.2-cp310-cp310-win_amd64.whl", hash = "sha256:58705da316756681ad3c9c73fd15499aa4d8c69f9fd38dc8a35e06c12468582f", size = 38774, upload-time = "2025-01-14T10:33:32.897Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cd/f7/a2aab2cbc7a665efab072344a8949a71081eed1d2f451f7f7d2b966594a2/wrapt-1.17.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ff04ef6eec3eee8a5efef2401495967a916feaa353643defcc03fc74fe213b58", size = 53308, upload-time = "2025-01-14T10:33:33.992Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/50/ff/149aba8365fdacef52b31a258c4dc1c57c79759c335eff0b3316a2664a64/wrapt-1.17.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4db983e7bca53819efdbd64590ee96c9213894272c776966ca6306b73e4affda", size = 38488, upload-time = "2025-01-14T10:33:35.264Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/65/46/5a917ce85b5c3b490d35c02bf71aedaa9f2f63f2d15d9949cc4ba56e8ba9/wrapt-1.17.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9abc77a4ce4c6f2a3168ff34b1da9b0f311a8f1cfd694ec96b0603dff1c79438", size = 38776, upload-time = "2025-01-14T10:33:38.28Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ca/74/336c918d2915a4943501c77566db41d1bd6e9f4dbc317f356b9a244dfe83/wrapt-1.17.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b929ac182f5ace000d459c59c2c9c33047e20e935f8e39371fa6e3b85d56f4a", size = 83776, upload-time = "2025-01-14T10:33:40.678Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/09/99/c0c844a5ccde0fe5761d4305485297f91d67cf2a1a824c5f282e661ec7ff/wrapt-1.17.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f09b286faeff3c750a879d336fb6d8713206fc97af3adc14def0cdd349df6000", size = 75420, upload-time = "2025-01-14T10:33:41.868Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b4/b0/9fc566b0fe08b282c850063591a756057c3247b2362b9286429ec5bf1721/wrapt-1.17.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7ed2d9d039bd41e889f6fb9364554052ca21ce823580f6a07c4ec245c1f5d6", size = 83199, upload-time = "2025-01-14T10:33:43.598Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9d/4b/71996e62d543b0a0bd95dda485219856def3347e3e9380cc0d6cf10cfb2f/wrapt-1.17.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:129a150f5c445165ff941fc02ee27df65940fcb8a22a61828b1853c98763a64b", size = 82307, upload-time = "2025-01-14T10:33:48.499Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/39/35/0282c0d8789c0dc9bcc738911776c762a701f95cfe113fb8f0b40e45c2b9/wrapt-1.17.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1fb5699e4464afe5c7e65fa51d4f99e0b2eadcc176e4aa33600a3df7801d6662", size = 75025, upload-time = "2025-01-14T10:33:51.191Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4f/6d/90c9fd2c3c6fee181feecb620d95105370198b6b98a0770cba090441a828/wrapt-1.17.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9a2bce789a5ea90e51a02dfcc39e31b7f1e662bc3317979aa7e5538e3a034f72", size = 81879, upload-time = "2025-01-14T10:33:52.328Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8f/fa/9fb6e594f2ce03ef03eddbdb5f4f90acb1452221a5351116c7c4708ac865/wrapt-1.17.2-cp311-cp311-win32.whl", hash = "sha256:4afd5814270fdf6380616b321fd31435a462019d834f83c8611a0ce7484c7317", size = 36419, upload-time = "2025-01-14T10:33:53.551Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/47/f8/fb1773491a253cbc123c5d5dc15c86041f746ed30416535f2a8df1f4a392/wrapt-1.17.2-cp311-cp311-win_amd64.whl", hash = "sha256:acc130bc0375999da18e3d19e5a86403667ac0c4042a094fefb7eec8ebac7cf3", size = 38773, upload-time = "2025-01-14T10:33:56.323Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a1/bd/ab55f849fd1f9a58ed7ea47f5559ff09741b25f00c191231f9f059c83949/wrapt-1.17.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d5e2439eecc762cd85e7bd37161d4714aa03a33c5ba884e26c81559817ca0925", size = 53799, upload-time = "2025-01-14T10:33:57.4Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/53/18/75ddc64c3f63988f5a1d7e10fb204ffe5762bc663f8023f18ecaf31a332e/wrapt-1.17.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fc7cb4c1c744f8c05cd5f9438a3caa6ab94ce8344e952d7c45a8ed59dd88392", size = 38821, upload-time = "2025-01-14T10:33:59.334Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/48/2a/97928387d6ed1c1ebbfd4efc4133a0633546bec8481a2dd5ec961313a1c7/wrapt-1.17.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fdbdb757d5390f7c675e558fd3186d590973244fab0c5fe63d373ade3e99d40", size = 38919, upload-time = "2025-01-14T10:34:04.093Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/73/54/3bfe5a1febbbccb7a2f77de47b989c0b85ed3a6a41614b104204a788c20e/wrapt-1.17.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bb1d0dbf99411f3d871deb6faa9aabb9d4e744d67dcaaa05399af89d847a91d", size = 88721, upload-time = "2025-01-14T10:34:07.163Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/25/cb/7262bc1b0300b4b64af50c2720ef958c2c1917525238d661c3e9a2b71b7b/wrapt-1.17.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d18a4865f46b8579d44e4fe1e2bcbc6472ad83d98e22a26c963d46e4c125ef0b", size = 80899, upload-time = "2025-01-14T10:34:09.82Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2a/5a/04cde32b07a7431d4ed0553a76fdb7a61270e78c5fd5a603e190ac389f14/wrapt-1.17.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc570b5f14a79734437cb7b0500376b6b791153314986074486e0b0fa8d71d98", size = 89222, upload-time = "2025-01-14T10:34:11.258Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/09/28/2e45a4f4771fcfb109e244d5dbe54259e970362a311b67a965555ba65026/wrapt-1.17.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6d9187b01bebc3875bac9b087948a2bccefe464a7d8f627cf6e48b1bbae30f82", size = 86707, upload-time = "2025-01-14T10:34:12.49Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c6/d2/dcb56bf5f32fcd4bd9aacc77b50a539abdd5b6536872413fd3f428b21bed/wrapt-1.17.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9e8659775f1adf02eb1e6f109751268e493c73716ca5761f8acb695e52a756ae", size = 79685, upload-time = "2025-01-14T10:34:15.043Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/80/4e/eb8b353e36711347893f502ce91c770b0b0929f8f0bed2670a6856e667a9/wrapt-1.17.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8b2816ebef96d83657b56306152a93909a83f23994f4b30ad4573b00bd11bb9", size = 87567, upload-time = "2025-01-14T10:34:16.563Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/17/27/4fe749a54e7fae6e7146f1c7d914d28ef599dacd4416566c055564080fe2/wrapt-1.17.2-cp312-cp312-win32.whl", hash = "sha256:468090021f391fe0056ad3e807e3d9034e0fd01adcd3bdfba977b6fdf4213ea9", size = 36672, upload-time = "2025-01-14T10:34:17.727Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/15/06/1dbf478ea45c03e78a6a8c4be4fdc3c3bddea5c8de8a93bc971415e47f0f/wrapt-1.17.2-cp312-cp312-win_amd64.whl", hash = "sha256:ec89ed91f2fa8e3f52ae53cd3cf640d6feff92ba90d62236a81e4e563ac0e991", size = 38865, upload-time = "2025-01-14T10:34:19.577Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2d/82/f56956041adef78f849db6b289b282e72b55ab8045a75abad81898c28d19/wrapt-1.17.2-py3-none-any.whl", hash = "sha256:b18f2d1533a71f069c7f82d524a52599053d4c7166e9dd374ae2136b7f40f7c8", size = 23594, upload-time = "2025-01-14T10:35:44.018Z" }, +] + +[[package]] +name = "wsproto" +version = "1.2.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "h11" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c9/4a/44d3c295350d776427904d73c189e10aeae66d7f555bb2feee16d1e4ba5a/wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065", size = 53425, upload-time = "2022-08-23T19:58:21.447Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736", size = 24226, upload-time = "2022-08-23T19:58:19.96Z" }, +] + +[[package]] +name = "xgboost" +version = "1.6.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "numpy" }, + { name = "scipy" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/77/89/92b399140a7688443fc182b54240822c903e906121d63446eb2f84350e99/xgboost-1.6.0.tar.gz", hash = "sha256:9c944c2495cb426b8a365021565755c39ee0b53156cf5e53a4346bdad2e3b734", size = 775427, upload-time = "2022-04-16T04:16:36.568Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f1/71/abca2240b5d19aa3e90c8228cf307962fc9f598acc3c623fb49db83b4092/xgboost-1.6.0-py3-none-macosx_10_15_x86_64.macosx_11_0_x86_64.macosx_12_0_x86_64.whl", hash = "sha256:5f7fd61024c41d0c424a8272dfd27797a0393a616b717c05c0f981a49a47b4fd", size = 1712537, upload-time = "2022-04-16T04:15:30.361Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/49/d0/85c9c40e7ca1a4bc05278c1e57a89c43ab846be4cb5227871ca7605921a6/xgboost-1.6.0-py3-none-macosx_12_0_arm64.whl", hash = "sha256:ad27c6a72f6abef6d20e67f957fb25553bb09a6d1c4eaf08cb8ee7efca288255", size = 1529734, upload-time = "2022-04-16T04:16:15.473Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c3/be/18970943eb7e9d9ded5e37e87c1dc02c8a961416f725f2734629f26d69d5/xgboost-1.6.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:b1d532b8d548dd3acb4bd5f56632339e48167d9e2ec0eda2d8d6b4cd772e03b4", size = 2472197, upload-time = "2022-04-16T04:12:15.75Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bf/64/c467a20848adc3d1c3f45d60df9c7cd0c40a548fd534a9f842a35114039d/xgboost-1.6.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:640b9649104f22f0dc43c7202d22cde5531cc303801a9c75cad3f2b6e413dcf7", size = 193735183, upload-time = "2022-04-16T04:09:35.821Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/64/51/3e33a4df0ca66474e7f4e357328a5c7b35fb52cbc48b312c64d276d37da8/xgboost-1.6.0-py3-none-win_amd64.whl", hash = "sha256:e2f9baca0b7cbc208ad4fbafa4cd70b50b292717ee8ba817a3ba7a0fe49de958", size = 126068123, upload-time = "2022-04-16T04:14:02.044Z" }, +] + +[[package]] +name = "xlrd" +version = "2.0.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/07/5a/377161c2d3538d1990d7af382c79f3b2372e880b65de21b01b1a2b78691e/xlrd-2.0.2.tar.gz", hash = "sha256:08b5e25de58f21ce71dc7db3b3b8106c1fa776f3024c54e45b45b374e89234c9", size = 100167, upload-time = "2025-06-14T08:46:39.039Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1a/62/c8d562e7766786ba6587d09c5a8ba9f718ed3fa8af7f4553e8f91c36f302/xlrd-2.0.2-py2.py3-none-any.whl", hash = "sha256:ea762c3d29f4cca48d82df517b6d89fbce4db3107f9d78713e48cd321d5c9aa9", size = 96555, upload-time = "2025-06-14T08:46:37.766Z" }, +] + +[[package]] +name = "xlsxwriter" +version = "3.2.5" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a7/47/7704bac42ac6fe1710ae099b70e6a1e68ed173ef14792b647808c357da43/xlsxwriter-3.2.5.tar.gz", hash = "sha256:7e88469d607cdc920151c0ab3ce9cf1a83992d4b7bc730c5ffdd1a12115a7dbe", size = 213306, upload-time = "2025-06-17T08:59:14.619Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fa/34/a22e6664211f0c8879521328000bdcae9bf6dbafa94a923e531f6d5b3f73/xlsxwriter-3.2.5-py3-none-any.whl", hash = "sha256:4f4824234e1eaf9d95df9a8fe974585ff91d0f5e3d3f12ace5b71e443c1c6abd", size = 172347, upload-time = "2025-06-17T08:59:13.453Z" }, +] + +[[package]] +name = "xpinyin" +version = "0.7.6" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/16/f2/d548d2f91106644b1b51df4cc59c1b3fabe9048954f18011775250c32d53/xpinyin-0.7.6.tar.gz", hash = "sha256:dec6aa0f4d9f9b6788d8131245293f1951180333a6d474b467b2d556221862fe", size = 131664, upload-time = "2020-12-21T07:58:32.453Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a5/30/40e099a8da32105c8adf996abe92a5bbf5ecd338de2c4cc491b5718299ce/xpinyin-0.7.6-py3-none-any.whl", hash = "sha256:1d78eac9f612c20e155d7c3eb9dd7f9d3ec4e2667c52049e990b8bd036171a52", size = 129510, upload-time = "2020-12-21T07:58:30.32Z" }, +] + +[[package]] +name = "xxhash" +version = "3.5.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/00/5e/d6e5258d69df8b4ed8c83b6664f2b47d30d2dec551a29ad72a6c69eafd31/xxhash-3.5.0.tar.gz", hash = "sha256:84f2caddf951c9cbf8dc2e22a89d4ccf5d86391ac6418fe81e3c67d0cf60b45f", size = 84241, upload-time = "2024-08-17T09:20:38.972Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bb/8a/0e9feca390d512d293afd844d31670e25608c4a901e10202aa98785eab09/xxhash-3.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ece616532c499ee9afbb83078b1b952beffef121d989841f7f4b3dc5ac0fd212", size = 31970, upload-time = "2024-08-17T09:17:35.675Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/16/e6/be5aa49580cd064a18200ab78e29b88b1127e1a8c7955eb8ecf81f2626eb/xxhash-3.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3171f693dbc2cef6477054a665dc255d996646b4023fe56cb4db80e26f4cc520", size = 30801, upload-time = "2024-08-17T09:17:37.353Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/20/ee/b8a99ebbc6d1113b3a3f09e747fa318c3cde5b04bd9c197688fadf0eeae8/xxhash-3.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c5d3e570ef46adaf93fc81b44aca6002b5a4d8ca11bd0580c07eac537f36680", size = 220927, upload-time = "2024-08-17T09:17:38.835Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/58/62/15d10582ef159283a5c2b47f6d799fc3303fe3911d5bb0bcc820e1ef7ff4/xxhash-3.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7cb29a034301e2982df8b1fe6328a84f4b676106a13e9135a0d7e0c3e9f806da", size = 200360, upload-time = "2024-08-17T09:17:40.851Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/23/41/61202663ea9b1bd8e53673b8ec9e2619989353dba8cfb68e59a9cbd9ffe3/xxhash-3.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d0d307d27099bb0cbeea7260eb39ed4fdb99c5542e21e94bb6fd29e49c57a23", size = 428528, upload-time = "2024-08-17T09:17:42.545Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f2/07/d9a3059f702dec5b3b703737afb6dda32f304f6e9da181a229dafd052c29/xxhash-3.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0342aafd421795d740e514bc9858ebddfc705a75a8c5046ac56d85fe97bf196", size = 194149, upload-time = "2024-08-17T09:17:44.361Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/eb/58/27caadf78226ecf1d62dbd0c01d152ed381c14c1ee4ad01f0d460fc40eac/xxhash-3.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3dbbd9892c5ebffeca1ed620cf0ade13eb55a0d8c84e0751a6653adc6ac40d0c", size = 207703, upload-time = "2024-08-17T09:17:46.656Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b1/08/32d558ce23e1e068453c39aed7b3c1cdc690c177873ec0ca3a90d5808765/xxhash-3.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4cc2d67fdb4d057730c75a64c5923abfa17775ae234a71b0200346bfb0a7f482", size = 216255, upload-time = "2024-08-17T09:17:48.031Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3f/d4/2b971e2d2b0a61045f842b622ef11e94096cf1f12cd448b6fd426e80e0e2/xxhash-3.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ec28adb204b759306a3d64358a5e5c07d7b1dd0ccbce04aa76cb9377b7b70296", size = 202744, upload-time = "2024-08-17T09:17:50.045Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/19/ae/6a6438864a8c4c39915d7b65effd85392ebe22710412902487e51769146d/xxhash-3.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:1328f6d8cca2b86acb14104e381225a3d7b42c92c4b86ceae814e5c400dbb415", size = 210115, upload-time = "2024-08-17T09:17:51.834Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/48/7d/b3c27c27d1fc868094d02fe4498ccce8cec9fcc591825c01d6bcb0b4fc49/xxhash-3.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8d47ebd9f5d9607fd039c1fbf4994e3b071ea23eff42f4ecef246ab2b7334198", size = 414247, upload-time = "2024-08-17T09:17:53.094Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a1/05/918f9e7d2fbbd334b829997045d341d6239b563c44e683b9a7ef8fe50f5d/xxhash-3.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b96d559e0fcddd3343c510a0fe2b127fbff16bf346dd76280b82292567523442", size = 191419, upload-time = "2024-08-17T09:17:54.906Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/08/29/dfe393805b2f86bfc47c290b275f0b7c189dc2f4e136fd4754f32eb18a8d/xxhash-3.5.0-cp310-cp310-win32.whl", hash = "sha256:61c722ed8d49ac9bc26c7071eeaa1f6ff24053d553146d5df031802deffd03da", size = 30114, upload-time = "2024-08-17T09:17:56.566Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7b/d7/aa0b22c4ebb7c3ccb993d4c565132abc641cd11164f8952d89eb6a501909/xxhash-3.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:9bed5144c6923cc902cd14bb8963f2d5e034def4486ab0bbe1f58f03f042f9a9", size = 30003, upload-time = "2024-08-17T09:17:57.596Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/69/12/f969b81541ee91b55f1ce469d7ab55079593c80d04fd01691b550e535000/xxhash-3.5.0-cp310-cp310-win_arm64.whl", hash = "sha256:893074d651cf25c1cc14e3bea4fceefd67f2921b1bb8e40fcfeba56820de80c6", size = 26773, upload-time = "2024-08-17T09:17:59.169Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b8/c7/afed0f131fbda960ff15eee7f304fa0eeb2d58770fade99897984852ef23/xxhash-3.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:02c2e816896dc6f85922ced60097bcf6f008dedfc5073dcba32f9c8dd786f3c1", size = 31969, upload-time = "2024-08-17T09:18:00.852Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8c/0c/7c3bc6d87e5235672fcc2fb42fd5ad79fe1033925f71bf549ee068c7d1ca/xxhash-3.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6027dcd885e21581e46d3c7f682cfb2b870942feeed58a21c29583512c3f09f8", size = 30800, upload-time = "2024-08-17T09:18:01.863Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/04/9e/01067981d98069eec1c20201f8c145367698e9056f8bc295346e4ea32dd1/xxhash-3.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1308fa542bbdbf2fa85e9e66b1077eea3a88bef38ee8a06270b4298a7a62a166", size = 221566, upload-time = "2024-08-17T09:18:03.461Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d4/09/d4996de4059c3ce5342b6e1e6a77c9d6c91acce31f6ed979891872dd162b/xxhash-3.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c28b2fdcee797e1c1961cd3bcd3d545cab22ad202c846235197935e1df2f8ef7", size = 201214, upload-time = "2024-08-17T09:18:05.616Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/62/f5/6d2dc9f8d55a7ce0f5e7bfef916e67536f01b85d32a9fbf137d4cadbee38/xxhash-3.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:924361811732ddad75ff23e90efd9ccfda4f664132feecb90895bade6a1b4623", size = 429433, upload-time = "2024-08-17T09:18:06.957Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d9/72/9256303f10e41ab004799a4aa74b80b3c5977d6383ae4550548b24bd1971/xxhash-3.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89997aa1c4b6a5b1e5b588979d1da048a3c6f15e55c11d117a56b75c84531f5a", size = 194822, upload-time = "2024-08-17T09:18:08.331Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/34/92/1a3a29acd08248a34b0e6a94f4e0ed9b8379a4ff471f1668e4dce7bdbaa8/xxhash-3.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:685c4f4e8c59837de103344eb1c8a3851f670309eb5c361f746805c5471b8c88", size = 208538, upload-time = "2024-08-17T09:18:10.332Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/53/ad/7fa1a109663366de42f724a1cdb8e796a260dbac45047bce153bc1e18abf/xxhash-3.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dbd2ecfbfee70bc1a4acb7461fa6af7748ec2ab08ac0fa298f281c51518f982c", size = 216953, upload-time = "2024-08-17T09:18:11.707Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/35/02/137300e24203bf2b2a49b48ce898ecce6fd01789c0fcd9c686c0a002d129/xxhash-3.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:25b5a51dc3dfb20a10833c8eee25903fd2e14059e9afcd329c9da20609a307b2", size = 203594, upload-time = "2024-08-17T09:18:13.799Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/23/03/aeceb273933d7eee248c4322b98b8e971f06cc3880e5f7602c94e5578af5/xxhash-3.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a8fb786fb754ef6ff8c120cb96629fb518f8eb5a61a16aac3a979a9dbd40a084", size = 210971, upload-time = "2024-08-17T09:18:15.824Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e3/64/ed82ec09489474cbb35c716b189ddc1521d8b3de12b1b5ab41ce7f70253c/xxhash-3.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a905ad00ad1e1c34fe4e9d7c1d949ab09c6fa90c919860c1534ff479f40fd12d", size = 415050, upload-time = "2024-08-17T09:18:17.142Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/71/43/6db4c02dcb488ad4e03bc86d70506c3d40a384ee73c9b5c93338eb1f3c23/xxhash-3.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:963be41bcd49f53af6d795f65c0da9b4cc518c0dd9c47145c98f61cb464f4839", size = 192216, upload-time = "2024-08-17T09:18:18.779Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/22/6d/db4abec29e7a567455344433d095fdb39c97db6955bb4a2c432e486b4d28/xxhash-3.5.0-cp311-cp311-win32.whl", hash = "sha256:109b436096d0a2dd039c355fa3414160ec4d843dfecc64a14077332a00aeb7da", size = 30120, upload-time = "2024-08-17T09:18:20.009Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/52/1c/fa3b61c0cf03e1da4767213672efe186b1dfa4fc901a4a694fb184a513d1/xxhash-3.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:b702f806693201ad6c0a05ddbbe4c8f359626d0b3305f766077d51388a6bac58", size = 30003, upload-time = "2024-08-17T09:18:21.052Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6b/8e/9e6fc572acf6e1cc7ccb01973c213f895cb8668a9d4c2b58a99350da14b7/xxhash-3.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:c4dcb4120d0cc3cc448624147dba64e9021b278c63e34a38789b688fd0da9bf3", size = 26777, upload-time = "2024-08-17T09:18:22.809Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/07/0e/1bfce2502c57d7e2e787600b31c83535af83746885aa1a5f153d8c8059d6/xxhash-3.5.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:14470ace8bd3b5d51318782cd94e6f94431974f16cb3b8dc15d52f3b69df8e00", size = 31969, upload-time = "2024-08-17T09:18:24.025Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3f/d6/8ca450d6fe5b71ce521b4e5db69622383d039e2b253e9b2f24f93265b52c/xxhash-3.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:59aa1203de1cb96dbeab595ded0ad0c0056bb2245ae11fac11c0ceea861382b9", size = 30787, upload-time = "2024-08-17T09:18:25.318Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5b/84/de7c89bc6ef63d750159086a6ada6416cc4349eab23f76ab870407178b93/xxhash-3.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08424f6648526076e28fae6ea2806c0a7d504b9ef05ae61d196d571e5c879c84", size = 220959, upload-time = "2024-08-17T09:18:26.518Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fe/86/51258d3e8a8545ff26468c977101964c14d56a8a37f5835bc0082426c672/xxhash-3.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:61a1ff00674879725b194695e17f23d3248998b843eb5e933007ca743310f793", size = 200006, upload-time = "2024-08-17T09:18:27.905Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/02/0a/96973bd325412feccf23cf3680fd2246aebf4b789122f938d5557c54a6b2/xxhash-3.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2f2c61bee5844d41c3eb015ac652a0229e901074951ae48581d58bfb2ba01be", size = 428326, upload-time = "2024-08-17T09:18:29.335Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/11/a7/81dba5010f7e733de88af9555725146fc133be97ce36533867f4c7e75066/xxhash-3.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d32a592cac88d18cc09a89172e1c32d7f2a6e516c3dfde1b9adb90ab5df54a6", size = 194380, upload-time = "2024-08-17T09:18:30.706Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fb/7d/f29006ab398a173f4501c0e4977ba288f1c621d878ec217b4ff516810c04/xxhash-3.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70dabf941dede727cca579e8c205e61121afc9b28516752fd65724be1355cc90", size = 207934, upload-time = "2024-08-17T09:18:32.133Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8a/6e/6e88b8f24612510e73d4d70d9b0c7dff62a2e78451b9f0d042a5462c8d03/xxhash-3.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e5d0ddaca65ecca9c10dcf01730165fd858533d0be84c75c327487c37a906a27", size = 216301, upload-time = "2024-08-17T09:18:33.474Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/af/51/7862f4fa4b75a25c3b4163c8a873f070532fe5f2d3f9b3fc869c8337a398/xxhash-3.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e5b5e16c5a480fe5f59f56c30abdeba09ffd75da8d13f6b9b6fd224d0b4d0a2", size = 203351, upload-time = "2024-08-17T09:18:34.889Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/22/61/8d6a40f288f791cf79ed5bb113159abf0c81d6efb86e734334f698eb4c59/xxhash-3.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149b7914451eb154b3dfaa721315117ea1dac2cc55a01bfbd4df7c68c5dd683d", size = 210294, upload-time = "2024-08-17T09:18:36.355Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/17/02/215c4698955762d45a8158117190261b2dbefe9ae7e5b906768c09d8bc74/xxhash-3.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:eade977f5c96c677035ff39c56ac74d851b1cca7d607ab3d8f23c6b859379cab", size = 414674, upload-time = "2024-08-17T09:18:38.536Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/31/5c/b7a8db8a3237cff3d535261325d95de509f6a8ae439a5a7a4ffcff478189/xxhash-3.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fa9f547bd98f5553d03160967866a71056a60960be00356a15ecc44efb40ba8e", size = 192022, upload-time = "2024-08-17T09:18:40.138Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/78/e3/dd76659b2811b3fd06892a8beb850e1996b63e9235af5a86ea348f053e9e/xxhash-3.5.0-cp312-cp312-win32.whl", hash = "sha256:f7b58d1fd3551b8c80a971199543379be1cee3d0d409e1f6d8b01c1a2eebf1f8", size = 30170, upload-time = "2024-08-17T09:18:42.163Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d9/6b/1c443fe6cfeb4ad1dcf231cdec96eb94fb43d6498b4469ed8b51f8b59a37/xxhash-3.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:fa0cafd3a2af231b4e113fba24a65d7922af91aeb23774a8b78228e6cd785e3e", size = 30040, upload-time = "2024-08-17T09:18:43.699Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0f/eb/04405305f290173acc0350eba6d2f1a794b57925df0398861a20fbafa415/xxhash-3.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:586886c7e89cb9828bcd8a5686b12e161368e0064d040e225e72607b43858ba2", size = 26796, upload-time = "2024-08-17T09:18:45.29Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ab/9a/233606bada5bd6f50b2b72c45de3d9868ad551e83893d2ac86dc7bb8553a/xxhash-3.5.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:2014c5b3ff15e64feecb6b713af12093f75b7926049e26a580e94dcad3c73d8c", size = 29732, upload-time = "2024-08-17T09:20:11.175Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0c/67/f75276ca39e2c6604e3bee6c84e9db8a56a4973fde9bf35989787cf6e8aa/xxhash-3.5.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fab81ef75003eda96239a23eda4e4543cedc22e34c373edcaf744e721a163986", size = 36214, upload-time = "2024-08-17T09:20:12.335Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0f/f8/f6c61fd794229cc3848d144f73754a0c107854372d7261419dcbbd286299/xxhash-3.5.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e2febf914ace002132aa09169cc572e0d8959d0f305f93d5828c4836f9bc5a6", size = 32020, upload-time = "2024-08-17T09:20:13.537Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/79/d3/c029c99801526f859e6b38d34ab87c08993bf3dcea34b11275775001638a/xxhash-3.5.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5d3a10609c51da2a1c0ea0293fc3968ca0a18bd73838455b5bca3069d7f8e32b", size = 40515, upload-time = "2024-08-17T09:20:14.669Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/62/e3/bef7b82c1997579c94de9ac5ea7626d01ae5858aa22bf4fcb38bf220cb3e/xxhash-3.5.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5a74f23335b9689b66eb6dbe2a931a88fcd7a4c2cc4b1cb0edba8ce381c7a1da", size = 30064, upload-time = "2024-08-17T09:20:15.925Z" }, +] + +[[package]] +name = "yarl" +version = "1.20.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/packages/cb/65/7fed0d774abf47487c64be14e9223749468922817b5e8792b8a64792a1bb/yarl-1.20.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6032e6da6abd41e4acda34d75a816012717000fa6839f37124a47fcefc49bec4", size = 132910, upload-time = "2025-06-10T00:42:31.108Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8a/7b/988f55a52da99df9e56dc733b8e4e5a6ae2090081dc2754fc8fd34e60aa0/yarl-1.20.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2c7b34d804b8cf9b214f05015c4fee2ebe7ed05cf581e7192c06555c71f4446a", size = 90644, upload-time = "2025-06-10T00:42:33.851Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f7/de/30d98f03e95d30c7e3cc093759982d038c8833ec2451001d45ef4854edc1/yarl-1.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c869f2651cc77465f6cd01d938d91a11d9ea5d798738c1dc077f3de0b5e5fed", size = 89322, upload-time = "2025-06-10T00:42:35.688Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e0/7a/f2f314f5ebfe9200724b0b748de2186b927acb334cf964fd312eb86fc286/yarl-1.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62915e6688eb4d180d93840cda4110995ad50c459bf931b8b3775b37c264af1e", size = 323786, upload-time = "2025-06-10T00:42:37.817Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/15/3f/718d26f189db96d993d14b984ce91de52e76309d0fd1d4296f34039856aa/yarl-1.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:41ebd28167bc6af8abb97fec1a399f412eec5fd61a3ccbe2305a18b84fb4ca73", size = 319627, upload-time = "2025-06-10T00:42:39.937Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a5/76/8fcfbf5fa2369157b9898962a4a7d96764b287b085b5b3d9ffae69cdefd1/yarl-1.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21242b4288a6d56f04ea193adde174b7e347ac46ce6bc84989ff7c1b1ecea84e", size = 339149, upload-time = "2025-06-10T00:42:42.627Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3c/95/d7fc301cc4661785967acc04f54a4a42d5124905e27db27bb578aac49b5c/yarl-1.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bea21cdae6c7eb02ba02a475f37463abfe0a01f5d7200121b03e605d6a0439f8", size = 333327, upload-time = "2025-06-10T00:42:44.842Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/65/94/e21269718349582eee81efc5c1c08ee71c816bfc1585b77d0ec3f58089eb/yarl-1.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f8a891e4a22a89f5dde7862994485e19db246b70bb288d3ce73a34422e55b23", size = 326054, upload-time = "2025-06-10T00:42:47.149Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/32/ae/8616d1f07853704523519f6131d21f092e567c5af93de7e3e94b38d7f065/yarl-1.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd803820d44c8853a109a34e3660e5a61beae12970da479cf44aa2954019bf70", size = 315035, upload-time = "2025-06-10T00:42:48.852Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/48/aa/0ace06280861ef055855333707db5e49c6e3a08840a7ce62682259d0a6c0/yarl-1.20.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b982fa7f74c80d5c0c7b5b38f908971e513380a10fecea528091405f519b9ebb", size = 338962, upload-time = "2025-06-10T00:42:51.024Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/20/52/1e9d0e6916f45a8fb50e6844f01cb34692455f1acd548606cbda8134cd1e/yarl-1.20.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:33f29ecfe0330c570d997bcf1afd304377f2e48f61447f37e846a6058a4d33b2", size = 335399, upload-time = "2025-06-10T00:42:53.007Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f2/65/60452df742952c630e82f394cd409de10610481d9043aa14c61bf846b7b1/yarl-1.20.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:835ab2cfc74d5eb4a6a528c57f05688099da41cf4957cf08cad38647e4a83b30", size = 338649, upload-time = "2025-06-10T00:42:54.964Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7b/f5/6cd4ff38dcde57a70f23719a838665ee17079640c77087404c3d34da6727/yarl-1.20.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:46b5e0ccf1943a9a6e766b2c2b8c732c55b34e28be57d8daa2b3c1d1d4009309", size = 358563, upload-time = "2025-06-10T00:42:57.28Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d1/90/c42eefd79d0d8222cb3227bdd51b640c0c1d0aa33fe4cc86c36eccba77d3/yarl-1.20.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:df47c55f7d74127d1b11251fe6397d84afdde0d53b90bedb46a23c0e534f9d24", size = 357609, upload-time = "2025-06-10T00:42:59.055Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/03/c8/cea6b232cb4617514232e0f8a718153a95b5d82b5290711b201545825532/yarl-1.20.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76d12524d05841276b0e22573f28d5fbcb67589836772ae9244d90dd7d66aa13", size = 350224, upload-time = "2025-06-10T00:43:01.248Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ce/a3/eaa0ab9712f1f3d01faf43cf6f1f7210ce4ea4a7e9b28b489a2261ca8db9/yarl-1.20.1-cp310-cp310-win32.whl", hash = "sha256:6c4fbf6b02d70e512d7ade4b1f998f237137f1417ab07ec06358ea04f69134f8", size = 81753, upload-time = "2025-06-10T00:43:03.486Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8f/34/e4abde70a9256465fe31c88ed02c3f8502b7b5dead693a4f350a06413f28/yarl-1.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:aef6c4d69554d44b7f9d923245f8ad9a707d971e6209d51279196d8e8fe1ae16", size = 86817, upload-time = "2025-06-10T00:43:05.231Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b1/18/893b50efc2350e47a874c5c2d67e55a0ea5df91186b2a6f5ac52eff887cd/yarl-1.20.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:47ee6188fea634bdfaeb2cc420f5b3b17332e6225ce88149a17c413c77ff269e", size = 133833, upload-time = "2025-06-10T00:43:07.393Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/89/ed/b8773448030e6fc47fa797f099ab9eab151a43a25717f9ac043844ad5ea3/yarl-1.20.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d0f6500f69e8402d513e5eedb77a4e1818691e8f45e6b687147963514d84b44b", size = 91070, upload-time = "2025-06-10T00:43:09.538Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e3/e3/409bd17b1e42619bf69f60e4f031ce1ccb29bd7380117a55529e76933464/yarl-1.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a8900a42fcdaad568de58887c7b2f602962356908eedb7628eaf6021a6e435b", size = 89818, upload-time = "2025-06-10T00:43:11.575Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f8/77/64d8431a4d77c856eb2d82aa3de2ad6741365245a29b3a9543cd598ed8c5/yarl-1.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bad6d131fda8ef508b36be3ece16d0902e80b88ea7200f030a0f6c11d9e508d4", size = 347003, upload-time = "2025-06-10T00:43:14.088Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8d/d2/0c7e4def093dcef0bd9fa22d4d24b023788b0a33b8d0088b51aa51e21e99/yarl-1.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:df018d92fe22aaebb679a7f89fe0c0f368ec497e3dda6cb81a567610f04501f1", size = 336537, upload-time = "2025-06-10T00:43:16.431Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f0/f3/fc514f4b2cf02cb59d10cbfe228691d25929ce8f72a38db07d3febc3f706/yarl-1.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f969afbb0a9b63c18d0feecf0db09d164b7a44a053e78a7d05f5df163e43833", size = 362358, upload-time = "2025-06-10T00:43:18.704Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ea/6d/a313ac8d8391381ff9006ac05f1d4331cee3b1efaa833a53d12253733255/yarl-1.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:812303eb4aa98e302886ccda58d6b099e3576b1b9276161469c25803a8db277d", size = 357362, upload-time = "2025-06-10T00:43:20.888Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/00/70/8f78a95d6935a70263d46caa3dd18e1f223cf2f2ff2037baa01a22bc5b22/yarl-1.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98c4a7d166635147924aa0bf9bfe8d8abad6fffa6102de9c99ea04a1376f91e8", size = 348979, upload-time = "2025-06-10T00:43:23.169Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cb/05/42773027968968f4f15143553970ee36ead27038d627f457cc44bbbeecf3/yarl-1.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12e768f966538e81e6e7550f9086a6236b16e26cd964cf4df35349970f3551cf", size = 337274, upload-time = "2025-06-10T00:43:27.111Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/05/be/665634aa196954156741ea591d2f946f1b78ceee8bb8f28488bf28c0dd62/yarl-1.20.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe41919b9d899661c5c28a8b4b0acf704510b88f27f0934ac7a7bebdd8938d5e", size = 363294, upload-time = "2025-06-10T00:43:28.96Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/eb/90/73448401d36fa4e210ece5579895731f190d5119c4b66b43b52182e88cd5/yarl-1.20.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8601bc010d1d7780592f3fc1bdc6c72e2b6466ea34569778422943e1a1f3c389", size = 358169, upload-time = "2025-06-10T00:43:30.701Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c3/b0/fce922d46dc1eb43c811f1889f7daa6001b27a4005587e94878570300881/yarl-1.20.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:daadbdc1f2a9033a2399c42646fbd46da7992e868a5fe9513860122d7fe7a73f", size = 362776, upload-time = "2025-06-10T00:43:32.51Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f1/0d/b172628fce039dae8977fd22caeff3eeebffd52e86060413f5673767c427/yarl-1.20.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:03aa1e041727cb438ca762628109ef1333498b122e4c76dd858d186a37cec845", size = 381341, upload-time = "2025-06-10T00:43:34.543Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6b/9b/5b886d7671f4580209e855974fe1cecec409aa4a89ea58b8f0560dc529b1/yarl-1.20.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:642980ef5e0fa1de5fa96d905c7e00cb2c47cb468bfcac5a18c58e27dbf8d8d1", size = 379988, upload-time = "2025-06-10T00:43:36.489Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/73/be/75ef5fd0fcd8f083a5d13f78fd3f009528132a1f2a1d7c925c39fa20aa79/yarl-1.20.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:86971e2795584fe8c002356d3b97ef6c61862720eeff03db2a7c86b678d85b3e", size = 371113, upload-time = "2025-06-10T00:43:38.592Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/50/4f/62faab3b479dfdcb741fe9e3f0323e2a7d5cd1ab2edc73221d57ad4834b2/yarl-1.20.1-cp311-cp311-win32.whl", hash = "sha256:597f40615b8d25812f14562699e287f0dcc035d25eb74da72cae043bb884d773", size = 81485, upload-time = "2025-06-10T00:43:41.038Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f0/09/d9c7942f8f05c32ec72cd5c8e041c8b29b5807328b68b4801ff2511d4d5e/yarl-1.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:26ef53a9e726e61e9cd1cda6b478f17e350fb5800b4bd1cd9fe81c4d91cfeb2e", size = 86686, upload-time = "2025-06-10T00:43:42.692Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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 = "yfinance" +version = "0.2.65" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "curl-cffi" }, + { name = "frozendict" }, + { name = "multitasking" }, + { name = "numpy" }, + { name = "pandas" }, + { name = "peewee" }, + { name = "platformdirs" }, + { name = "protobuf" }, + { name = "pytz" }, + { name = "requests" }, + { name = "websockets" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a3/c1/2ef5acda45a71297f4be22e205359e0f93b0171f2b6ebdd681362e725686/yfinance-0.2.65.tar.gz", hash = "sha256:3d465e58c49be9d61f9862829de3e00bef6b623809f32f4efb5197b62fc60485", size = 128666, upload-time = "2025-07-06T16:20:12.769Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c9/1e/631c80e0f97aef46eb73549b9b0f60d94057294e040740f4cad0cb1f48e4/yfinance-0.2.65-py2.py3-none-any.whl", hash = "sha256:7be13abb0d80a17230bf798e9c6a324fa2bef0846684a6d4f7fa2abd21938963", size = 119438, upload-time = "2025-07-06T16:20:11.251Z" }, +] + +[[package]] +name = "zhipuai" +version = "2.0.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "cachetools" }, + { name = "httpx" }, + { name = "pydantic" }, + { name = "pyjwt" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a4/90/299e3456ee7ee1e118593552e03b86da2e9adaa0d454e467aeb4b22032a4/zhipuai-2.0.1.tar.gz", hash = "sha256:297bbdbe9393da2d1dc8066c39cf39bb2342f170d86f2b7b7a13ba368c53d701", size = 16760, upload-time = "2024-01-16T11:44:07.936Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8f/05/c3d4556886b5c6cf8c0b96eb80448ee8154c0dcc87086df018e817779ed4/zhipuai-2.0.1-py3-none-any.whl", hash = "sha256:738033d95696c3d5117dc4487e37d924e3ebbcdfa0072812b3f63a08ff72274a", size = 26386, upload-time = "2024-01-16T11:44:05.803Z" }, +] + +[[package]] +name = "zipp" +version = "3.23.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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" }, +] + +[[package]] +name = "zlib-state" +version = "0.1.9" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/84/ee/d4a461b3f96ae5ddb3ab89a294075ecb7c28a28ff56be1fe8bd09f9c50b1/zlib_state-0.1.9.tar.gz", hash = "sha256:8baef0cd0ab9f9d556a35df3f57b8d0f8b4a49c3f028189ab401672939cf435d", size = 9473, upload-time = "2024-09-05T20:21:21.653Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/56/84/0619cadedd1ae873353fc2d15873bcd9be1a2a5d2f6c100006e7bc483124/zlib_state-0.1.9-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:97f45d0f80e9d7070229ecb36112eea6a17dc40053449a9c613ef837d9cb66b4", size = 20259, upload-time = "2024-09-05T20:24:59.394Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/be/84/75833ca8df9307aa098acca7ee1f959790b4e4f811242d5363908bd8c713/zlib_state-0.1.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3564eaa130f2533b87b82d0e622cfb5c25acec123e7bfe38d39db9ce6349cb52", size = 21783, upload-time = "2024-09-05T20:25:00.924Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ed/38/910748b5f788d311f43d2dabc11f0bddc173d600e0ede7f1660d78f4efb0/zlib_state-0.1.9-cp310-cp310-win_amd64.whl", hash = "sha256:0e633bd3fb65cd8c8f0fc5870cdd40354f218f815cc7a53fb525410251f06ab9", size = 12703, upload-time = "2024-09-05T20:22:21.162Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/47/be/22a0657de2e7ad230aec984935f980e77bd21706688a90ae2572de06e1d8/zlib_state-0.1.9-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bbfd191c908be2ba04319e57b4d166f9c625204c1a8c85d2eb968d9d7d14dcb", size = 20324, upload-time = "2024-09-05T20:25:01.817Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ad/48/14617cfa8ec97309e2e3cabc42b3e106e9354775940dc96443e9e5f1e55c/zlib_state-0.1.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66dd680ef5c0d21fe1e673a4c68173feeda20f7933e3468c22c44d5960ebf621", size = 21816, upload-time = "2024-09-05T20:25:02.62Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/62/1a/a111ef96419b7195a3a50604668fe56cb5894fdecc79befcef964da5e103/zlib_state-0.1.9-cp311-cp311-win_amd64.whl", hash = "sha256:cdb7cdf2515d8c70c6a99a331bf8c1486b3bda77371e951961272cc9888494e1", size = 12704, upload-time = "2024-09-05T20:22:21.432Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6c/ae/739524e41a73cb77565bcb14d30478b322bc109f6e79fc583572eb75f637/zlib_state-0.1.9-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c22bc6ea28d1cbb717e7ba8254b12da5cff0820309d7ff46dba083d2dc44fd69", size = 20601, upload-time = "2024-09-05T20:25:03.428Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c6/f8/87cbda2338b5254db486804f8ce802a47a870b3f8572e757d37bd1f3d122/zlib_state-0.1.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06ed845442af6fc8ad885037b1393c02ff1554638cd43ff8718ca1fb8999b7c7", size = 22288, upload-time = "2024-09-05T20:25:04.449Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/99/88/cb175ba96b1b72b424b789151341206389b913bba4de2abffc6f767cb8cb/zlib_state-0.1.9-cp312-cp312-win_amd64.whl", hash = "sha256:862b120477db67df4ad8af8c135fe134ae4051693d6a6abf1c208d9d1170d7d8", size = 12734, upload-time = "2024-09-05T20:22:33.219Z" }, +] + +[[package]] +name = "zstandard" +version = "0.23.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation == 'PyPy'" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ed/f6/2ac0287b442160a89d726b17a9184a4c615bb5237db763791a7fd16d9df1/zstandard-0.23.0.tar.gz", hash = "sha256:b2d8c62d08e7255f68f7a740bae85b3c9b8e5466baa9cbf7f57f1cde0ac6bc09", size = 681701, upload-time = "2024-07-15T00:18:06.141Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2a/55/bd0487e86679db1823fc9ee0d8c9c78ae2413d34c0b461193b5f4c31d22f/zstandard-0.23.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bf0a05b6059c0528477fba9054d09179beb63744355cab9f38059548fedd46a9", size = 788701, upload-time = "2024-07-15T00:13:27.351Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e1/8a/ccb516b684f3ad987dfee27570d635822e3038645b1a950c5e8022df1145/zstandard-0.23.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fc9ca1c9718cb3b06634c7c8dec57d24e9438b2aa9a0f02b8bb36bf478538880", size = 633678, upload-time = "2024-07-15T00:13:30.24Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/12/89/75e633d0611c028e0d9af6df199423bf43f54bea5007e6718ab7132e234c/zstandard-0.23.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77da4c6bfa20dd5ea25cbf12c76f181a8e8cd7ea231c673828d0386b1740b8dc", size = 4941098, upload-time = "2024-07-15T00:13:32.526Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4a/7a/bd7f6a21802de358b63f1ee636ab823711c25ce043a3e9f043b4fcb5ba32/zstandard-0.23.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2170c7e0367dde86a2647ed5b6f57394ea7f53545746104c6b09fc1f4223573", size = 5308798, upload-time = "2024-07-15T00:13:34.925Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/79/3b/775f851a4a65013e88ca559c8ae42ac1352db6fcd96b028d0df4d7d1d7b4/zstandard-0.23.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c16842b846a8d2a145223f520b7e18b57c8f476924bda92aeee3a88d11cfc391", size = 5341840, upload-time = "2024-07-15T00:13:37.376Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/09/4f/0cc49570141dd72d4d95dd6fcf09328d1b702c47a6ec12fbed3b8aed18a5/zstandard-0.23.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:157e89ceb4054029a289fb504c98c6a9fe8010f1680de0201b3eb5dc20aa6d9e", size = 5440337, upload-time = "2024-07-15T00:13:39.772Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e7/7c/aaa7cd27148bae2dc095191529c0570d16058c54c4597a7d118de4b21676/zstandard-0.23.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:203d236f4c94cd8379d1ea61db2fce20730b4c38d7f1c34506a31b34edc87bdd", size = 4861182, upload-time = "2024-07-15T00:13:42.495Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ac/eb/4b58b5c071d177f7dc027129d20bd2a44161faca6592a67f8fcb0b88b3ae/zstandard-0.23.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dc5d1a49d3f8262be192589a4b72f0d03b72dcf46c51ad5852a4fdc67be7b9e4", size = 4932936, upload-time = "2024-07-15T00:13:44.234Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/44/f9/21a5fb9bb7c9a274b05ad700a82ad22ce82f7ef0f485980a1e98ed6e8c5f/zstandard-0.23.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:752bf8a74412b9892f4e5b58f2f890a039f57037f52c89a740757ebd807f33ea", size = 5464705, upload-time = "2024-07-15T00:13:46.822Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/49/74/b7b3e61db3f88632776b78b1db597af3f44c91ce17d533e14a25ce6a2816/zstandard-0.23.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80080816b4f52a9d886e67f1f96912891074903238fe54f2de8b786f86baded2", size = 4857882, upload-time = "2024-07-15T00:13:49.297Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4a/7f/d8eb1cb123d8e4c541d4465167080bec88481ab54cd0b31eb4013ba04b95/zstandard-0.23.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:84433dddea68571a6d6bd4fbf8ff398236031149116a7fff6f777ff95cad3df9", size = 4697672, upload-time = "2024-07-15T00:13:51.447Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5e/05/f7dccdf3d121309b60342da454d3e706453a31073e2c4dac8e1581861e44/zstandard-0.23.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ab19a2d91963ed9e42b4e8d77cd847ae8381576585bad79dbd0a8837a9f6620a", size = 5206043, upload-time = "2024-07-15T00:13:53.587Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/86/9d/3677a02e172dccd8dd3a941307621c0cbd7691d77cb435ac3c75ab6a3105/zstandard-0.23.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:59556bf80a7094d0cfb9f5e50bb2db27fefb75d5138bb16fb052b61b0e0eeeb0", size = 5667390, upload-time = "2024-07-15T00:13:56.137Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/41/7e/0012a02458e74a7ba122cd9cafe491facc602c9a17f590367da369929498/zstandard-0.23.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:27d3ef2252d2e62476389ca8f9b0cf2bbafb082a3b6bfe9d90cbcbb5529ecf7c", size = 5198901, upload-time = "2024-07-15T00:13:58.584Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/65/3a/8f715b97bd7bcfc7342d8adcd99a026cb2fb550e44866a3b6c348e1b0f02/zstandard-0.23.0-cp310-cp310-win32.whl", hash = "sha256:5d41d5e025f1e0bccae4928981e71b2334c60f580bdc8345f824e7c0a4c2a813", size = 430596, upload-time = "2024-07-15T00:14:00.693Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/19/b7/b2b9eca5e5a01111e4fe8a8ffb56bdcdf56b12448a24effe6cfe4a252034/zstandard-0.23.0-cp310-cp310-win_amd64.whl", hash = "sha256:519fbf169dfac1222a76ba8861ef4ac7f0530c35dd79ba5727014613f91613d4", size = 495498, upload-time = "2024-07-15T00:14:02.741Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9e/40/f67e7d2c25a0e2dc1744dd781110b0b60306657f8696cafb7ad7579469bd/zstandard-0.23.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:34895a41273ad33347b2fc70e1bff4240556de3c46c6ea430a7ed91f9042aa4e", size = 788699, upload-time = "2024-07-15T00:14:04.909Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e8/46/66d5b55f4d737dd6ab75851b224abf0afe5774976fe511a54d2eb9063a41/zstandard-0.23.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:77ea385f7dd5b5676d7fd943292ffa18fbf5c72ba98f7d09fc1fb9e819b34c23", size = 633681, upload-time = "2024-07-15T00:14:13.99Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/63/b6/677e65c095d8e12b66b8f862b069bcf1f1d781b9c9c6f12eb55000d57583/zstandard-0.23.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:983b6efd649723474f29ed42e1467f90a35a74793437d0bc64a5bf482bedfa0a", size = 4944328, upload-time = "2024-07-15T00:14:16.588Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/59/cc/e76acb4c42afa05a9d20827116d1f9287e9c32b7ad58cc3af0721ce2b481/zstandard-0.23.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80a539906390591dd39ebb8d773771dc4db82ace6372c4d41e2d293f8e32b8db", size = 5311955, upload-time = "2024-07-15T00:14:19.389Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/78/e4/644b8075f18fc7f632130c32e8f36f6dc1b93065bf2dd87f03223b187f26/zstandard-0.23.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:445e4cb5048b04e90ce96a79b4b63140e3f4ab5f662321975679b5f6360b90e2", size = 5344944, upload-time = "2024-07-15T00:14:22.173Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/76/3f/dbafccf19cfeca25bbabf6f2dd81796b7218f768ec400f043edc767015a6/zstandard-0.23.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd30d9c67d13d891f2360b2a120186729c111238ac63b43dbd37a5a40670b8ca", size = 5442927, upload-time = "2024-07-15T00:14:24.825Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0c/c3/d24a01a19b6733b9f218e94d1a87c477d523237e07f94899e1c10f6fd06c/zstandard-0.23.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d20fd853fbb5807c8e84c136c278827b6167ded66c72ec6f9a14b863d809211c", size = 4864910, upload-time = "2024-07-15T00:14:26.982Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1c/a9/cf8f78ead4597264f7618d0875be01f9bc23c9d1d11afb6d225b867cb423/zstandard-0.23.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed1708dbf4d2e3a1c5c69110ba2b4eb6678262028afd6c6fbcc5a8dac9cda68e", size = 4935544, upload-time = "2024-07-15T00:14:29.582Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2c/96/8af1e3731b67965fb995a940c04a2c20997a7b3b14826b9d1301cf160879/zstandard-0.23.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:be9b5b8659dff1f913039c2feee1aca499cfbc19e98fa12bc85e037c17ec6ca5", size = 5467094, upload-time = "2024-07-15T00:14:40.126Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ff/57/43ea9df642c636cb79f88a13ab07d92d88d3bfe3e550b55a25a07a26d878/zstandard-0.23.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:65308f4b4890aa12d9b6ad9f2844b7ee42c7f7a4fd3390425b242ffc57498f48", size = 4860440, upload-time = "2024-07-15T00:14:42.786Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/46/37/edb78f33c7f44f806525f27baa300341918fd4c4af9472fbc2c3094be2e8/zstandard-0.23.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:98da17ce9cbf3bfe4617e836d561e433f871129e3a7ac16d6ef4c680f13a839c", size = 4700091, upload-time = "2024-07-15T00:14:45.184Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c1/f1/454ac3962671a754f3cb49242472df5c2cced4eb959ae203a377b45b1a3c/zstandard-0.23.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:8ed7d27cb56b3e058d3cf684d7200703bcae623e1dcc06ed1e18ecda39fee003", size = 5208682, upload-time = "2024-07-15T00:14:47.407Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/85/b2/1734b0fff1634390b1b887202d557d2dd542de84a4c155c258cf75da4773/zstandard-0.23.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:b69bb4f51daf461b15e7b3db033160937d3ff88303a7bc808c67bbc1eaf98c78", size = 5669707, upload-time = "2024-07-15T00:15:03.529Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/52/5a/87d6971f0997c4b9b09c495bf92189fb63de86a83cadc4977dc19735f652/zstandard-0.23.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:034b88913ecc1b097f528e42b539453fa82c3557e414b3de9d5632c80439a473", size = 5201792, upload-time = "2024-07-15T00:15:28.372Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/79/02/6f6a42cc84459d399bd1a4e1adfc78d4dfe45e56d05b072008d10040e13b/zstandard-0.23.0-cp311-cp311-win32.whl", hash = "sha256:f2d4380bf5f62daabd7b751ea2339c1a21d1c9463f1feb7fc2bdcea2c29c3160", size = 430586, upload-time = "2024-07-15T00:15:32.26Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/be/a2/4272175d47c623ff78196f3c10e9dc7045c1b9caf3735bf041e65271eca4/zstandard-0.23.0-cp311-cp311-win_amd64.whl", hash = "sha256:62136da96a973bd2557f06ddd4e8e807f9e13cbb0bfb9cc06cfe6d98ea90dfe0", size = 495420, upload-time = "2024-07-15T00:15:34.004Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7b/83/f23338c963bd9de687d47bf32efe9fd30164e722ba27fb59df33e6b1719b/zstandard-0.23.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b4567955a6bc1b20e9c31612e615af6b53733491aeaa19a6b3b37f3b65477094", size = 788713, upload-time = "2024-07-15T00:15:35.815Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5b/b3/1a028f6750fd9227ee0b937a278a434ab7f7fdc3066c3173f64366fe2466/zstandard-0.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e172f57cd78c20f13a3415cc8dfe24bf388614324d25539146594c16d78fcc8", size = 633459, upload-time = "2024-07-15T00:15:37.995Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/26/af/36d89aae0c1f95a0a98e50711bc5d92c144939efc1f81a2fcd3e78d7f4c1/zstandard-0.23.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0e166f698c5a3e914947388c162be2583e0c638a4703fc6a543e23a88dea3c1", size = 4945707, upload-time = "2024-07-15T00:15:39.872Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cd/2e/2051f5c772f4dfc0aae3741d5fc72c3dcfe3aaeb461cc231668a4db1ce14/zstandard-0.23.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12a289832e520c6bd4dcaad68e944b86da3bad0d339ef7989fb7e88f92e96072", size = 5306545, upload-time = "2024-07-15T00:15:41.75Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0a/9e/a11c97b087f89cab030fa71206963090d2fecd8eb83e67bb8f3ffb84c024/zstandard-0.23.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d50d31bfedd53a928fed6707b15a8dbeef011bb6366297cc435accc888b27c20", size = 5337533, upload-time = "2024-07-15T00:15:44.114Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fc/79/edeb217c57fe1bf16d890aa91a1c2c96b28c07b46afed54a5dcf310c3f6f/zstandard-0.23.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72c68dda124a1a138340fb62fa21b9bf4848437d9ca60bd35db36f2d3345f373", size = 5436510, upload-time = "2024-07-15T00:15:46.509Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/81/4f/c21383d97cb7a422ddf1ae824b53ce4b51063d0eeb2afa757eb40804a8ef/zstandard-0.23.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53dd9d5e3d29f95acd5de6802e909ada8d8d8cfa37a3ac64836f3bc4bc5512db", size = 4859973, upload-time = "2024-07-15T00:15:49.939Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ab/15/08d22e87753304405ccac8be2493a495f529edd81d39a0870621462276ef/zstandard-0.23.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6a41c120c3dbc0d81a8e8adc73312d668cd34acd7725f036992b1b72d22c1772", size = 4936968, upload-time = "2024-07-15T00:15:52.025Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/eb/fa/f3670a597949fe7dcf38119a39f7da49a8a84a6f0b1a2e46b2f71a0ab83f/zstandard-0.23.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:40b33d93c6eddf02d2c19f5773196068d875c41ca25730e8288e9b672897c105", size = 5467179, upload-time = "2024-07-15T00:15:54.971Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4e/a9/dad2ab22020211e380adc477a1dbf9f109b1f8d94c614944843e20dc2a99/zstandard-0.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9206649ec587e6b02bd124fb7799b86cddec350f6f6c14bc82a2b70183e708ba", size = 4848577, upload-time = "2024-07-15T00:15:57.634Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/08/03/dd28b4484b0770f1e23478413e01bee476ae8227bbc81561f9c329e12564/zstandard-0.23.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76e79bc28a65f467e0409098fa2c4376931fd3207fbeb6b956c7c476d53746dd", size = 4693899, upload-time = "2024-07-15T00:16:00.811Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2b/64/3da7497eb635d025841e958bcd66a86117ae320c3b14b0ae86e9e8627518/zstandard-0.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:66b689c107857eceabf2cf3d3fc699c3c0fe8ccd18df2219d978c0283e4c508a", size = 5199964, upload-time = "2024-07-15T00:16:03.669Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/43/a4/d82decbab158a0e8a6ebb7fc98bc4d903266bce85b6e9aaedea1d288338c/zstandard-0.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9c236e635582742fee16603042553d276cca506e824fa2e6489db04039521e90", size = 5655398, upload-time = "2024-07-15T00:16:06.694Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f2/61/ac78a1263bc83a5cf29e7458b77a568eda5a8f81980691bbc6eb6a0d45cc/zstandard-0.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a8fffdbd9d1408006baaf02f1068d7dd1f016c6bcb7538682622c556e7b68e35", size = 5191313, upload-time = "2024-07-15T00:16:09.758Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e7/54/967c478314e16af5baf849b6ee9d6ea724ae5b100eb506011f045d3d4e16/zstandard-0.23.0-cp312-cp312-win32.whl", hash = "sha256:dc1d33abb8a0d754ea4763bad944fd965d3d95b5baef6b121c0c9013eaf1907d", size = 430877, upload-time = "2024-07-15T00:16:11.758Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/75/37/872d74bd7739639c4553bf94c84af7d54d8211b626b352bc57f0fd8d1e3f/zstandard-0.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:64585e1dba664dc67c7cdabd56c1e5685233fbb1fc1966cfba2a340ec0dfff7b", size = 495595, upload-time = "2024-07-15T00:16:13.731Z" }, +]