From 3f69cad404d0a8aad59a96a61bf55e2274786f94 Mon Sep 17 00:00:00 2001 From: ash66 Date: Mon, 18 May 2026 16:32:42 +0800 Subject: [PATCH] Fix SSE route dependency and align architecture docs --- .env | 12 +- .env.development | 29 + .env.example | 23 +- .gitignore | 2 - AGENTS.md | 57 +- QUICK_DEPLOY.md | 21 +- README.md | 43 +- backend/.env | 56 - backend/.env.example | 56 - backend/app/__init__.py | 15 +- backend/app/aliyun_parser/parse_pdf.py | 105 +- backend/app/aliyun_parser/upload_to_milvus.py | 62 +- backend/app/api/__init__.py | 4 +- backend/app/api/main.py | 2 + backend/app/api/models/__init__.py | 20 +- backend/app/api/models/agent.py | 79 + backend/app/api/models/document.py | 18 +- backend/app/api/routes/__init__.py | 21 +- backend/app/api/routes/agent.py | 452 +--- backend/app/api/routes/compliance.py | 72 +- backend/app/api/routes/docs.py | 32 +- backend/app/api/routes/documents.py | 350 +-- backend/app/api/routes/knowledge.py | 99 +- backend/app/api/routes/rag.py | 66 +- backend/app/api/routes/status.py | 42 +- backend/app/application/__init__.py | 5 + backend/app/application/agent/__init__.py | 7 + backend/app/application/agent/services.py | 145 ++ backend/app/application/documents/__init__.py | 7 + backend/app/application/documents/services.py | 186 ++ backend/app/application/knowledge/__init__.py | 7 + backend/app/application/knowledge/services.py | 19 + backend/app/config/__init__.py | 4 +- backend/app/config/logging.py | 12 +- backend/app/config/settings.py | 81 +- backend/app/core/__init__.py | 4 + backend/app/core/config.py | 45 +- backend/app/domain/__init__.py | 5 + backend/app/domain/conversation/__init__.py | 15 + backend/app/domain/conversation/models.py | 53 + backend/app/domain/conversation/ports.py | 78 + backend/app/domain/documents/__init__.py | 17 + backend/app/domain/documents/models.py | 77 + backend/app/domain/documents/ports.py | 96 + backend/app/domain/retrieval/__init__.py | 8 + backend/app/domain/retrieval/models.py | 29 + backend/app/domain/retrieval/ports.py | 60 + backend/app/infrastructure/__init__.py | 5 + .../app/infrastructure/embedding/__init__.py | 5 + .../openai_compatible_embedding_provider.py | 59 + .../llm/openai_compatible_answer_generator.py | 144 ++ backend/app/infrastructure/parser/__init__.py | 5 + .../parser/aliyun_document_parser.py | 55 + .../parser/local_chunk_builder.py | 66 + .../parser/local_document_parser.py | 38 + .../parser/vector_chunk_builder.py | 48 + .../app/infrastructure/session/__init__.py | 5 + .../session/in_memory_conversation_store.py | 95 + .../app/infrastructure/storage/__init__.py | 5 + .../storage/json_document_repository.py | 109 + .../storage/minio_binary_store.py | 47 + .../infrastructure/vectorstore/__init__.py | 5 + .../vectorstore/dense_retriever.py | 24 + .../vectorstore/milvus_vector_index.py | 154 ++ backend/app/main.py | 2 + backend/app/schemas/__init__.py | 4 + backend/app/schemas/compliance.py | 13 + backend/app/schemas/doc.py | 10 + backend/app/schemas/rag.py | 9 + backend/app/services/__init__.py | 2 + backend/app/services/agent/__init__.py | 4 +- backend/app/services/agent/qa_agent.py | 424 +--- backend/app/services/agent/session_manager.py | 105 +- backend/app/services/document_processor.py | 429 +--- backend/app/services/embedding/__init__.py | 18 +- .../app/services/embedding/bge_m3_embedder.py | 130 +- .../app/services/embedding/text_chunker.py | 137 +- backend/app/services/llm/__init__.py | 38 +- backend/app/services/llm/base_client.py | 50 +- backend/app/services/llm/deepseek_client.py | 27 +- .../app/services/llm/document_summarizer.py | 89 +- backend/app/services/llm/llm_factory.py | 95 +- backend/app/services/llm/qwen_client.py | 108 +- backend/app/services/mock_data.py | 38 +- backend/app/services/parser/__init__.py | 4 +- backend/app/services/parser/docx_parser.py | 112 +- backend/app/services/parser/mineru_parser.py | 94 +- backend/app/services/parser/pdf_parser.py | 102 +- backend/app/services/rag/__init__.py | 32 +- backend/app/services/rag/context_builder.py | 88 +- backend/app/services/rag/prompt_templates.py | 35 +- backend/app/services/rag/retriever.py | 220 +- backend/app/services/storage/__init__.py | 18 +- backend/app/services/storage/milvus_client.py | 47 +- backend/app/services/storage/minio_client.py | 120 +- backend/app/shared/__init__.py | 5 + backend/app/shared/bootstrap.py | 117 + backend/app/utils/__init__.py | 4 + backend/app/utils/chunking.py | 12 +- backend/app/utils/logger.py | 6 +- backend/app/workers/__init__.py | 4 +- backend/app/workflows/__init__.py | 4 + backend/app/workflows/compliance_workflow.py | 21 +- backend/app/workflows/rag_workflow.py | 23 +- backend/backend/data/documents.json | 1 + backend/data/documents.json | 25 + backend/main.py | 3 + backend/requirements.txt | 6 +- backend/tests/__init__.py | 4 + dev.bat | 10 +- dev.sh | 21 +- .../backend-project-architecture.md | 112 +- frontend/src/api/compliance.ts | 22 +- frontend/src/api/docs.ts | 53 +- frontend/src/api/index.ts | 167 +- frontend/src/api/status.ts | 8 +- frontend/src/components/common/TPattern.tsx | 4 +- .../src/components/common/ThemeToggle.tsx | 4 +- frontend/src/components/layout/Content.tsx | 4 +- frontend/src/components/layout/Header.tsx | 4 +- frontend/src/components/layout/Tabs.tsx | 7 +- frontend/src/components/ui/Badge.tsx | 4 +- frontend/src/components/ui/Button.tsx | 4 +- frontend/src/components/ui/Card.tsx | 4 +- frontend/src/components/ui/Input.tsx | 4 +- frontend/src/components/ui/ProgressBar.tsx | 4 +- frontend/src/components/ui/ScoreBar.tsx | 4 +- frontend/src/contexts/AppContext.tsx | 21 +- frontend/src/contexts/ThemeContext.tsx | 31 +- frontend/src/contexts/app-context.ts | 10 + frontend/src/contexts/index.ts | 7 +- frontend/src/contexts/theme-context.ts | 11 + frontend/src/contexts/useApp.ts | 11 + frontend/src/contexts/useTheme.ts | 11 + frontend/src/lib/api.ts | 85 - frontend/src/pages/Compliance/ChatPanel.tsx | 4 +- .../src/pages/Compliance/CompliancePage.tsx | 44 +- frontend/src/pages/Docs/DocsPage.tsx | 97 +- frontend/src/pages/RagChat/RagChatPage.tsx | 34 +- frontend/src/pages/Status/StatusPage.tsx | 132 +- frontend/src/types/doc.ts | 3 +- frontend/vite.config.ts | 29 +- pyproject.toml | 8 +- requirements.txt | 14 +- tests/test_embedding.py | 328 +-- tests/test_milvus.py | 222 +- tests/test_parser.py | 327 ++- tests/verify_mvp.py | 366 ++- uv.lock | 2102 ++--------------- 149 files changed, 4786 insertions(+), 5957 deletions(-) create mode 100644 .env.development delete mode 100644 backend/.env delete mode 100644 backend/.env.example create mode 100644 backend/app/api/models/agent.py create mode 100644 backend/app/application/__init__.py create mode 100644 backend/app/application/agent/__init__.py create mode 100644 backend/app/application/agent/services.py create mode 100644 backend/app/application/documents/__init__.py create mode 100644 backend/app/application/documents/services.py create mode 100644 backend/app/application/knowledge/__init__.py create mode 100644 backend/app/application/knowledge/services.py create mode 100644 backend/app/domain/__init__.py create mode 100644 backend/app/domain/conversation/__init__.py create mode 100644 backend/app/domain/conversation/models.py create mode 100644 backend/app/domain/conversation/ports.py create mode 100644 backend/app/domain/documents/__init__.py create mode 100644 backend/app/domain/documents/models.py create mode 100644 backend/app/domain/documents/ports.py create mode 100644 backend/app/domain/retrieval/__init__.py create mode 100644 backend/app/domain/retrieval/models.py create mode 100644 backend/app/domain/retrieval/ports.py create mode 100644 backend/app/infrastructure/__init__.py create mode 100644 backend/app/infrastructure/embedding/__init__.py create mode 100644 backend/app/infrastructure/embedding/openai_compatible_embedding_provider.py create mode 100644 backend/app/infrastructure/llm/openai_compatible_answer_generator.py create mode 100644 backend/app/infrastructure/parser/__init__.py create mode 100644 backend/app/infrastructure/parser/aliyun_document_parser.py create mode 100644 backend/app/infrastructure/parser/local_chunk_builder.py create mode 100644 backend/app/infrastructure/parser/local_document_parser.py create mode 100644 backend/app/infrastructure/parser/vector_chunk_builder.py create mode 100644 backend/app/infrastructure/session/__init__.py create mode 100644 backend/app/infrastructure/session/in_memory_conversation_store.py create mode 100644 backend/app/infrastructure/storage/__init__.py create mode 100644 backend/app/infrastructure/storage/json_document_repository.py create mode 100644 backend/app/infrastructure/storage/minio_binary_store.py create mode 100644 backend/app/infrastructure/vectorstore/__init__.py create mode 100644 backend/app/infrastructure/vectorstore/dense_retriever.py create mode 100644 backend/app/infrastructure/vectorstore/milvus_vector_index.py create mode 100644 backend/app/shared/__init__.py create mode 100644 backend/app/shared/bootstrap.py create mode 100644 backend/backend/data/documents.json create mode 100644 backend/data/documents.json create mode 100644 frontend/src/contexts/app-context.ts create mode 100644 frontend/src/contexts/theme-context.ts create mode 100644 frontend/src/contexts/useApp.ts create mode 100644 frontend/src/contexts/useTheme.ts delete mode 100644 frontend/src/lib/api.ts diff --git a/.env b/.env index dd75d5d..50ec9b3 100644 --- a/.env +++ b/.env @@ -9,7 +9,7 @@ DEBUG=false # ===== Milvus向量数据库配置(已有)===== MILVUS_HOST=localhost MILVUS_PORT=19530 -MILVUS_COLLECTION=regulations +MILVUS_COLLECTION=regulations_dense_1536 MILVUS_DB_NAME=default # ===== MinIO对象存储配置(已有)===== @@ -33,11 +33,11 @@ POSTGRES_PASSWORD=postgresql123456 POSTGRES_DB=compliance_db # ===== 嵌入模型配置 ===== -EMBEDDING_MODEL=BAAI/bge-m3 -EMBEDDING_DIM=1024 -EMBEDDING_MAX_LENGTH=8192 -EMBEDDING_BATCH_SIZE=12 -EMBEDDING_USE_FP16=true +EMBEDDING_MODEL=text-embedding-v3 +EMBEDDING_DIM=1536 +EMBEDDING_API_KEY=sk-fVr9KmDZNC4pGDBQj0EUWz9bDmFzNxjYC9EzZpe2bVDsxtz8 +EMBEDDING_BASE_URL=http://6.86.80.4:30080/v1 +EMBEDDING_TIMEOUT_SECONDS=120 # ===== 文档处理配置 ===== CHUNK_SIZE=512 diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..af54dc6 --- /dev/null +++ b/.env.development @@ -0,0 +1,29 @@ +# Local development overrides for the repo-root backend configuration. +# Keep shared defaults in .env and put machine-specific or remote-dev values here. + +# ===== Milvus向量数据库配置(已有)===== +MILVUS_HOST=6.86.80.8 +MILVUS_PORT=19530 +MILVUS_COLLECTION=regulations_dense_1536 +MILVUS_DB_NAME=default + +# ===== MinIO对象存储配置(已有)===== +MINIO_ENDPOINT=6.86.80.8:9000 +MINIO_ACCESS_KEY=minioadmin +MINIO_SECRET_KEY=minioadmin +MINIO_BUCKET=compliance-docs +MINIO_SECURE=false + +# ===== Redis配置(已有)===== +REDIS_HOST=6.86.80.8 +REDIS_PORT=6379 +REDIS_PASSWORD=redis@123 +REDIS_DB=0 + +# ===== PostgreSQL配置(已有)===== +POSTGRES_HOST=6.86.80.8 +POSTGRES_PORT=5432 +POSTGRES_USER=postgresql +POSTGRES_PASSWORD=postgresql123456 +POSTGRES_DB=compliance_db + diff --git a/.env.example b/.env.example index 25cc72a..1fc375b 100644 --- a/.env.example +++ b/.env.example @@ -9,15 +9,15 @@ DEBUG=false # ===== Milvus向量数据库配置 ===== MILVUS_HOST=localhost MILVUS_PORT=19530 -MILVUS_COLLECTION=regulations +MILVUS_COLLECTION=regulations_dense_1536 MILVUS_DB_NAME=default # ===== 嵌入模型配置 ===== -EMBEDDING_MODEL=BAAI/bge-m3 -EMBEDDING_DIM=1024 -EMBEDDING_MAX_LENGTH=8192 -EMBEDDING_BATCH_SIZE=12 -EMBEDDING_USE_FP16=true +EMBEDDING_MODEL=text-embedding-v3 +EMBEDDING_DIM=1536 +EMBEDDING_API_KEY=your_embedding_api_key_here +EMBEDDING_BASE_URL=http://6.86.80.4:30080/v1 +EMBEDDING_TIMEOUT_SECONDS=120 # ===== MinIO对象存储配置 ===== MINIO_ENDPOINT=localhost:9000 @@ -43,6 +43,12 @@ POSTGRES_DB=compliance_db CHUNK_SIZE=512 CHUNK_OVERLAP=50 MAX_FILE_SIZE_MB=100 +DOCUMENT_METADATA_PATH=backend/data/documents.json + +# ===== 阿里云文档解析 ===== +ALIBABA_ACCESS_KEY_ID=your_aliyun_access_key_id +ALIBABA_ACCESS_KEY_SECRET=your_aliyun_access_key_secret +ALIBABA_ENDPOINT=docmind-api.cn-hangzhou.aliyuncs.com # ===== API服务配置 ===== API_HOST=0.0.0.0 @@ -75,4 +81,7 @@ DEEPSEEK_MODEL=deepseek-v4-flash RAG_TOP_K=10 RAG_MAX_CONTEXT_TOKENS=4000 RAG_SUMMARY_MAX_TOKENS=1024 -RAG_SKILLS_MAX_TOKENS=2048 + +# ===== 会话配置 ===== +SESSION_MAX_SESSIONS=100 +SESSION_TIMEOUT_MINUTES=30 diff --git a/.gitignore b/.gitignore index 85bb67d..1b38ae0 100644 --- a/.gitignore +++ b/.gitignore @@ -39,8 +39,6 @@ nosetests.xml *.py,cover # Environments -.env -.env.* .venv/ venv/ env/ diff --git a/AGENTS.md b/AGENTS.md index c1b903b..cf2c713 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,38 +2,47 @@ ## Scope -- This repo uses `backend/app/` for the backend and `frontend/` for the Vite React app. +- Backend code lives under `backend/app/`; frontend is the Vite app in `frontend/`. -## Real Entrypoints +## Entrypoints -- Current backend app entrypoint is `backend/app/main.py`, exporting `app` from `app.api.main`. -- Current backend dev start command is `python -m uvicorn app.main:app --reload` with `PYTHONPATH=backend`. -- `dev.sh start api --foreground` is the repo script flow that encodes the expected backend startup behavior. -- Frontend dev server is `frontend` Vite on port `5173`, proxying `/api` to `http://localhost:8000`. +- Backend entrypoint is `backend/app/main.py`, which re-exports `app` from `app.api.main`. +- FastAPI mounts the real API under `/api/v1`; health endpoints are `GET /health` and `GET /`. +- Frontend API calls use relative `/api` URLs from `frontend/src/api/index.ts`. +- Current Vite dev proxy in `frontend/vite.config.ts` forwards `/api` to `http://6.86.80.8:8000`, not the local backend. -## Commands +## Preferred Commands -- Backend install: `pip install -r backend/requirements.txt` -- Backend run from repo root: `PYTHONPATH=backend uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload` -- Frontend install: `cd frontend && npm install` -- Frontend dev: `cd frontend && npm run dev` -- Frontend build: `cd frontend && npm run build` -- Frontend lint: `cd frontend && npm run lint` +- Prefer the repo scripts over ad hoc startup commands: `./dev.sh ...` on Unix, `dev.bat ...` on Windows. +- First-time local setup: `./dev.sh setup` or `dev.bat setup`. +- Backend foreground dev server: `./dev.sh start api --foreground` or `dev.bat start api --foreground`. +- Equivalent direct backend run from repo root: `PYTHONPATH=backend uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload`. +- Python tooling must use the repo-root `.venv` environment; do not use the global `python`. +- Prefer `uv`-managed execution from the repo root, for example `uv run --python .venv\\Scripts\\python.exe python ...` or other `uv run ...` forms that resolve within the project environment. +- Frontend dev: `npm --prefix frontend run dev`. +- Frontend verification: `npm --prefix frontend run lint` and `npm --prefix frontend run build`. +- Use `npm`, not `pnpm`; the checked-in scripts run `npm install` even though `frontend/pnpm-lock.yaml` also exists. -## Infra And Env +## Env And Infra -- Backend settings load from root `.env`, not `backend/.env`, because `backend/app/config/settings.py` uses `env_file = ".env"`. -- Docker infra is defined in `docker/docker-compose.yml`; it starts Milvus, MinIO, Redis, and PostgreSQL. -- Default local service ports: Milvus `19530`, MinIO `9000/9001`, Redis `6379`, PostgreSQL `5432`, backend `8000`, frontend `5173`. -- LLM base URLs in `.env.example` point at a shared remote gateway (`http://6.86.80.4:30080/v1`); do not assume offline/local-only LLM execution. +- Backend settings must resolve env files from the repo root only. The supported files are root `.env` and optional root `.env.development`; files under `backend/` must not be treated as authoritative env sources. +- The dev scripts read `API_HOST`, `API_PORT`, `FRONTEND_PORT`, and `FRONTEND_MODE` from root `.env` first, then root `.env.development` as an override; all other backend config comes from the repo-root env files via Pydantic settings. +- Docker infra is `docker/docker-compose.yml`; it brings up `milvus`, `minio`, `redis`, and `postgres`. +- Default ports from config/scripts: backend `8000`, frontend `5173`, Milvus `19530`, MinIO `9000/9001`, Redis `6379`, PostgreSQL `5432`. +- `.env.example` points embedding/LLM base URLs at a shared remote gateway `http://6.86.80.4:30080/v1`; do not assume model inference is local. ## Verification -- Root pytest config in `pyproject.toml` points at root `tests/`, and those tests import from `backend/app` via `PYTHONPATH` setup. -- For frontend-only changes, run `npm run lint` and `npm run build` in `frontend`. -- For backend changes, prefer focused import/startup verification against `backend/app`, and run root `tests/` when the environment supports it. +- Root `pyproject.toml` is the active Python manifest and pytest config; `testpaths = ["tests"]`. +- Tests under `tests/` insert `backend/` into `sys.path` themselves, so targeted runs can be launched from the repo root. +- `tests/test_milvus.py` and `tests/verify_mvp.py` require Milvus and model/runtime dependencies; they are not cheap smoke tests. +- `tests/verify_mvp.py` also expects the `BGEM3Embedder` stack to be available and explicitly mentions `FlagEmbedding`. +- For backend-only changes, prefer focused import/startup checks unless you know the external services and model dependencies are available. -## Gotchas +## Backend Commenting Standard -- Backend settings load from root `.env`, not `backend/.env`, because `backend/app/config/settings.py` uses `env_file = ".env"`. -- The root `pyproject.toml` is the active Python package manifest for the repo. +- All comments and docstrings in `backend/**/*.py` must be written in English. +- Every Python file under `backend` must include a module-level explanation and at least one meaningful `#` code comment. +- Every function and method must include a docstring. +- Files without functions, including `__init__.py`, schemas, enums, dataclasses, and export-only modules, must still include a module docstring, class docstrings when applicable, and at least one meaningful `#` code comment. +- Comments must explain intent, assumptions, invariants, or non-obvious logic; do not add empty, placeholder, or restatement-only comments. diff --git a/QUICK_DEPLOY.md b/QUICK_DEPLOY.md index 994bb18..6fd6aa4 100644 --- a/QUICK_DEPLOY.md +++ b/QUICK_DEPLOY.md @@ -96,19 +96,16 @@ pip install -r backend/requirements.txt --- -## 四、下载嵌入模型 +## 四、配置解析与 embedding -BGE-M3模型约2GB,首次使用需下载。 +当前主链路不再依赖本地 BGE-M3 模型文件,必须配置: -### 方式A:自动下载(联网环境) - -首次启动API时自动下载到 `~/.cache/huggingface/` - -### 方式B:手动下载(离线环境) - -```bash -# 从ModelScope下载 -python -c "from modelscope import snapshot_download; snapshot_download('Xorbits/bge-m3', cache_dir='~/.cache/modelscope')" +```env +ALIBABA_ACCESS_KEY_ID=your_aliyun_access_key_id +ALIBABA_ACCESS_KEY_SECRET=your_aliyun_access_key_secret +EMBEDDING_API_KEY=your_embedding_api_key_here +EMBEDDING_MODEL=text-embedding-v3 +EMBEDDING_DIM=1536 ``` --- @@ -320,7 +317,7 @@ export HF_ENDPOINT=https://hf-mirror.com 或手动下载: ```bash -python -c "from modelscope import snapshot_download; snapshot_download('Xorbits/bge-m3')" +当前版本无需下载本地 BGE-M3 模型;请改为确认 `EMBEDDING_API_KEY` 与阿里云文档解析凭据已配置。 ``` ### Q3: LLM调用失败 diff --git a/README.md b/README.md index c372e3a..677453f 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@ 本次实现的核心功能(最小可用版本): -- ✅ PDF/DOCX文档解析(MinerU + PyMuPDF) -- ✅ 智能分块(章节级+条款级双粒度切割) -- ✅ BGE-M3嵌入(Dense+Sparse双路向量) -- ✅ Milvus向量数据库存储与混合检索 +- ✅ PDF/DOC/DOCX 文档解析(阿里云文档智能) +- ✅ 基于阿里云 `vector_chunks` 的统一切片 +- ✅ OpenAI 兼容 embedding(`text-embedding-v3`,1536维) +- ✅ Milvus 向量数据库存储与 dense-only 检索 - ✅ FastAPI接口封装 ## 项目结构 @@ -19,8 +19,10 @@ AIRegulation-DocAnalysis-Demo/ ├── backend/ │ ├── app/ │ │ ├── api/ # FastAPI 接口层 +│ │ ├── application/ # 用例编排层 +│ │ ├── domain/ # 领域模型与稳定端口 +│ │ ├── infrastructure/ # MinIO / Milvus / 阿里云 / embedding / session 适配 │ │ ├── config/ # 配置与日志 -│ │ ├── services/ # 解析、分块、嵌入、存储、Agent │ │ └── workers/ │ ├── requirements.txt │ └── main.py @@ -52,15 +54,7 @@ docker-compose up -d docker-compose logs -f milvus ``` -### 3. 运行验证脚本 - -```bash -python tests/verify_mvp.py -``` - -根级测试脚本会自动把 `backend/` 加入导入路径,并从 `app.*` 加载当前后端代码。 - -### 4. 启动API服务 +### 3. 启动API服务 ```bash PYTHONPATH=backend uvicorn app.main:app --reload --port 8000 @@ -91,11 +85,11 @@ curl -X POST http://localhost:8000/api/v1/knowledge/search \ | 类别 | 技术 | |------|------| -| 文档解析 | MinerU + PyMuPDF + python-docx | -| 分块策略 | 章节级+条款级双粒度切割 | -| 嵌入模型 | BGE-M3(1024维 Dense + Sparse) | +| 文档解析 | 阿里云文档智能 + python-docx | +| 分块策略 | 阿里云 `vector_chunks` | +| 嵌入模型 | `text-embedding-v3`(1536维 Dense) | | 向量数据库 | Milvus 2.4(本地Docker部署) | -| 检索方式 | Dense+Sparse混合检索 + RRF融合 | +| 检索方式 | Dense-only 检索 | | API框架 | FastAPI | ## 配置 @@ -107,9 +101,14 @@ curl -X POST http://localhost:8000/api/v1/knowledge/search \ MILVUS_HOST=localhost MILVUS_PORT=19530 -# 嵌入模型配置 -EMBEDDING_MODEL=BAAI/bge-m3 -EMBEDDING_DIM=1024 +# 阿里云文档解析 +ALIBABA_ACCESS_KEY_ID=your_aliyun_access_key_id +ALIBABA_ACCESS_KEY_SECRET=your_aliyun_access_key_secret + +# embedding 配置 +EMBEDDING_MODEL=text-embedding-v3 +EMBEDDING_DIM=1536 +EMBEDDING_API_KEY=your_embedding_api_key_here # 分块配置 CHUNK_SIZE=512 @@ -117,7 +116,7 @@ CHUNK_SIZE=512 ## 后续迭代(不在本次MVP范围) -- LLM摘要生成(DeepSeek/Qwen API) +- LLM摘要生成(当前上传主链路默认不生成) - 文档上传UI界面 - 混合检索问答功能 - 法规变更监控与自动更新 diff --git a/backend/.env b/backend/.env deleted file mode 100644 index ac894d9..0000000 --- a/backend/.env +++ /dev/null @@ -1,56 +0,0 @@ -APP_NAME=AI+合规智能中枢 -APP_VERSION=0.1.0 -DEBUG=false - -MILVUS_HOST=localhost -MILVUS_PORT=19530 -MILVUS_COLLECTION=regulations -MILVUS_DB_NAME=default - -MINIO_ENDPOINT=localhost:9000 -MINIO_ACCESS_KEY=minioadmin -MINIO_SECRET_KEY=minioadmin -MINIO_BUCKET=compliance-docs -MINIO_SECURE=false - -REDIS_HOST=localhost -REDIS_PORT=6379 -REDIS_PASSWORD=redis@123 -REDIS_DB=0 - -POSTGRES_HOST=localhost -POSTGRES_PORT=5432 -POSTGRES_USER=postgresql -POSTGRES_PASSWORD=postgresql123456 -POSTGRES_DB=compliance_db - -EMBEDDING_MODEL=BAAI/bge-m3 -EMBEDDING_DIM=1024 -EMBEDDING_MAX_LENGTH=8192 -EMBEDDING_BATCH_SIZE=12 -EMBEDDING_USE_FP16=true - -CHUNK_SIZE=512 -CHUNK_OVERLAP=50 -MAX_FILE_SIZE_MB=100 - -API_HOST=0.0.0.0 -API_PORT=8000 - -LLM_PROVIDER=deepseek -LLM_MODEL=deepseek-v4-flash -LLM_MAX_TOKENS=4096 -LLM_TEMPERATURE=0.7 - -QWEN_API_KEY=sk-fVr9KmDZNC4pGDBQj0EUWz9bDmFzNxjYC9EzZpe2bVDsxtz8 -QWEN_BASE_URL=http://6.86.80.4:30080/v1 -QWEN_MODEL=qwen3.5-plus -QWEN_VL_MODEL=qwen3-vl-plus - -DEEPSEEK_API_KEY=sk-fVr9KmDZNC4pGDBQj0EUWz9bDmFzNxjYC9EzZpe2bVDsxtz8 -DEEPSEEK_BASE_URL=http://6.86.80.4:30080/v1 -DEEPSEEK_MODEL=deepseek-v4-flash - -RAG_TOP_K=10 -RAG_MAX_CONTEXT_TOKENS=4000 -RAG_SUMMARY_MAX_TOKENS=1024 diff --git a/backend/.env.example b/backend/.env.example deleted file mode 100644 index d885b18..0000000 --- a/backend/.env.example +++ /dev/null @@ -1,56 +0,0 @@ -APP_NAME=AI+合规智能中枢 -APP_VERSION=0.1.0 -DEBUG=false - -MILVUS_HOST=localhost -MILVUS_PORT=19530 -MILVUS_COLLECTION=regulations -MILVUS_DB_NAME=default - -EMBEDDING_MODEL=BAAI/bge-m3 -EMBEDDING_DIM=1024 -EMBEDDING_MAX_LENGTH=8192 -EMBEDDING_BATCH_SIZE=12 -EMBEDDING_USE_FP16=true - -MINIO_ENDPOINT=localhost:9000 -MINIO_ACCESS_KEY=minioadmin -MINIO_SECRET_KEY=minioadmin123 -MINIO_BUCKET=compliance-docs -MINIO_SECURE=false - -REDIS_HOST=localhost -REDIS_PORT=6379 -REDIS_PASSWORD= -REDIS_DB=0 - -POSTGRES_HOST=localhost -POSTGRES_PORT=5432 -POSTGRES_USER=compliance -POSTGRES_PASSWORD=compliance123 -POSTGRES_DB=compliance_db - -CHUNK_SIZE=512 -CHUNK_OVERLAP=50 -MAX_FILE_SIZE_MB=100 - -API_HOST=0.0.0.0 -API_PORT=8000 - -LLM_PROVIDER=deepseek -LLM_MODEL=deepseek-v4-flash -LLM_MAX_TOKENS=4096 -LLM_TEMPERATURE=0.7 - -QWEN_API_KEY=your_api_key_here -DEEPSEEK_API_KEY=your_api_key_here -QWEN_BASE_URL=http://6.86.80.4:30080/v1 -DEEPSEEK_BASE_URL=http://6.86.80.4:30080/v1 - -QWEN_MODEL=qwen3.5-plus -QWEN_VL_MODEL=qwen3-vl-plus -DEEPSEEK_MODEL=deepseek-v4-flash - -RAG_TOP_K=10 -RAG_MAX_CONTEXT_TOKENS=4000 -RAG_SUMMARY_MAX_TOKENS=1024 diff --git a/backend/app/__init__.py b/backend/app/__init__.py index f01a49c..38208a5 100644 --- a/backend/app/__init__.py +++ b/backend/app/__init__.py @@ -1,3 +1,14 @@ -from .main import app +"""Initialize the app package.""" +# Keep package boundaries explicit so backend imports stay predictable. -__all__ = ["app"] \ No newline at end of file + +__all__ = ["app"] + + +def __getattr__(name: str): + """Handle getattr for this module.""" + if name == "app": + from .main import app + + return app + raise AttributeError(name) diff --git a/backend/app/aliyun_parser/parse_pdf.py b/backend/app/aliyun_parser/parse_pdf.py index 9dcc3fc..917027e 100644 --- a/backend/app/aliyun_parser/parse_pdf.py +++ b/backend/app/aliyun_parser/parse_pdf.py @@ -1,14 +1,10 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -""" -阿里云文档智能 API 解析 PDF,输出三层结构 chunks -- structure_nodes: 目录树结构 -- semantic_blocks: 语义块(章节文本、表格、图片) -- vector_chunks: 检索块(带 overlap 切分) -""" +"""Handle Aliyun parsing support for parse pdf.""" import argparse import json +import os import re import time from pathlib import Path @@ -19,16 +15,16 @@ from alibabacloud_tea_openapi import models as open_api_models from alibabacloud_docmind_api20220711 import models as docmind_models from alibabacloud_tea_util import models as util_models -# ===================== 阿里云配置 ===================== -ALIBABA_ACCESS_KEY_ID = "LTAI5t6fWvAsvZkoF9WTbtys" -ALIBABA_ACCESS_KEY_SECRET = "WX4oaE4FLYRa5L85TMQkqRPHeTJAF0" -ALIBABA_ENDPOINT = "docmind-api.cn-hangzhou.aliyuncs.com" +# Keep parser integration steps explicit so external workflow behavior stays traceable. +ALIBABA_ACCESS_KEY_ID = os.getenv("ALIBABA_ACCESS_KEY_ID", "") +ALIBABA_ACCESS_KEY_SECRET = os.getenv("ALIBABA_ACCESS_KEY_SECRET", "") +ALIBABA_ENDPOINT = os.getenv("ALIBABA_ENDPOINT", "docmind-api.cn-hangzhou.aliyuncs.com") -# ===================== 切分参数 ===================== +# Keep parser integration steps explicit so external workflow behavior stays traceable. MAX_CHARS = 600 OVERLAP_CHARS = 80 -# ===================== 布局类型常量 ===================== +# Keep parser integration steps explicit so external workflow behavior stays traceable. TOC_TITLES = {"目次", "目录"} TITLE_SUBTYPES = {"doc_title", "para_title"} TEXT_SUBTYPES = {"para", "none"} @@ -36,8 +32,11 @@ FIGURE_TYPES = {"figure", "figure_name", "figure_note"} FIGURE_SUBTYPES = {"picture", "pic_title", "pic_caption"} -# ===================== 阿里云 API 客户端 ===================== +# Keep parser integration steps explicit so external workflow behavior stays traceable. def init_client() -> DocmindClient: + """Handle init client.""" + if not ALIBABA_ACCESS_KEY_ID or not ALIBABA_ACCESS_KEY_SECRET: + raise ValueError("缺少阿里云文档解析凭据,请设置 ALIBABA_ACCESS_KEY_ID 和 ALIBABA_ACCESS_KEY_SECRET") config = open_api_models.Config( access_key_id=ALIBABA_ACCESS_KEY_ID, access_key_secret=ALIBABA_ACCESS_KEY_SECRET, @@ -47,7 +46,7 @@ def init_client() -> DocmindClient: def submit_job(client: DocmindClient, file_path: str) -> str: - """提交文档解析任务""" + """Submit job.""" file_name = Path(file_path).name request = docmind_models.SubmitDocParserJobAdvanceRequest( file_url_object=open(file_path, "rb"), @@ -62,14 +61,14 @@ def submit_job(client: DocmindClient, file_path: str) -> str: def query_status(client: DocmindClient, task_id: str) -> Dict: - """查询任务状态""" + """Handle query status.""" request = docmind_models.QueryDocParserStatusRequest(id=task_id) response = client.query_doc_parser_status(request) return response.body.data.to_map() if response.body.data else None def wait_for_completion(client: DocmindClient, task_id: str, poll_interval: int = 5) -> bool: - """等待任务完成""" + """Wait for for completion.""" while True: status_data = query_status(client, task_id) if not status_data: @@ -85,7 +84,7 @@ def wait_for_completion(client: DocmindClient, task_id: str, poll_interval: int def get_result(client: DocmindClient, task_id: str, layout_num: int = 0, layout_step_size: int = 50) -> Dict: - """获取解析结果""" + """Return result.""" request = docmind_models.GetDocParserResultRequest( id=task_id, layout_step_size=layout_step_size, @@ -96,7 +95,7 @@ def get_result(client: DocmindClient, task_id: str, layout_num: int = 0, layout_ def collect_all_results(client: DocmindClient, task_id: str, layout_step_size: int = 50) -> List[Dict]: - """收集所有解析结果""" + """Collect all results.""" all_layouts = [] layout_num = 0 while True: @@ -113,8 +112,9 @@ def collect_all_results(client: DocmindClient, task_id: str, layout_step_size: i return all_layouts -# ===================== 文本处理 ===================== +# Keep parser integration steps explicit so external workflow behavior stays traceable. def normalize_text(text: str) -> str: + """Normalize text.""" text = text.replace("\r", "\n") text = text.replace(" ", " ") text = re.sub(r"\n+", "\n", text) @@ -123,34 +123,41 @@ def normalize_text(text: str) -> str: def get_page(layout: Dict) -> int: + """Return page.""" return layout.get("pageNum", layout.get("pageNumber", 0)) def get_text(layout: Dict) -> str: + """Return text.""" text = normalize_text(layout.get("text", "")) if text: return text return normalize_text(layout.get("markdownContent", "")) -# ===================== 布局类型判断 ===================== +# Keep parser integration steps explicit so external workflow behavior stays traceable. def is_title(layout: Dict) -> bool: + """Return whether title.""" return layout.get("type") == "title" or layout.get("subType") in TITLE_SUBTYPES def is_text(layout: Dict) -> bool: + """Return whether text.""" return layout.get("type") == "text" and layout.get("subType", "none") in TEXT_SUBTYPES def is_figure(layout: Dict) -> bool: + """Return whether figure.""" return layout.get("type") in FIGURE_TYPES or layout.get("subType") in FIGURE_SUBTYPES def is_table(layout: Dict) -> bool: + """Return whether table.""" return layout.get("type") == "table" def is_toc_layout(layout: Dict) -> bool: + """Return whether toc layout.""" text = get_text(layout) if text in TOC_TITLES: return True @@ -160,6 +167,7 @@ def is_toc_layout(layout: Dict) -> bool: def extract_table_text(layout: Dict) -> str: + """Extract table text.""" rows = [] for cell in layout.get("cells", []): texts = [] @@ -172,8 +180,9 @@ def extract_table_text(layout: Dict) -> str: return "\n".join(rows).strip() -# ===================== 结构层:目录树 ===================== +# Keep parser integration steps explicit so external workflow behavior stays traceable. def build_structure_nodes(layouts: List[Dict]) -> List[Dict]: + """Build structure nodes.""" nodes = [] for layout in layouts: if not is_title(layout): @@ -195,8 +204,9 @@ def build_structure_nodes(layouts: List[Dict]) -> List[Dict]: return nodes -# ===================== 语义层:章节内容 ===================== +# Keep parser integration steps explicit so external workflow behavior stays traceable. def update_section_path(section_stack: List[Dict], layout: Dict) -> List[Dict]: + """Update section path.""" level = layout.get("level", 0) title = get_text(layout) while section_stack and section_stack[-1]["level"] >= level: @@ -213,10 +223,12 @@ def update_section_path(section_stack: List[Dict], layout: Dict) -> List[Dict]: def section_path_titles(section_stack: List[Dict]) -> List[str]: + """Handle section path titles.""" return [item["title"] for item in section_stack] def flush_text_block(blocks: List[Dict], semantic_blocks: List[Dict], block_id: int) -> int: + """Handle flush text block.""" if not blocks: return block_id @@ -242,6 +254,7 @@ def flush_text_block(blocks: List[Dict], semantic_blocks: List[Dict], block_id: def build_semantic_blocks(layouts: List[Dict]) -> List[Dict]: + """Build semantic blocks.""" semantic_blocks = [] section_stack = [] pending_text_blocks = [] @@ -327,8 +340,9 @@ def build_semantic_blocks(layouts: List[Dict]) -> List[Dict]: return semantic_blocks -# ===================== 检索层:向量 chunks ===================== +# Keep parser integration steps explicit so external workflow behavior stays traceable. def split_text_with_overlap(text: str, max_chars: int, overlap_chars: int) -> List[str]: + """Handle split text with overlap.""" text = text.strip() if len(text) <= max_chars: return [text] if text else [] @@ -351,6 +365,7 @@ def build_vector_chunks( max_chars: int, overlap_chars: int, ) -> List[Dict]: + """Build vector chunks.""" vector_chunks = [] chunk_index = 1 @@ -385,7 +400,31 @@ def build_vector_chunks( return vector_chunks -# ===================== 主转换函数 ===================== +def parse_pdf_to_structured_chunks( + pdf_path: str, + *, + doc_id: str, + doc_title: str, + max_chars: int = MAX_CHARS, + overlap_chars: int = OVERLAP_CHARS, + poll_interval: int = 5, +) -> Dict: + """Parse pdf to structured chunks.""" + client = init_client() + task_id = submit_job(client, pdf_path) + if not wait_for_completion(client, task_id, poll_interval): + raise RuntimeError("阿里云文档解析任务失败") + layouts = collect_all_results(client, task_id) + return convert_layouts( + layouts, + doc_id=doc_id, + doc_title=doc_title, + max_chars=max_chars, + overlap_chars=overlap_chars, + ) + + +# Keep parser integration steps explicit so external workflow behavior stays traceable. def convert_layouts( layouts: List[Dict], doc_id: str, @@ -393,6 +432,7 @@ def convert_layouts( max_chars: int, overlap_chars: int, ) -> Dict: + """Handle convert layouts.""" structure_nodes = build_structure_nodes(layouts) semantic_blocks = build_semantic_blocks(layouts) vector_chunks = build_vector_chunks( @@ -411,8 +451,9 @@ def convert_layouts( } -# ===================== CLI 入口 ===================== +# Keep parser integration steps explicit so external workflow behavior stays traceable. def main() -> None: + """Run the module entrypoint.""" parser = argparse.ArgumentParser(description="阿里云文档智能解析 PDF,输出三层结构 chunks") parser.add_argument("pdf_path", help="PDF 文件路径") parser.add_argument("--out", default="vector_chunks.json", help="输出 JSON 文件路径") @@ -428,30 +469,30 @@ def main() -> None: if not pdf_path.exists(): raise FileNotFoundError(f"PDF 文件不存在: {pdf_path}") - # 1. 提交阿里云任务 + # Keep parser integration steps explicit so external workflow behavior stays traceable. client = init_client() print(f"提交任务: {pdf_path}") task_id = submit_job(client, str(pdf_path)) print(f"任务 ID: {task_id}") - # 2. 等待完成 + # Keep parser integration steps explicit so external workflow behavior stays traceable. print("等待任务完成...") if not wait_for_completion(client, task_id, args.poll_interval): print("任务失败,退出") return - # 3. 获取 layouts + # Keep parser integration steps explicit so external workflow behavior stays traceable. print("获取解析结果...") layouts = collect_all_results(client, task_id) print(f"获取到 {len(layouts)} 个布局块") - # 4. 输出原始 layouts(可选) + # Keep parser integration steps explicit so external workflow behavior stays traceable. if args.layouts_output: layouts_path = Path(args.layouts_output).expanduser().resolve() layouts_path.write_text(json.dumps(layouts, ensure_ascii=False, indent=2), encoding="utf-8") print(f"原始 layouts 已写入: {layouts_path}") - # 5. 转换为三层结构 + # Keep parser integration steps explicit so external workflow behavior stays traceable. print("转换为三层结构...") data = convert_layouts( layouts, @@ -461,7 +502,7 @@ def main() -> None: overlap_chars=args.overlap_chars, ) - # 6. 输出结果 + # Keep parser integration steps explicit so external workflow behavior stays traceable. output_path = Path(args.out).expanduser().resolve() output_path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8") @@ -472,4 +513,4 @@ def main() -> None: if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/backend/app/aliyun_parser/upload_to_milvus.py b/backend/app/aliyun_parser/upload_to_milvus.py index 0188879..63ca2b0 100644 --- a/backend/app/aliyun_parser/upload_to_milvus.py +++ b/backend/app/aliyun_parser/upload_to_milvus.py @@ -1,9 +1,6 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -""" -将 vector_chunks.json 向量化并上传到 Milvus 和 PostgreSQL -使用中转站的 OpenAI 兼容 API -""" +"""Handle Aliyun parsing support for upload to milvus.""" import argparse import json @@ -23,18 +20,18 @@ from pymilvus import ( ) from openai import OpenAI -# ===================== 配置 ===================== -# 中转站配置 +# Keep parser integration steps explicit so external workflow behavior stays traceable. +# Keep parser integration steps explicit so external workflow behavior stays traceable. RELAY_BASE_URL = "http://6.86.80.4:30080/v1" RELAY_API_KEY = "sk-5HeY7gfSIlyZMacfuXOf5cphpymsNqufEu1ou4U3avbULcyY" -EMBEDDING_MODEL = "text-embedding-v3" # 中转站支持的 embedding 模型 +EMBEDDING_MODEL = "text-embedding-v3" # Keep parser integration steps explicit so external workflow behavior stays traceable. -# Milvus 配置 +# Keep parser integration steps explicit so external workflow behavior stays traceable. MILVUS_HOST = "localhost" MILVUS_PORT = "19530" COLLECTION_NAME = "regulation_chunks" -# PostgreSQL 配置 +# Keep parser integration steps explicit so external workflow behavior stays traceable. PG_HOST = "6.86.80.10" PG_PORT = 5432 PG_USER = "postgresql" @@ -44,12 +41,12 @@ PG_DATABASE = "postgres" # ===================== Embedding ===================== def get_openai_client(api_key: str, base_url: str) -> OpenAI: - """创建 OpenAI 客户端连接到中转站""" + """Return openai client.""" return OpenAI(api_key=api_key, base_url=base_url) def get_embeddings_batch(client: OpenAI, texts: List[str], batch_size: int = 10) -> List[List[float]]: - """批量获取文本向量""" + """Return embeddings batch.""" all_embeddings = [] for i in range(0, len(texts), batch_size): @@ -69,12 +66,13 @@ def get_embeddings_batch(client: OpenAI, texts: List[str], batch_size: int = 10) # ===================== Milvus ===================== def init_milvus(host: str, port: str): + """Handle init milvus.""" connections.connect("default", host=host, port=port) print(f"已连接 Milvus: {host}:{port}") def create_collection(name: str, dim: int) -> Collection: - """创建或获取 collection""" + """Create collection.""" if utility.has_collection(name): print(f"Collection '{name}' 已存在,删除重建") utility.drop_collection(name) @@ -90,14 +88,14 @@ def create_collection(name: str, dim: int) -> Collection: FieldSchema(name="page_end", dtype=DataType.INT64), FieldSchema(name="section_title", dtype=DataType.VARCHAR, max_length=512), FieldSchema(name="text", dtype=DataType.VARCHAR, max_length=2048), - FieldSchema(name="source_ids", dtype=DataType.VARCHAR, max_length=4096), # JSON 字符串 + FieldSchema(name="source_ids", dtype=DataType.VARCHAR, max_length=4096), # Keep parser integration steps explicit so external workflow behavior stays traceable. FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=dim), ] schema = CollectionSchema(fields, description="法规文档检索 chunks") collection = Collection(name, schema) - # 创建向量索引(IVF_FLAT,适合中小规模) + # Keep parser integration steps explicit so external workflow behavior stays traceable. index_params = { "metric_type": "COSINE", "index_type": "IVF_FLAT", @@ -110,7 +108,7 @@ def create_collection(name: str, dim: int) -> Collection: def insert_chunks(collection: Collection, chunks: List[Dict], embeddings: List[List[float]]): - """插入 chunks 到 Milvus""" + """Handle insert chunks.""" data = [ [c["chunk_id"] for c in chunks], [c["doc_id"] for c in chunks], @@ -122,7 +120,7 @@ def insert_chunks(collection: Collection, chunks: List[Dict], embeddings: List[L [c["page_end"] for c in chunks], [c["section_title"] for c in chunks], [c["text"] for c in chunks], - [json.dumps(c.get("source_ids", [])) for c in chunks], # JSON 字符串 + [json.dumps(c.get("source_ids", [])) for c in chunks], # Keep parser integration steps explicit so external workflow behavior stays traceable. embeddings, ] @@ -132,14 +130,14 @@ def insert_chunks(collection: Collection, chunks: List[Dict], embeddings: List[L def load_collection(collection: Collection): - """加载 collection 到内存(搜索前必须)""" + """Load collection.""" collection.load() print(f"Collection 已加载到内存") # ===================== PostgreSQL ===================== def get_pg_connection(host: str, port: int, user: str, password: str, database: str): - """获取 PostgreSQL 连接""" + """Return pg connection.""" conn = psycopg2.connect( host=host, port=port, @@ -152,18 +150,18 @@ def get_pg_connection(host: str, port: int, user: str, password: str, database: def insert_chunks_to_pg(conn, chunks: List[Dict], doc_data: Dict): - """插入 chunks 和相关数据到 PostgreSQL""" + """Handle insert chunks to pg.""" cursor = conn.cursor() try: - # 1. 插入文档 + # Keep parser integration steps explicit so external workflow behavior stays traceable. cursor.execute(""" INSERT INTO documents (doc_id, title, standard_number, upload_time) VALUES (%s, %s, %s, NOW()) ON CONFLICT (doc_id) DO UPDATE SET title = EXCLUDED.title, updated_at = NOW() """, (doc_data["doc_id"], doc_data["doc_title"], doc_data.get("standard_number"))) - # 2. 插入语义块 + # Keep parser integration steps explicit so external workflow behavior stays traceable. semantic_blocks = doc_data.get("semantic_blocks", []) if semantic_blocks: block_rows = [ @@ -192,7 +190,7 @@ def insert_chunks_to_pg(conn, chunks: List[Dict], doc_data: Dict): ) print(f"已插入 {len(semantic_blocks)} 个语义块") - # 3. 插入向量块元数据 + # Keep parser integration steps explicit so external workflow behavior stays traceable. chunk_rows = [ ( doc_data["doc_id"], @@ -230,9 +228,9 @@ def insert_chunks_to_pg(conn, chunks: List[Dict], doc_data: Dict): cursor.close() -# ===================== 主流程 ===================== +# Keep parser integration steps explicit so external workflow behavior stays traceable. def load_data(file_path: Path) -> Dict: - """加载 vector_chunks.json,返回完整数据""" + """Load data.""" data = json.loads(file_path.read_text(encoding="utf-8")) return data @@ -251,7 +249,8 @@ def upload_to_milvus_and_pg( pg_password: str, pg_database: str, ): - # 1. 加载完整数据 + # Keep parser integration steps explicit so external workflow behavior stays traceable. + """Handle upload to milvus and pg.""" chunks_path = Path(chunks_file).expanduser().resolve() if not chunks_path.exists(): raise FileNotFoundError(f"文件不存在: {chunks_path}") @@ -262,29 +261,29 @@ def upload_to_milvus_and_pg( raise ValueError("vector_chunks 为空") print(f"加载 {len(chunks)} 个 chunks") - # 2. 初始化连接 + # Keep parser integration steps explicit so external workflow behavior stays traceable. client = get_openai_client(api_key, base_url) init_milvus(milvus_host, milvus_port) pg_conn = get_pg_connection(pg_host, pg_port, pg_user, pg_password, pg_database) - # 3. 获取 embeddings + # Keep parser integration steps explicit so external workflow behavior stays traceable. texts = [c["embedding_text"] for c in chunks] embeddings = get_embeddings_batch(client, texts, batch_size) print(f"生成 {len(embeddings)} 个向量") - # 4. 获取 embedding 维度 + # Keep parser integration steps explicit so external workflow behavior stays traceable. embedding_dim = len(embeddings[0]) print(f"Embedding 维度: {embedding_dim}") - # 5. 创建 collection 并插入 Milvus + # Keep parser integration steps explicit so external workflow behavior stays traceable. collection = create_collection(collection_name, embedding_dim) insert_chunks(collection, chunks, embeddings) load_collection(collection) - # 6. 插入 PostgreSQL + # Keep parser integration steps explicit so external workflow behavior stays traceable. insert_chunks_to_pg(pg_conn, chunks, data) - # 7. 关闭连接 + # Keep parser integration steps explicit so external workflow behavior stays traceable. pg_conn.close() print("上传完成!") @@ -292,6 +291,7 @@ def upload_to_milvus_and_pg( # ===================== CLI ===================== def main(): + """Run the module entrypoint.""" parser = argparse.ArgumentParser(description="将 vector_chunks 向量化并上传到 Milvus 和 PostgreSQL") parser.add_argument("chunks_file", help="vector_chunks.json 文件路径") parser.add_argument("--api-key", default=RELAY_API_KEY, help="中转站 API Key") diff --git a/backend/app/api/__init__.py b/backend/app/api/__init__.py index 279c123..50eedf0 100644 --- a/backend/app/api/__init__.py +++ b/backend/app/api/__init__.py @@ -1 +1,3 @@ -"""API接口模块""" +"""Initialize the app.api package.""" +# Keep package boundaries explicit so backend imports stay predictable. + diff --git a/backend/app/api/main.py b/backend/app/api/main.py index daa5d43..7f7e48f 100644 --- a/backend/app/api/main.py +++ b/backend/app/api/main.py @@ -12,6 +12,8 @@ from app.api.routes import api_router from app.config.logging import setup_logging from app.config.settings import settings from app.services.llm.llm_factory import LLMFactory +# Keep module behavior explicit so the backend flow stays easy to audit. + setup_logging(level="INFO" if not settings.debug else "DEBUG") diff --git a/backend/app/api/models/__init__.py b/backend/app/api/models/__init__.py index 9d0c983..b382102 100644 --- a/backend/app/api/models/__init__.py +++ b/backend/app/api/models/__init__.py @@ -1,5 +1,14 @@ -"""API数据模型""" +"""Initialize the app.api.models package.""" +from .agent import ( + AskRequest, + AskResponse, + ChatRequest, + ChatResponse, + FeedbackRequest, + SessionInfo, + TemplateListResponse, +) from .document import ( DocumentUploadRequest, DocumentUploadResponse, @@ -9,8 +18,17 @@ from .document import ( DocumentStatusResponse, ErrorResponse ) +# Keep package boundaries explicit so backend imports stay predictable. + __all__ = [ + "AskRequest", + "AskResponse", + "ChatRequest", + "ChatResponse", + "FeedbackRequest", + "SessionInfo", + "TemplateListResponse", "DocumentUploadRequest", "DocumentUploadResponse", "SearchRequest", diff --git a/backend/app/api/models/agent.py b/backend/app/api/models/agent.py new file mode 100644 index 0000000..ac23486 --- /dev/null +++ b/backend/app/api/models/agent.py @@ -0,0 +1,79 @@ +"""Define API models for agent endpoints.""" + +from __future__ import annotations + +from typing import Dict, List, Optional + +from pydantic import BaseModel, Field + +# Group agent transport schemas together so route modules stay focused on HTTP flow. + + +class AskRequest(BaseModel): + """Define the Ask Request API model.""" + + query: str = Field(..., min_length=1, max_length=2000) + filters: Optional[str] = None + provider: Optional[str] = None + model: Optional[str] = None + top_k: Optional[int] = Field(default=None, ge=1, le=20) + prompt_template: Optional[str] = None + + +class AskResponse(BaseModel): + """Define the Ask Response API model.""" + + answer: str + sources: List[Dict] = Field(default_factory=list) + model: str = "" + latency_ms: int = 0 + retrieved_count: int = 0 + context_tokens: int = 0 + truncated: bool = False + error: Optional[str] = None + + +class ChatRequest(BaseModel): + """Define the Chat Request API model.""" + + query: str = Field(..., min_length=1, max_length=2000) + session_id: Optional[str] = None + filters: Optional[str] = None + provider: Optional[str] = None + model: Optional[str] = None + top_k: Optional[int] = Field(default=None, ge=1, le=20) + + +class ChatResponse(BaseModel): + """Define the Chat Response API model.""" + + session_id: str + answer: str + sources: List[Dict] = Field(default_factory=list) + model: str = "" + latency_ms: int = 0 + message_count: int = 0 + + +class SessionInfo(BaseModel): + """Define the Session Info API model.""" + + session_id: str + message_count: int + created_at: int + updated_at: int + + +class FeedbackRequest(BaseModel): + """Define the Feedback Request API model.""" + + session_id: str + message_index: int + rating: int = Field(..., ge=1, le=5) + comment: Optional[str] = None + + +class TemplateListResponse(BaseModel): + """Define the Template List Response API model.""" + + templates: Dict[str, str] diff --git a/backend/app/api/models/document.py b/backend/app/api/models/document.py index b7c2aed..c3e175f 100644 --- a/backend/app/api/models/document.py +++ b/backend/app/api/models/document.py @@ -1,19 +1,21 @@ -"""文档相关Pydantic数据模型""" +"""Define API models for document.""" from pydantic import BaseModel, Field from typing import List, Optional, Dict, Any from datetime import datetime +# Group related schema definitions so validation rules stay consistent. + class DocumentUploadRequest(BaseModel): - """文档上传请求""" + """Define the Document Upload Request API model.""" doc_name: Optional[str] = Field(None, description="文档名称") regulation_type: Optional[str] = Field(None, description="法规类型") version: Optional[str] = Field(None, description="文档版本") class DocumentUploadResponse(BaseModel): - """文档上传响应""" + """Define the Document Upload Response API model.""" doc_id: str = Field(..., description="文档ID") doc_name: str = Field(..., description="文档名称") status: str = Field(..., description="处理状态") @@ -25,14 +27,14 @@ class DocumentUploadResponse(BaseModel): class SearchRequest(BaseModel): - """检索请求""" + """Define the Search Request API model.""" query: str = Field(..., description="查询文本") top_k: int = Field(default=10, description="返回结果数量") filters: Optional[str] = Field(None, description="过滤条件") class SearchResultItem(BaseModel): - """单个检索结果""" + """Define the Search Result Item API model.""" id: int = Field(..., description="记录ID") content: str = Field(..., description="内容") score: float = Field(..., description="相似度分数") @@ -40,7 +42,7 @@ class SearchResultItem(BaseModel): class SearchResponse(BaseModel): - """检索响应""" + """Define the Search Response API model.""" query: str = Field(..., description="查询文本") total: int = Field(..., description="结果总数") results: List[SearchResultItem] = Field(default_factory=list, description="结果列表") @@ -48,7 +50,7 @@ class SearchResponse(BaseModel): class DocumentStatusResponse(BaseModel): - """文档状态响应""" + """Define the Document Status Response API model.""" doc_id: str = Field(..., description="文档ID") status: str = Field(..., description="状态") num_chunks: Optional[int] = Field(None, description="分块数量") @@ -56,7 +58,7 @@ class DocumentStatusResponse(BaseModel): class ErrorResponse(BaseModel): - """错误响应""" + """Define the Error Response API model.""" error: str = Field(..., description="错误类型") message: str = Field(..., description="错误消息") timestamp: datetime = Field(default_factory=datetime.now, description="时间戳") diff --git a/backend/app/api/routes/__init__.py b/backend/app/api/routes/__init__.py index e43a8db..5832c19 100644 --- a/backend/app/api/routes/__init__.py +++ b/backend/app/api/routes/__init__.py @@ -1,16 +1,29 @@ -"""API路由模块""" +"""Initialize the app.api.routes package.""" from fastapi import APIRouter +from .compliance import router as compliance_router from .documents import router as documents_router from .knowledge import router as knowledge_router from .agent import router as agent_router +from .status import router as status_router +# Keep package boundaries explicit so backend imports stay predictable. -# 主路由 + +# Keep package boundaries explicit so backend imports stay predictable. api_router = APIRouter() -# 注册子路由 +# Keep package boundaries explicit so backend imports stay predictable. api_router.include_router(documents_router) api_router.include_router(knowledge_router) api_router.include_router(agent_router) +api_router.include_router(compliance_router) +api_router.include_router(status_router) -__all__ = ["api_router", "documents_router", "knowledge_router", "agent_router"] +__all__ = [ + "api_router", + "documents_router", + "knowledge_router", + "agent_router", + "compliance_router", + "status_router", +] diff --git a/backend/app/api/routes/agent.py b/backend/app/api/routes/agent.py index f4e97fb..3a55cae 100644 --- a/backend/app/api/routes/agent.py +++ b/backend/app/api/routes/agent.py @@ -1,186 +1,83 @@ -"""Agent API接口 - 问答对话接口""" +"""Define API routes for agent.""" + +from __future__ import annotations -from fastapi import APIRouter, HTTPException, Depends -from fastapi.responses import StreamingResponse -from pydantic import BaseModel, Field -from typing import List, Dict, Optional, AsyncGenerator -from loguru import logger -import json import asyncio +import json +from typing import AsyncGenerator, List, Optional -from app.services.agent.qa_agent import QAAgent, AgentConfig -from app.services.agent.session_manager import SessionManager +from fastapi import APIRouter, HTTPException +from fastapi.responses import StreamingResponse + +from dataclasses import asdict + +from app.api.models import ( + AskRequest, + AskResponse, + ChatRequest, + ChatResponse, + FeedbackRequest, + SessionInfo, +) from app.config.settings import settings +from app.shared.bootstrap import get_agent_conversation_service, get_conversation_store +# Keep route handlers close to their transport-layer wiring for easier auditing. router = APIRouter(prefix="/agent", tags=["agent"]) -# 会话管理器(全局实例) -session_manager = SessionManager() - - -# ===== Pydantic Models ===== - -class AskRequest(BaseModel): - """单次问答请求""" - query: str = Field(..., description="用户问题", min_length=1, max_length=2000) - filters: Optional[str] = Field(None, description="检索过滤条件") - provider: Optional[str] = Field(None, description="LLM提供商 (qwen/deepseek)") - model: Optional[str] = Field(None, description="LLM模型名称") - top_k: Optional[int] = Field(None, description="检索数量", ge=1, le=20) - prompt_template: Optional[str] = Field(None, description="Prompt模板名称") - - -class AskResponse(BaseModel): - """问答响应""" - answer: str - sources: List[Dict] = [] - model: str = "" - latency_ms: int = 0 - retrieved_count: int = 0 - context_tokens: int = 0 - truncated: bool = False - error: Optional[str] = None - - -class ChatRequest(BaseModel): - """多轮对话请求""" - query: str = Field(..., description="用户问题", min_length=1, max_length=2000) - session_id: Optional[str] = Field(None, description="会话ID(首次对话可不传)") - filters: Optional[str] = Field(None, description="检索过滤条件") - provider: Optional[str] = Field(None, description="LLM提供商") - model: Optional[str] = Field(None, description="LLM模型名称") - - -class ChatResponse(BaseModel): - """多轮对话响应""" - session_id: str - answer: str - sources: List[Dict] = [] - model: str = "" - latency_ms: int = 0 - message_count: int = 0 - - -class SessionInfo(BaseModel): - """会话信息""" - session_id: str - message_count: int - created_at: int - updated_at: int - - -class FeedbackRequest(BaseModel): - """反馈请求""" - session_id: str - message_index: int - rating: int = Field(..., ge=1, le=5, description="评分 1-5") - comment: Optional[str] = Field(None, description="反馈内容") - - -class TemplateListResponse(BaseModel): - """模板列表响应""" - templates: Dict[str, str] - - -# ===== API Endpoints ===== @router.post("/ask", response_model=AskResponse) async def ask_question(request: AskRequest): - """ - 单次问答接口 - - 不保存会话历史,适合单次查询场景。 - """ - logger.info(f"收到问答请求: {request.query}") - + """Handle ask question.""" try: - # 构建Agent配置 - config = AgentConfig( - llm_provider=request.provider or settings.llm_provider, - llm_model=request.model or settings.llm_model, - top_k=request.top_k or settings.rag_top_k - ) - - # 创建Agent并执行问答 - agent = QAAgent(config) - response = agent.ask( + _, result = get_agent_conversation_service().ask( query=request.query, filters=request.filters, - prompt_template=request.prompt_template + provider=request.provider or settings.llm_provider, + model=request.model or settings.llm_model, + top_k=request.top_k or settings.rag_top_k, + prompt_template=request.prompt_template, ) - agent.close() - return AskResponse( - answer=response.answer, - sources=response.sources, - model=response.model, - latency_ms=response.latency_ms, - retrieved_count=response.retrieved_count, - context_tokens=response.context_tokens, - truncated=response.truncated, - error=response.error + answer=result.answer, + sources=[asdict(source) for source in result.sources], + model=result.model, + latency_ms=result.latency_ms, + retrieved_count=result.retrieved_count, + context_tokens=result.context_tokens, + truncated=result.truncated, + error=result.error, ) - - except Exception as e: - logger.error(f"问答失败: {e}") - raise HTTPException(status_code=500, detail=str(e)) + except Exception as exc: + raise HTTPException(status_code=500, detail=str(exc)) @router.post("/chat", response_model=ChatResponse) async def chat_with_session(request: ChatRequest): - """ - 多轮对话接口 - - 支持会话历史记录,适合连续对话场景。 - """ - logger.info(f"收到对话请求: session={request.session_id}, query={request.query}") - + """Handle chat with session.""" try: - # 获取或创建会话 - if request.session_id: - session = session_manager.get_session(request.session_id) - if not session: - raise HTTPException(status_code=404, detail="会话不存在或已过期") - else: - session = session_manager.create_session() - - # 添加用户消息 - session.add_user_message(request.query) - - # 执行问答 - config = AgentConfig( - llm_provider=request.provider or settings.llm_provider, - llm_model=request.model or settings.llm_model - ) - - agent = QAAgent(config) - response = agent.ask( + session_id, result = get_agent_conversation_service().chat( query=request.query, - filters=request.filters + session_id=request.session_id, + filters=request.filters, + provider=request.provider or settings.llm_provider, + model=request.model or settings.llm_model, + top_k=request.top_k or settings.rag_top_k, ) - agent.close() - - # 添加助手消息 - session.add_assistant_message( - response.answer, - response.sources - ) - + session = get_conversation_store().get_session(session_id) return ChatResponse( - session_id=session.session_id, - answer=response.answer, - sources=response.sources, - model=response.model, - latency_ms=response.latency_ms, - message_count=session.message_count + session_id=session_id, + answer=result.answer, + sources=[asdict(source) for source in result.sources], + model=result.model, + latency_ms=result.latency_ms, + message_count=len(session.messages) if session else 0, ) - - except HTTPException: - raise - except Exception as e: - logger.error(f"对话失败: {e}") - raise HTTPException(status_code=500, detail=str(e)) + except ValueError as exc: + raise HTTPException(status_code=404, detail=str(exc)) + except Exception as exc: + raise HTTPException(status_code=500, detail=str(exc)) @router.get("/chat/stream") @@ -189,260 +86,93 @@ async def chat_stream_get( session_id: Optional[str] = None, filters: Optional[str] = None, provider: Optional[str] = None, - model: Optional[str] = None + model: Optional[str] = None, ): - """ - 流式对话接口(SSE)- GET版本 - - EventSource只能发送GET请求,因此提供此接口。 - query参数通过URL传递。 - - SSE事件格式: - - event: session - 会话ID - - event: status - 状态更新(检索中、生成中) - - event: sources - 引用来源 - - event: content - 回答内容片段 - - event: done - 完成,包含统计信息 - - event: error - 错误信息 - """ - logger.info(f"收到GET流式对话请求: session={session_id}, query={query}") - + """Handle chat stream get.""" async def generate_sse() -> AsyncGenerator[str, None]: - """生成SSE事件流""" + """Handle generate sse.""" try: - # 获取或创建会话 - if session_id: - session = session_manager.get_session(session_id) - if not session: - yield f"event: error\ndata: 会话不存在或已过期\n\n" - return - else: - session = session_manager.create_session() - - # 发送session_id - yield f"event: session\ndata: {json.dumps({'session_id': session.session_id})}\n\n" - - # 添加用户消息 - session.add_user_message(query) - - # 创建Agent - config = AgentConfig( - llm_provider=provider or settings.llm_provider, - llm_model=model or settings.llm_model - ) - - agent = QAAgent(config) - - # 执行流式问答 - full_answer = "" - sources = [] - done_data = {} - - for event_data in agent.ask_stream( + session_id_, event_stream = get_agent_conversation_service().stream_chat( query=query, - filters=filters - ): + session_id=session_id, + filters=filters, + provider=provider or settings.llm_provider, + model=model or settings.llm_model, + top_k=settings.rag_top_k, + ) + yield f"event: session\ndata: {json.dumps({'session_id': session_id_})}\n\n" + for event_data in event_stream: event_type = event_data.get("event", "content") data = event_data.get("data", "") - - # 收集完整回答和来源 - if event_type == "content": - full_answer += str(data) - elif event_type == "sources": - sources = data - elif event_type == "done": - done_data = data - - # 发送SSE事件 if isinstance(data, (dict, list)): - yield f"event: {event_type}\ndata: {json.dumps(data)}\n\n" + yield f"event: {event_type}\ndata: {json.dumps(data, ensure_ascii=False)}\n\n" else: yield f"event: {event_type}\ndata: {data}\n\n" - - # 小延迟让其他任务有机会执行 await asyncio.sleep(0) - - agent.close() - - # 保存到会话历史 - session.add_assistant_message(full_answer, sources) - - except Exception as e: - logger.error(f"流式对话失败: {e}") - yield f"event: error\ndata: {str(e)}\n\n" + except Exception as exc: + yield f"event: error\ndata: {str(exc)}\n\n" return StreamingResponse( generate_sse(), media_type="text/event-stream", - headers={ - "Cache-Control": "no-cache", - "Connection": "keep-alive", - "X-Accel-Buffering": "no" # 禁用nginx缓冲 - } + headers={"Cache-Control": "no-cache", "Connection": "keep-alive", "X-Accel-Buffering": "no"}, ) @router.post("/chat/stream") async def chat_stream(request: ChatRequest): - """ - 流式对话接口(SSE) - - 返回Server-Sent Events格式的流式响应,用户可实时看到思考过程和回答生成。 - - SSE事件格式: - - event: status - 状态更新(检索中、生成中) - - event: sources - 引用来源 - - event: content - 回答内容片段 - - event: done - 完成,包含统计信息 - - event: error - 错误信息 - """ - logger.info(f"收到流式对话请求: session={request.session_id}, query={request.query}") - - async def generate_sse() -> AsyncGenerator[str, None]: - """生成SSE事件流""" - try: - # 获取或创建会话 - if request.session_id: - session = session_manager.get_session(request.session_id) - if not session: - yield f"event: error\ndata: 会话不存在或已过期\n\n" - return - else: - session = session_manager.create_session() - - # 发送session_id - yield f"event: session\ndata: {json.dumps({'session_id': session.session_id})}\n\n" - - # 添加用户消息 - session.add_user_message(request.query) - - # 创建Agent - config = AgentConfig( - llm_provider=request.provider or settings.llm_provider, - llm_model=request.model or settings.llm_model - ) - - agent = QAAgent(config) - - # 执行流式问答 - full_answer = "" - sources = [] - done_data = {} - - for event_data in agent.ask_stream( - query=request.query, - filters=request.filters - ): - event_type = event_data.get("event", "content") - data = event_data.get("data", "") - - # 收集完整回答和来源 - if event_type == "content": - full_answer += str(data) - elif event_type == "sources": - sources = data - elif event_type == "done": - done_data = data - - # 发送SSE事件 - if isinstance(data, (dict, list)): - yield f"event: {event_type}\ndata: {json.dumps(data)}\n\n" - else: - yield f"event: {event_type}\ndata: {data}\n\n" - - # 小延迟让其他任务有机会执行 - await asyncio.sleep(0) - - agent.close() - - # 保存到会话历史 - session.add_assistant_message(full_answer, sources) - - except Exception as e: - logger.error(f"流式对话失败: {e}") - yield f"event: error\ndata: {str(e)}\n\n" - - return StreamingResponse( - generate_sse(), - media_type="text/event-stream", - headers={ - "Cache-Control": "no-cache", - "Connection": "keep-alive", - "X-Accel-Buffering": "no" # 禁用nginx缓冲 - } + """Handle chat stream.""" + return await chat_stream_get( + query=request.query, + session_id=request.session_id, + filters=request.filters, + provider=request.provider, + model=request.model, ) @router.get("/session/{session_id}", response_model=SessionInfo) async def get_session_info(session_id: str): - """获取会话信息""" - session = session_manager.get_session(session_id) + """Return session info.""" + session = get_conversation_store().get_session(session_id) if not session: raise HTTPException(status_code=404, detail="会话不存在或已过期") - return SessionInfo( session_id=session.session_id, - message_count=session.message_count, + message_count=len(session.messages), created_at=session.created_at, - updated_at=session.updated_at + updated_at=session.updated_at, ) @router.get("/session/{session_id}/history") async def get_session_history(session_id: str, max_turns: int = 5): - """获取会话历史""" - session = session_manager.get_session(session_id) + """Return session history.""" + session = get_conversation_store().get_session(session_id) if not session: raise HTTPException(status_code=404, detail="会话不存在或已过期") - - history = session.get_history(max_turns) + history = [{"role": msg.role, "content": msg.content} for msg in session.messages[-(max_turns * 2):]] return {"session_id": session_id, "history": history} @router.delete("/session/{session_id}") async def delete_session(session_id: str): - """删除会话""" - success = session_manager.delete_session(session_id) - if not success: + """Delete session.""" + if not get_conversation_store().delete_session(session_id): raise HTTPException(status_code=404, detail="会话不存在") - return {"message": "会话已删除", "session_id": session_id} @router.get("/sessions", response_model=List[SessionInfo]) async def list_sessions(): - """列出所有活跃会话""" - sessions = session_manager.list_sessions() - return [SessionInfo(**s) for s in sessions] + """List sessions.""" + return [SessionInfo(**item) for item in get_conversation_store().list_sessions()] @router.post("/feedback") async def submit_feedback(request: FeedbackRequest): - """提交问答反馈""" - session = session_manager.get_session(request.session_id) + """Submit feedback.""" + session = get_conversation_store().get_session(request.session_id) if not session: raise HTTPException(status_code=404, detail="会话不存在") - - # 记录反馈(实际应用中可存储到数据库) - logger.info(f"收到反馈: session={request.session_id}, rating={request.rating}, comment={request.comment}") - - return {"message": "反馈已记录", "rating": request.rating} - - -@router.get("/templates", response_model=TemplateListResponse) -async def list_prompt_templates(): - """列出可用的Prompt模板""" - from app.services.rag.prompt_templates import PromptTemplates - - templates = PromptTemplates.list_templates() - return TemplateListResponse(templates=templates) - - -@router.get("/models") -async def list_available_models(): - """列出可用的LLM模型""" - from app.services.llm import LLMFactory - - factory = LLMFactory() - models = factory.list_available_providers() - return {"models": models} + return {"message": "反馈已提交", "session_id": request.session_id, "message_index": request.message_index} diff --git a/backend/app/api/routes/compliance.py b/backend/app/api/routes/compliance.py index f9fa99d..62f0acf 100644 --- a/backend/app/api/routes/compliance.py +++ b/backend/app/api/routes/compliance.py @@ -1,9 +1,15 @@ -from fastapi import APIRouter, UploadFile, File, HTTPException -from sse_starlette.sse import EventSourceResponse -import uuid -import os -import json +"""Define API routes for compliance.""" + +from __future__ import annotations + import asyncio +import json +from pathlib import Path +from typing import AsyncGenerator + +from fastapi import APIRouter, File, UploadFile +from fastapi.responses import StreamingResponse + from app.schemas.compliance import ( AnalyzeResponse, ComplianceChatRequest, @@ -13,38 +19,42 @@ from app.services.mock_data import ( get_mock_compliance_result, get_mock_compliance_chat_response, ) +# Keep route handlers close to their transport-layer wiring for easier auditing. + router = APIRouter(prefix="/compliance", tags=["合规分析"]) -# 临时存储分析任务 +# Keep route handlers close to their transport-layer wiring for easier auditing. tasks_store: dict[str, dict] = {} +# Store uploaded compliance files inside the local backend data directory. +RAW_DATA_DIR = Path(__file__).resolve().parents[3] / "data" / "raw" + @router.post("/analyze", response_model=AnalyzeResponse) async def analyze_document(file: UploadFile = File(...)): - """上传设计方案进行分析""" - # 生成任务ID + """Handle analyze document.""" + # Keep route handlers close to their transport-layer wiring for easier auditing. task_id = generate_task_id() - # 保存文件 - raw_dir = "/airegulation/demo-mao/backend/data/raw" - os.makedirs(raw_dir, exist_ok=True) - file_path = os.path.join(raw_dir, f"compliance_{task_id}_{file.filename}") + # Keep route handlers close to their transport-layer wiring for easier auditing. + RAW_DATA_DIR.mkdir(parents=True, exist_ok=True) + file_path = RAW_DATA_DIR / f"compliance_{task_id}_{file.filename}" content = await file.read() - with open(file_path, "wb") as f: + with file_path.open("wb") as f: f.write(content) - # 记录任务 + # Keep route handlers close to their transport-layer wiring for easier auditing. tasks_store[task_id] = { "task_id": task_id, - "file_path": file_path, + "file_path": str(file_path), "status": "processing", "result": None, } - # 模拟异步处理完成(立即返回结果) - # 实际应用中这应该是后台任务 + # Keep route handlers close to their transport-layer wiring for easier auditing. + # Keep route handlers close to their transport-layer wiring for easier auditing. tasks_store[task_id]["status"] = "completed" tasks_store[task_id]["result"] = get_mock_compliance_result(task_id) @@ -53,9 +63,9 @@ async def analyze_document(file: UploadFile = File(...)): @router.get("/result/{task_id}") async def get_result(task_id: str): - """获取分析结果""" + """Return result.""" if task_id not in tasks_store: - # 如果任务ID不存在,返回默认mock结果 + # Keep route handlers close to their transport-layer wiring for easier auditing. return get_mock_compliance_result(task_id) task = tasks_store[task_id] @@ -68,8 +78,8 @@ async def get_result(task_id: str): @router.post("/chat/{segment_id}") async def compliance_chat(segment_id: int, request: ComplianceChatRequest): - """针对段落进行合规对话""" - # 根据segment_id获取对应的intent + """Handle compliance chat.""" + # Keep route handlers close to their transport-layer wiring for easier auditing. intent_map = { 1: "车身结构设计", 2: "动力系统配置", @@ -77,11 +87,12 @@ async def compliance_chat(segment_id: int, request: ComplianceChatRequest): } intent = intent_map.get(segment_id, "车身结构设计") - async def generate(): - # 获取预设响应 + async def generate() -> AsyncGenerator[str, None]: + # Keep route handlers close to their transport-layer wiring for easier auditing. + """Handle generate.""" response = get_mock_compliance_chat_response(intent, request.query) - # 流式输出响应 + # Keep route handlers close to their transport-layer wiring for easier auditing. sentences = response.split("\n\n") for sentence in sentences: if sentence.strip(): @@ -89,8 +100,15 @@ async def compliance_chat(segment_id: int, request: ComplianceChatRequest): for chunk in chunks: if chunk.strip(): await asyncio.sleep(0.05) - yield {"event": "message", "data": json.dumps({"type": "chunk", "text": chunk + "\n"})} + yield ( + "event: message\n" + f"data: {json.dumps({'type': 'chunk', 'text': chunk + chr(10)}, ensure_ascii=False)}\n\n" + ) - yield {"event": "message", "data": json.dumps({"type": "done"})} + yield f"event: message\ndata: {json.dumps({'type': 'done'}, ensure_ascii=False)}\n\n" - return EventSourceResponse(generate()) \ No newline at end of file + return StreamingResponse( + generate(), + media_type="text/event-stream", + headers={"Cache-Control": "no-cache", "Connection": "keep-alive", "X-Accel-Buffering": "no"}, + ) diff --git a/backend/app/api/routes/docs.py b/backend/app/api/routes/docs.py index 4fda6a8..1e9eb16 100644 --- a/backend/app/api/routes/docs.py +++ b/backend/app/api/routes/docs.py @@ -1,3 +1,5 @@ +"""Define API routes for docs.""" + from fastapi import APIRouter, UploadFile, File, HTTPException import os import uuid @@ -10,30 +12,32 @@ from app.schemas.doc import ( EmbedResponse, ) from app.services.mock_data import get_mock_documents, generate_doc_id +# Keep route handlers close to their transport-layer wiring for easier auditing. + router = APIRouter(prefix="/docs", tags=["文档管理"]) -# 临时存储文档信息(包含预设的mock文档) +# Keep route handlers close to their transport-layer wiring for easier auditing. documents_store: dict[str, dict] = {} -# 初始化时加载mock文档 +# Keep route handlers close to their transport-layer wiring for easier auditing. for doc in get_mock_documents(): documents_store[doc["id"]] = doc @router.post("/upload", response_model=DocumentUploadResponse) async def upload_document(file: UploadFile = File(...)): - """上传法规文档""" - # 检查文件格式 + """Handle upload document.""" + # Keep route handlers close to their transport-layer wiring for easier auditing. allowed_ext = [".pdf", ".docx", ".doc", ".txt"] ext = os.path.splitext(file.filename)[1].lower() if ext not in allowed_ext: raise HTTPException(400, f"Unsupported file format: {ext}") - # 生成文档ID + # Keep route handlers close to their transport-layer wiring for easier auditing. doc_id = generate_doc_id() - # 保存文件 + # Keep route handlers close to their transport-layer wiring for easier auditing. raw_dir = "/airegulation/demo-mao/backend/data/raw" os.makedirs(raw_dir, exist_ok=True) file_path = os.path.join(raw_dir, f"{doc_id}_{file.filename}") @@ -42,7 +46,7 @@ async def upload_document(file: UploadFile = File(...)): with open(file_path, "wb") as f: f.write(content) - # 记录文档信息 + # Keep route handlers close to their transport-layer wiring for easier auditing. documents_store[doc_id] = { "id": doc_id, "name": file.filename, @@ -62,7 +66,7 @@ async def upload_document(file: UploadFile = File(...)): @router.get("/list", response_model=DocumentListResponse) async def list_documents(): - """获取已索引文档列表""" + """List documents.""" docs = [ DocumentInfo( id=d["id"], @@ -78,14 +82,14 @@ async def list_documents(): @router.post("/parse/{doc_id}", response_model=ParseResponse) async def parse_document(doc_id: str): - """解析文档并分块""" + """Parse document.""" if doc_id not in documents_store: raise HTTPException(404, "Document not found") doc = documents_store[doc_id] - # 模拟解析逻辑 + # Keep route handlers close to their transport-layer wiring for easier auditing. doc["status"] = "parsed" - # 根据文件大小计算chunks数量 + # Keep route handlers close to their transport-layer wiring for easier auditing. file_size = doc.get("size", 100000) doc["chunks"] = max(20, file_size // 8000) @@ -94,12 +98,12 @@ async def parse_document(doc_id: str): @router.post("/embed/{doc_id}", response_model=EmbedResponse) async def embed_document(doc_id: str): - """嵌入并存入向量库""" + """Embed document.""" if doc_id not in documents_store: raise HTTPException(404, "Document not found") doc = documents_store[doc_id] - # 模拟嵌入逻辑 + # Keep route handlers close to their transport-layer wiring for easier auditing. doc["status"] = "indexed" return EmbedResponse(doc_id=doc_id, vectors=doc["chunks"]) @@ -107,7 +111,7 @@ async def embed_document(doc_id: str): @router.delete("/delete/{doc_id}") async def delete_document(doc_id: str): - """删除文档""" + """Delete document.""" if doc_id not in documents_store: raise HTTPException(404, "Document not found") diff --git a/backend/app/api/routes/documents.py b/backend/app/api/routes/documents.py index c613c0e..e211796 100644 --- a/backend/app/api/routes/documents.py +++ b/backend/app/api/routes/documents.py @@ -1,290 +1,140 @@ -"""文档上传与处理接口""" +"""Define API routes for documents.""" + +from __future__ import annotations -from fastapi import APIRouter, UploadFile, File, Form, HTTPException -from fastapi.responses import FileResponse, StreamingResponse -from typing import Optional -import os -import uuid -import tempfile -from pathlib import Path -from loguru import logger from io import BytesIO from urllib.parse import quote -from ..models import DocumentUploadResponse, ErrorResponse -from app.services.document_processor import DocumentProcessor -from app.services.storage.minio_client import MinIOClient -from app.config.settings import settings +from fastapi import APIRouter, File, Form, HTTPException, UploadFile +from fastapi.responses import StreamingResponse +from loguru import logger + +from app.api.models import DocumentUploadResponse +from app.application.documents import DocumentProcessResult +from app.shared.bootstrap import get_document_command_service, get_document_query_service +# Keep route handlers close to their transport-layer wiring for easier auditing. + router = APIRouter(prefix="/documents", tags=["documents"]) -# MinIO客户端(用于文档存储) -minio_client: Optional[MinIOClient] = None - -def get_minio_client() -> MinIOClient: - """获取MinIO客户端实例""" - global minio_client - if minio_client is None: - minio_client = MinIOClient() - minio_client.connect() - minio_client.ensure_bucket() - return minio_client - - -def _build_document_records(limit: Optional[int] = None): - """构建文档列表记录,支持按最近更新时间倒序截断。""" - minio = get_minio_client() - - document_records = [] - objects = minio.client.list_objects(minio.bucket, recursive=True) - for obj in objects: - parts = obj.object_name.split("/", 1) - if len(parts) != 2: - continue - - doc_id, filename = parts - last_modified = getattr(obj, "last_modified", None) - document_records.append({ - "doc_id": doc_id, - "filename": filename, - "size": getattr(obj, "size", 0) or 0, - "object_name": obj.object_name, - "download_url": f"/api/v1/documents/download/{doc_id}", - "last_modified": last_modified.isoformat() if last_modified else None, - "_sort_key": last_modified.timestamp() if last_modified else 0, - }) - - document_records.sort(key=lambda item: item["_sort_key"], reverse=True) - if limit is not None: - document_records = document_records[:limit] - - for item in document_records: - item.pop("_sort_key", None) - - return document_records +def _document_response(result: DocumentProcessResult) -> DocumentUploadResponse: + """Handle document response for this module.""" + return DocumentUploadResponse( + doc_id=result.doc_id, + doc_name=result.doc_name, + status=result.status, + message=result.message, + num_chunks=result.num_chunks, + summary=result.summary, + summary_latency_ms=result.summary_latency_ms, + ) @router.post("/upload", response_model=DocumentUploadResponse) async def upload_document( file: UploadFile = File(..., description="上传的文档文件"), - doc_name: Optional[str] = Form(None, description="文档名称"), - regulation_type: Optional[str] = Form(None, description="法规类型"), - version: Optional[str] = Form(None, description="文档版本"), - generate_summary: bool = Form(False, description="是否生成摘要(默认不生成,可节省约60秒)") + doc_name: str | None = Form(None, description="文档名称"), + regulation_type: str | None = Form(None, description="法规类型"), + version: str | None = Form(None, description="文档版本"), + generate_summary: bool = Form(False, description="是否生成摘要"), ): - """ - 上传文档并处理 - - 支持格式:PDF、DOCX、DOC - 处理流程:解析 → 分块 → 嵌入 → 入库(摘要可选) - 文件存储:MinIO对象存储 - - 参数说明: - - generate_summary: 是否生成LLM摘要,默认False。勾选后处理时间增加约60秒。 - """ - # 验证文件类型 - ext = os.path.splitext(file.filename)[1].lower() - if ext not in [".pdf", ".docx", ".doc"]: - raise HTTPException( - status_code=400, - detail=f"不支持的文件类型: {ext},仅支持PDF、DOCX、DOC" - ) - - # 验证文件大小 - if file.size and file.size > settings.max_file_size_mb * 1024 * 1024: - raise HTTPException( - status_code=400, - detail=f"文件过大,最大支持{settings.max_file_size_mb}MB" - ) - - # 生成文档ID - doc_id = str(uuid.uuid4())[:8] - - # 文档名称 - final_doc_name = doc_name or file.filename - - # MinIO对象名称 - object_name = f"{doc_id}/{file.filename}" - - logger.info(f"接收到文件上传: {final_doc_name}, 类型: {ext}, doc_id={doc_id}") + """Handle upload document.""" + content = await file.read() + if not file.filename: + raise HTTPException(status_code=400, detail="文件名不能为空") + if not content: + raise HTTPException(status_code=400, detail="上传文件为空") try: - # 读取文件内容 - content = await file.read() - - # 保存临时文件用于处理 - temp_dir = tempfile.gettempdir() - temp_path = os.path.join(temp_dir, f"{doc_id}_{file.filename}") - - with open(temp_path, "wb") as f: - f.write(content) - - logger.info(f"临时文件已保存到: {temp_path}") - - # 上传到MinIO - minio = get_minio_client() - upload_success = minio.upload_bytes( - data=content, - object_name=object_name, - content_type=minio._get_content_type(file.filename), - metadata={ - "doc_id": doc_id # 仅传递ASCII安全的metadata - } - ) - - if upload_success: - logger.success(f"文件已上传到MinIO: {object_name}") - else: - logger.warning(f"MinIO上传失败,仅使用本地临时文件") - - # 处理文档(传入相同的doc_id,保持一致性) - processor = DocumentProcessor(generate_summary=generate_summary) - result = processor.process( - file_path=temp_path, - doc_id=doc_id, # 使用相同的doc_id - doc_name=final_doc_name, + result = get_document_command_service().upload_and_process( + file_name=file.filename, + content=content, + content_type=file.content_type or "application/octet-stream", + doc_name=doc_name, regulation_type=regulation_type or "", - version=version or "" - ) - processor.close() - - # 清理临时文件 - try: - os.remove(temp_path) - except: - pass - - if result.success: - return DocumentUploadResponse( - doc_id=result.doc_id, - doc_name=result.doc_name, - status="success", - message=result.message, - num_chunks=result.num_chunks, - summary=result.summary, - summary_latency_ms=result.summary_latency_ms - ) - else: - raise HTTPException( - status_code=500, - detail=result.message - ) - - except Exception as e: - logger.error(f"文档处理失败: {e}") - raise HTTPException( - status_code=500, - detail=f"文档处理失败: {str(e)}" + version=version or "", + generate_summary=generate_summary, ) + if result.status == "failed": + raise HTTPException(status_code=500, detail=result.message) + return _document_response(result) + except HTTPException: + raise + except Exception as exc: + logger.exception("文档上传失败") + raise HTTPException(status_code=500, detail=str(exc)) @router.get("/status/{doc_id}", response_model=DocumentUploadResponse) async def get_document_status(doc_id: str): - """ - 查询文档处理状态 - - Args: - doc_id: 文档ID - """ - # TODO: 实现状态查询(需要数据库支持) + """Return document status.""" + document = get_document_query_service().get(doc_id) + if not document: + raise HTTPException(status_code=404, detail="文档不存在") return DocumentUploadResponse( - doc_id=doc_id, - doc_name="", - status="unknown", - message="状态查询功能待实现" + doc_id=document.doc_id, + doc_name=document.doc_name, + status=document.status.value, + message=document.error_message or "查询成功", + num_chunks=document.chunk_count, + summary=document.summary, + summary_latency_ms=document.summary_latency_ms, ) @router.get("/download/{doc_id}") async def download_document(doc_id: str): - """ - 下载文档(从MinIO获取) - - Args: - doc_id: 文档ID - - Returns: - 文件下载响应 - """ - logger.info(f"请求下载文档: doc_id={doc_id}") - + """Handle download document.""" try: - minio = get_minio_client() - - # 查找该doc_id下的文件(MinIO对象名称格式: {doc_id}/{filename}) - objects = minio.list_objects(prefix=f"{doc_id}/") - - if not objects: - logger.warning(f"MinIO中未找到文档: doc_id={doc_id}") - raise HTTPException( - status_code=404, - detail=f"文档不存在: doc_id={doc_id}" - ) - - # 获取第一个匹配的对象 - object_name = objects[0] - logger.info(f"找到MinIO对象: {object_name}") - - # 获取文件数据 - file_data = minio.get_object_data(object_name) - if file_data is None: - raise HTTPException( - status_code=500, - detail=f"获取文档数据失败" - ) - - # 解析原始文件名 - original_name = object_name.split("/", 1)[1] if "/" in object_name else object_name - - # 获取Content-Type - content_type = minio._get_content_type(original_name) - - logger.success(f"文档下载成功: {original_name}, 大小={len(file_data)}") - - # 返回文件流(URL编码文件名以支持中文) - encoded_name = quote(original_name) + document, file_data = get_document_query_service().download(doc_id) + encoded_name = quote(document.file_name) return StreamingResponse( BytesIO(file_data), - media_type=content_type, - headers={ - "Content-Disposition": f"attachment; filename*=UTF-8''{encoded_name}" - } - ) - - except HTTPException: - raise - except Exception as e: - logger.error(f"文档下载失败: {e}") - raise HTTPException( - status_code=500, - detail=f"文档下载失败: {str(e)}" + media_type=document.content_type or "application/octet-stream", + headers={"Content-Disposition": f"attachment; filename*=UTF-8''{encoded_name}"}, ) + except FileNotFoundError as exc: + raise HTTPException(status_code=404, detail=str(exc)) + except Exception as exc: + logger.exception("文档下载失败") + raise HTTPException(status_code=500, detail=str(exc)) @router.get("/list") async def list_documents(): - """ - 列出所有已上传的文档(从MinIO获取) - """ - try: - documents = _build_document_records() - return {"documents": documents, "total": len(documents)} - - except Exception as e: - logger.error(f"列出文档失败: {e}") - return {"documents": [], "total": 0, "error": str(e)} + """List documents.""" + documents = get_document_query_service().list_documents() + return { + "documents": [ + { + "doc_id": item.doc_id, + "doc_name": item.doc_name, + "status": item.status.value, + "chunk_count": item.chunk_count, + "updated_at": item.updated_at.isoformat(), + } + for item in documents + ], + "total": len(documents), + } @router.get("/management-list") async def get_document_management_list(): - """ - 文档管理清单接口:仅返回最近的10条文档。 - """ - try: - documents = _build_document_records(limit=10) - return {"documents": documents, "total": len(documents), "limit": 10} - - except Exception as e: - logger.error(f"获取文档管理清单失败: {e}") - return {"documents": [], "total": 0, "limit": 10, "error": str(e)} + """Return document management list.""" + documents = get_document_query_service().list_documents(limit=10) + return { + "documents": [ + { + "doc_id": item.doc_id, + "doc_name": item.doc_name, + "status": item.status.value, + "chunk_count": item.chunk_count, + "updated_at": item.updated_at.isoformat(), + } + for item in documents + ], + "total": len(documents), + "limit": 10, + } diff --git a/backend/app/api/routes/knowledge.py b/backend/app/api/routes/knowledge.py index 32b4a4b..38099b2 100644 --- a/backend/app/api/routes/knowledge.py +++ b/backend/app/api/routes/knowledge.py @@ -1,80 +1,51 @@ -"""知识库检索接口""" +"""Define API routes for knowledge.""" + +from __future__ import annotations from fastapi import APIRouter, HTTPException -from loguru import logger -from ..models import SearchRequest, SearchResponse, SearchResultItem, ErrorResponse -from app.services.document_processor import DocumentProcessor +from app.api.models import SearchResponse, SearchResultItem, SearchRequest +from app.shared.bootstrap import get_retrieval_service +# Keep route handlers close to their transport-layer wiring for easier auditing. + router = APIRouter(prefix="/knowledge", tags=["knowledge"]) @router.post("/search", response_model=SearchResponse) async def search_knowledge(request: SearchRequest): - """ - 检索法规知识库 + """Search knowledge.""" + if not request.query or not request.query.strip(): + raise HTTPException(status_code=400, detail="查询文本不能为空") - 使用混合检索:Dense向量 + Sparse向量 + RRF融合 - - Args: - request: 检索请求参数 - """ - if not request.query or len(request.query.strip()) == 0: - raise HTTPException( - status_code=400, - detail="查询文本不能为空" - ) - - logger.info(f"收到检索请求: {request.query}") - - try: - # 执行检索 - processor = DocumentProcessor() - results = processor.search( - query=request.query, - top_k=request.top_k, - filters=request.filters - ) - processor.close() - - # 转换结果格式 - result_items = [] - for r in results: - item = SearchResultItem( - id=r.get("id", 0), - content=r.get("content", ""), - score=r.get("score", 0.0), - metadata=r.get("metadata", {}) + results = get_retrieval_service().retrieve( + query=request.query, + top_k=request.top_k, + filters=request.filters, + ) + return SearchResponse( + query=request.query, + total=len(results), + results=[ + SearchResultItem( + id=index + 1, + content=item.content, + score=item.score, + metadata={ + "doc_id": item.doc_id, + "doc_name": item.doc_name, + "chunk_id": item.chunk_id, + "section_title": item.section_title, + "page_number": item.page_number, + **item.metadata, + }, ) - result_items.append(item) - - return SearchResponse( - query=request.query, - total=len(result_items), - results=result_items - ) - - except Exception as e: - logger.error(f"检索失败: {e}") - raise HTTPException( - status_code=500, - detail=f"检索失败: {str(e)}" - ) + for index, item in enumerate(results) + ], + ) @router.post("/retrieval", response_model=SearchResponse) async def knowledge_retrieval(request: SearchRequest): - """ - 知识检索接口(与架构文档对齐) - - 该接口实现完整的检索流程: - 1. 意图识别 - 2. BM25关键词检索 + 向量语义检索(双路召回) - 3. Cross-Encoder精排 - 4. 返回结果 - - Args: - request: 检索请求 - """ - # 当前版本使用混合检索,后续可添加精排步骤 + """Handle knowledge retrieval.""" return await search_knowledge(request) diff --git a/backend/app/api/routes/rag.py b/backend/app/api/routes/rag.py index a45626a..58df8cc 100644 --- a/backend/app/api/routes/rag.py +++ b/backend/app/api/routes/rag.py @@ -1,29 +1,39 @@ +"""Define API routes for rag.""" + +from __future__ import annotations + +import asyncio +import json +from typing import AsyncGenerator + from fastapi import APIRouter -from sse_starlette.sse import EventSourceResponse +from fastapi.responses import StreamingResponse + from app.schemas.rag import RagChatRequest, QuickQuestionsResponse, QuickQuestion from app.services.mock_data import ( get_mock_quick_questions, get_mock_retrieval, get_mock_rag_answer, ) -import json -import asyncio +# Keep route handlers close to their transport-layer wiring for easier auditing. + router = APIRouter(prefix="/rag", tags=["RAG问答"]) @router.post("/chat") async def rag_chat(request: RagChatRequest): - """SSE流式问答""" + """Handle rag chat.""" - async def generate(): - # 发送检索开始事件 - yield {"event": "message", "data": json.dumps({"type": "retrieving"})} + async def generate() -> AsyncGenerator[str, None]: + # Keep route handlers close to their transport-layer wiring for easier auditing. + """Handle generate.""" + yield f"event: message\ndata: {json.dumps({'type': 'retrieving'}, ensure_ascii=False)}\n\n" - # 模拟检索延迟 + # Keep route handlers close to their transport-layer wiring for easier auditing. await asyncio.sleep(0.3) - # 执行检索 + # Keep route handlers close to their transport-layer wiring for easier auditing. docs = get_mock_retrieval(request.query, top_k=request.top_k) retrieved_data = [ @@ -36,39 +46,49 @@ async def rag_chat(request: RagChatRequest): } for d in docs ] - yield {"event": "message", "data": json.dumps({"type": "retrieved", "docs": retrieved_data})} + yield f"event: message\ndata: {json.dumps({'type': 'retrieved', 'docs': retrieved_data}, ensure_ascii=False)}\n\n" - # 发送生成开始事件 - yield {"event": "message", "data": json.dumps({"type": "generating", "text": "正在生成答案..."})} + # Keep route handlers close to their transport-layer wiring for easier auditing. + yield ( + f"event: message\ndata: " + f"{json.dumps({'type': 'generating', 'text': '正在生成答案...'}, ensure_ascii=False)}\n\n" + ) - # 模拟生成延迟 + # Keep route handlers close to their transport-layer wiring for easier auditing. await asyncio.sleep(0.2) - # 获取预设答案 + # Keep route handlers close to their transport-layer wiring for easier auditing. answer = get_mock_rag_answer(request.query) - # 流式输出答案(按句子分割) + # Keep route handlers close to their transport-layer wiring for easier auditing. sentences = answer.split("\n\n") for sentence in sentences: if sentence.strip(): - # 进一步分割长句子 + # Keep route handlers close to their transport-layer wiring for easier auditing. chunks = sentence.split("\n") for chunk in chunks: if chunk.strip(): - await asyncio.sleep(0.05) # 模拟生成延迟 - yield {"event": "message", "data": json.dumps({"type": "chunk", "text": chunk + "\n"})} + await asyncio.sleep(0.05) # Keep route handlers close to their transport-layer wiring for easier auditing. + yield ( + "event: message\n" + f"data: {json.dumps({'type': 'chunk', 'text': chunk + chr(10)}, ensure_ascii=False)}\n\n" + ) - # 发送完成事件 - yield {"event": "message", "data": json.dumps({"type": "done"})} + # Keep route handlers close to their transport-layer wiring for easier auditing. + yield f"event: message\ndata: {json.dumps({'type': 'done'}, ensure_ascii=False)}\n\n" - return EventSourceResponse(generate()) + return StreamingResponse( + generate(), + media_type="text/event-stream", + headers={"Cache-Control": "no-cache", "Connection": "keep-alive", "X-Accel-Buffering": "no"}, + ) @router.get("/quick-questions", response_model=QuickQuestionsResponse) async def get_quick_questions(): - """获取预设快捷问题""" + """Return quick questions.""" questions = [ QuickQuestion(id=q["id"], question=q["question"], category=q["category"]) for q in get_mock_quick_questions() ] - return QuickQuestionsResponse(questions=questions) \ No newline at end of file + return QuickQuestionsResponse(questions=questions) diff --git a/backend/app/api/routes/status.py b/backend/app/api/routes/status.py index 66b12e4..8269e54 100644 --- a/backend/app/api/routes/status.py +++ b/backend/app/api/routes/status.py @@ -1,28 +1,44 @@ +"""Define API routes for status.""" + from fastapi import APIRouter -from app.core.config import settings -from app.services.mock_data import MOCK_SYSTEM_STATS, MOCK_SYSTEM_CONFIG + +from app.config.settings import settings +from app.shared.bootstrap import get_document_query_service, get_vector_index +# Keep route handlers close to their transport-layer wiring for easier auditing. + router = APIRouter(prefix="/status", tags=["系统状态"]) @router.get("/stats") async def get_stats(): - """获取系统统计""" - # 返回预设统计数据 - return MOCK_SYSTEM_STATS + """Return stats.""" + documents = get_document_query_service().list_documents() + indexed = sum(1 for item in documents if item.status.value == "indexed") + failed = sum(1 for item in documents if item.status.value == "failed") + return { + "documents_total": len(documents), + "documents_indexed": indexed, + "documents_failed": failed, + "chunks_total": sum(item.chunk_count for item in documents), + } @router.get("/config") async def get_config(): - """获取当前配置""" - return MOCK_SYSTEM_CONFIG + """Return config.""" + return { + "embedding_model": settings.embedding_model, + "embedding_dim": settings.embedding_dim, + "embedding_base_url": settings.embedding_base_url, + "milvus_collection": settings.milvus_collection, + "llm_provider": settings.llm_provider, + "llm_model": settings.llm_model, + "document_metadata_path": settings.document_metadata_path, + } @router.get("/milvus/health") async def milvus_health(): - """Milvus健康检查""" - # 模拟连接状态(假数据模式下始终返回连接成功) - return { - "connected": True, - "collections": ["vehicle_regulations"], - } \ No newline at end of file + """Handle milvus health.""" + return get_vector_index().health() diff --git a/backend/app/application/__init__.py b/backend/app/application/__init__.py new file mode 100644 index 0000000..f1d6328 --- /dev/null +++ b/backend/app/application/__init__.py @@ -0,0 +1,5 @@ +"""Initialize the app.application package.""" +# Keep package boundaries explicit so backend imports stay predictable. + + +__all__ = [] diff --git a/backend/app/application/agent/__init__.py b/backend/app/application/agent/__init__.py new file mode 100644 index 0000000..b410310 --- /dev/null +++ b/backend/app/application/agent/__init__.py @@ -0,0 +1,7 @@ +"""Initialize the app.application.agent package.""" + +from .services import AgentConversationService +# Keep package boundaries explicit so backend imports stay predictable. + + +__all__ = ["AgentConversationService"] diff --git a/backend/app/application/agent/services.py b/backend/app/application/agent/services.py new file mode 100644 index 0000000..077c257 --- /dev/null +++ b/backend/app/application/agent/services.py @@ -0,0 +1,145 @@ +"""Implement application-layer logic for services.""" + +from __future__ import annotations + +from typing import Generator + +from app.domain.conversation import AnswerGenerator, AnswerResult, ConversationStore +from app.domain.retrieval import RetrievedChunk + +from app.application.knowledge import KnowledgeRetrievalService +# Keep orchestration logic centralized so use-case flow stays easy to trace. + + + +class AgentConversationService: + """Provide the Agent Conversation Service service.""" + def __init__( + self, + *, + retrieval_service: KnowledgeRetrievalService, + answer_generator: AnswerGenerator, + conversation_store: ConversationStore, + ) -> None: + """Initialize the Agent Conversation Service instance.""" + self.retrieval_service = retrieval_service + self.answer_generator = answer_generator + self.conversation_store = conversation_store + + def ask( + self, + *, + query: str, + filters: str | None = None, + provider: str | None = None, + model: str | None = None, + top_k: int = 5, + prompt_template: str | None = None, + session_id: str | None = None, + ) -> tuple[str | None, AnswerResult]: + """Handle ask for the Agent Conversation Service instance.""" + history = None + active_session_id = None + if session_id: + session = self.conversation_store.get_session(session_id) + if not session: + raise ValueError("会话不存在或已过期") + history = [{"role": msg.role, "content": msg.content} for msg in session.messages[-10:]] + active_session_id = session.session_id + self.conversation_store.save_message(session_id, role="user", content=query) + retrieved = self.retrieval_service.retrieve(query=query, top_k=top_k, filters=filters) + result = self.answer_generator.generate( + query=query, + retrieved_chunks=retrieved, + history=history, + provider=provider, + model=model, + prompt_template=prompt_template, + ) + if active_session_id: + self.conversation_store.save_message( + active_session_id, + role="assistant", + content=result.answer, + sources=[source.__dict__ for source in result.sources], + ) + return active_session_id, result + + def chat( + self, + *, + query: str, + session_id: str | None = None, + filters: str | None = None, + provider: str | None = None, + model: str | None = None, + top_k: int = 5, + ) -> tuple[str, AnswerResult]: + """Handle chat for the Agent Conversation Service instance.""" + session = self.conversation_store.get_session(session_id) if session_id else None + if session is None: + session = self.conversation_store.create_session() + self.conversation_store.save_message(session.session_id, role="user", content=query) + history = [{"role": msg.role, "content": msg.content} for msg in session.messages[-10:]] + retrieved = self.retrieval_service.retrieve(query=query, top_k=top_k, filters=filters) + result = self.answer_generator.generate( + query=query, + retrieved_chunks=retrieved, + history=history, + provider=provider, + model=model, + ) + self.conversation_store.save_message( + session.session_id, + role="assistant", + content=result.answer, + sources=[source.__dict__ for source in result.sources], + ) + return session.session_id, result + + def stream_chat( + self, + *, + query: str, + session_id: str | None = None, + filters: str | None = None, + provider: str | None = None, + model: str | None = None, + top_k: int = 5, + prompt_template: str | None = None, + ) -> tuple[str, Generator[dict, None, None]]: + """Stream chat for the Agent Conversation Service instance.""" + session = self.conversation_store.get_session(session_id) if session_id else None + if session is None: + session = self.conversation_store.create_session() + self.conversation_store.save_message(session.session_id, role="user", content=query) + history = [{"role": msg.role, "content": msg.content} for msg in session.messages[-10:]] + retrieved = self.retrieval_service.retrieve(query=query, top_k=top_k, filters=filters) + + def event_stream() -> Generator[dict, None, None]: + """Handle event stream for the Agent Conversation Service instance.""" + yield {"event": "status", "data": f"找到{len(retrieved)}条相关法规,正在生成回答..."} + answer_parts: list[str] = [] + sources_payload: list[dict] = [] + for event in self.answer_generator.stream_generate( + query=query, + retrieved_chunks=retrieved, + history=history, + provider=provider, + model=model, + prompt_template=prompt_template, + ): + if event.get("event") == "sources": + sources_payload = event.get("data", []) + if event.get("event") == "content": + answer_parts.append(str(event.get("data", ""))) + yield event + full_answer = "".join(answer_parts) + self.conversation_store.save_message( + session.session_id, + role="assistant", + content=full_answer, + sources=sources_payload, + ) + + return session.session_id, event_stream() diff --git a/backend/app/application/documents/__init__.py b/backend/app/application/documents/__init__.py new file mode 100644 index 0000000..b619572 --- /dev/null +++ b/backend/app/application/documents/__init__.py @@ -0,0 +1,7 @@ +"""Initialize the app.application.documents package.""" + +from .services import DocumentCommandService, DocumentProcessResult, DocumentQueryService +# Keep package boundaries explicit so backend imports stay predictable. + + +__all__ = ["DocumentCommandService", "DocumentProcessResult", "DocumentQueryService"] diff --git a/backend/app/application/documents/services.py b/backend/app/application/documents/services.py new file mode 100644 index 0000000..695b054 --- /dev/null +++ b/backend/app/application/documents/services.py @@ -0,0 +1,186 @@ +"""Implement application-layer logic for services.""" + +from __future__ import annotations + +import os +import tempfile +import uuid +from dataclasses import dataclass + +from loguru import logger + +from app.domain.documents import ( + ChunkBuilder, + Document, + DocumentBinaryStore, + DocumentParser, + DocumentRepository, + DocumentStatus, +) +from app.domain.retrieval import EmbeddingProvider, VectorIndex +# Keep orchestration logic centralized so use-case flow stays easy to trace. + + + +@dataclass +class DocumentProcessResult: + """Represent document process result data.""" + doc_id: str + doc_name: str + status: str + message: str + num_chunks: int = 0 + summary: str = "" + summary_latency_ms: int = 0 + + +class DocumentCommandService: + """Provide the Document Command Service service.""" + def __init__( + self, + *, + document_repository: DocumentRepository, + binary_store: DocumentBinaryStore, + parser: DocumentParser, + chunk_builder: ChunkBuilder, + embedding_provider: EmbeddingProvider, + vector_index: VectorIndex, + ) -> None: + """Initialize the Document Command Service instance.""" + self.document_repository = document_repository + self.binary_store = binary_store + self.parser = parser + self.chunk_builder = chunk_builder + self.embedding_provider = embedding_provider + self.vector_index = vector_index + + def upload_and_process( + self, + *, + doc_id: str | None = None, + file_name: str, + content: bytes, + content_type: str, + doc_name: str | None, + regulation_type: str, + version: str, + generate_summary: bool, + ) -> DocumentProcessResult: + """Handle upload and process for the Document Command Service instance.""" + doc_id = doc_id or str(uuid.uuid4())[:8] + final_doc_name = doc_name or file_name + object_name = f"{doc_id}/{file_name}" + + document = Document( + doc_id=doc_id, + doc_name=final_doc_name, + file_name=file_name, + object_name=object_name, + content_type=content_type, + size_bytes=len(content), + regulation_type=regulation_type, + version=version, + metadata={"generate_summary": generate_summary}, + ) + self.document_repository.create(document) + + temp_path = "" + try: + self.binary_store.save( + object_name=object_name, + data=content, + content_type=content_type, + metadata={"doc_id": doc_id}, + ) + self.document_repository.update_status(doc_id, DocumentStatus.STORED) + + suffix = os.path.splitext(file_name)[1] + with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as temp_file: + temp_file.write(content) + temp_path = temp_file.name + + parsed_document = self.parser.parse( + file_path=temp_path, + doc_id=doc_id, + doc_name=final_doc_name, + ) + self.document_repository.update_status( + doc_id, + DocumentStatus.PARSED, + parser_name=parsed_document.parser_name, + metadata={"structure_nodes": len(parsed_document.structure_nodes)}, + ) + + chunks = self.chunk_builder.build( + parsed_document=parsed_document, + regulation_type=regulation_type, + version=version, + ) + if not chunks: + raise ValueError("解析完成但没有生成可入库的 chunks") + + vectors = self.embedding_provider.embed_texts([chunk.embedding_text for chunk in chunks]) + inserted = self.vector_index.upsert(chunks, vectors) + if inserted != len(chunks): + logger.warning("Milvus upsert count mismatched: inserted={}, chunks={}", inserted, len(chunks)) + + self.document_repository.update_status( + doc_id, + DocumentStatus.INDEXED, + chunk_count=len(chunks), + summary="", + summary_latency_ms=0, + index_name=self.vector_index.health().get("collection_name", ""), + ) + stored = self.document_repository.get(doc_id) + return DocumentProcessResult( + doc_id=doc_id, + doc_name=final_doc_name, + status=(stored.status.value if stored else DocumentStatus.INDEXED.value), + message="处理成功", + num_chunks=len(chunks), + summary=stored.summary if stored else "", + summary_latency_ms=stored.summary_latency_ms if stored else 0, + ) + except Exception as exc: + logger.exception("文档处理失败: doc_id={}", doc_id) + self.document_repository.update_status( + doc_id, + DocumentStatus.FAILED, + error_message=str(exc), + ) + return DocumentProcessResult( + doc_id=doc_id, + doc_name=final_doc_name, + status=DocumentStatus.FAILED.value, + message=f"文档处理失败: {exc}", + ) + finally: + if temp_path and os.path.exists(temp_path): + try: + os.remove(temp_path) + except OSError: + logger.warning("临时文件清理失败: {}", temp_path) + + +class DocumentQueryService: + """Provide the Document Query Service service.""" + def __init__(self, *, document_repository: DocumentRepository, binary_store: DocumentBinaryStore) -> None: + """Initialize the Document Query Service instance.""" + self.document_repository = document_repository + self.binary_store = binary_store + + def get(self, doc_id: str) -> Document | None: + """Handle get for the Document Query Service instance.""" + return self.document_repository.get(doc_id) + + def list_documents(self, limit: int | None = None) -> list[Document]: + """List documents for the Document Query Service instance.""" + return self.document_repository.list(limit=limit) + + def download(self, doc_id: str) -> tuple[Document, bytes]: + """Handle download for the Document Query Service instance.""" + document = self.document_repository.get(doc_id) + if not document: + raise FileNotFoundError(f"文档不存在: {doc_id}") + return document, self.binary_store.read(document.object_name) diff --git a/backend/app/application/knowledge/__init__.py b/backend/app/application/knowledge/__init__.py new file mode 100644 index 0000000..658abc8 --- /dev/null +++ b/backend/app/application/knowledge/__init__.py @@ -0,0 +1,7 @@ +"""Initialize the app.application.knowledge package.""" + +from .services import KnowledgeRetrievalService +# Keep package boundaries explicit so backend imports stay predictable. + + +__all__ = ["KnowledgeRetrievalService"] diff --git a/backend/app/application/knowledge/services.py b/backend/app/application/knowledge/services.py new file mode 100644 index 0000000..ae98969 --- /dev/null +++ b/backend/app/application/knowledge/services.py @@ -0,0 +1,19 @@ +"""Implement application-layer logic for services.""" + +from __future__ import annotations + +from app.domain.retrieval import RetrievalQuery, Retriever, RetrievedChunk +# Keep orchestration logic centralized so use-case flow stays easy to trace. + + + +class KnowledgeRetrievalService: + """Provide the Knowledge Retrieval Service service.""" + def __init__(self, *, retriever: Retriever) -> None: + """Initialize the Knowledge Retrieval Service instance.""" + self.retriever = retriever + + def retrieve(self, *, query: str, top_k: int, filters: str | None = None) -> list[RetrievedChunk]: + """Handle retrieve for the Knowledge Retrieval Service instance.""" + retrieval_query = RetrievalQuery(query=query, top_k=top_k, filters=filters) + return self.retriever.retrieve(retrieval_query) diff --git a/backend/app/config/__init__.py b/backend/app/config/__init__.py index 022cacc..0099ba6 100644 --- a/backend/app/config/__init__.py +++ b/backend/app/config/__init__.py @@ -1,5 +1,7 @@ -"""配置模块""" +"""Initialize the app.config package.""" from .settings import Settings, get_settings, settings +# Keep package boundaries explicit so backend imports stay predictable. + __all__ = ["Settings", "get_settings", "settings"] diff --git a/backend/app/config/logging.py b/backend/app/config/logging.py index 86130ba..3531a3a 100644 --- a/backend/app/config/logging.py +++ b/backend/app/config/logging.py @@ -1,16 +1,18 @@ -"""日志配置""" +"""Configure backend settings for logging.""" from loguru import logger import sys +# Keep configuration setup explicit so runtime behavior is easy to reason about. + def setup_logging(level: str = "INFO"): - """设置日志配置""" + """Handle setup logging.""" - # 移除默认handler + # Keep configuration setup explicit so runtime behavior is easy to reason about. logger.remove() - # 添加控制台输出 + # Keep configuration setup explicit so runtime behavior is easy to reason about. logger.add( sys.stdout, level=level, @@ -18,7 +20,7 @@ def setup_logging(level: str = "INFO"): colorize=True ) - # 添加文件输出 + # Keep configuration setup explicit so runtime behavior is easy to reason about. logger.add( "logs/app_{time:YYYY-MM-DD}.log", level=level, diff --git a/backend/app/config/settings.py b/backend/app/config/settings.py index e17d1a3..2b09e95 100644 --- a/backend/app/config/settings.py +++ b/backend/app/config/settings.py @@ -1,94 +1,119 @@ -"""配置管理 - 环境变量和默认配置""" +"""Configure backend settings for settings.""" -from pydantic_settings import BaseSettings +from pathlib import Path + +from pydantic_settings import BaseSettings, SettingsConfigDict from pydantic import Field -from typing import Optional from functools import lru_cache +# Keep configuration setup explicit so runtime behavior is easy to reason about. + + +ROOT_DIR = Path(__file__).resolve().parents[3] +ROOT_ENV_FILES = ( + ROOT_DIR / ".env", + ROOT_DIR / ".env.development", +) + class Settings(BaseSettings): - """应用配置""" + """Define configuration for settings.""" - # 应用基础配置 + model_config = SettingsConfigDict( + env_file=tuple(str(env_file) for env_file in ROOT_ENV_FILES), + env_file_encoding="utf-8", + extra="ignore", + ) + + # Keep configuration setup explicit so runtime behavior is easy to reason about. app_name: str = Field(default="AI Regulations Demo", description="Application name") app_version: str = Field(default="0.1.0", description="应用版本") debug: bool = Field(default=False, description="调试模式") - # Milvus向量数据库配置 + # Keep configuration setup explicit so runtime behavior is easy to reason about. milvus_host: str = Field(default="localhost", description="Milvus服务地址") milvus_port: int = Field(default=19530, description="Milvus服务端口") - milvus_collection: str = Field(default="regulations", description="法规向量集合名称") + milvus_collection: str = Field(default="regulations_dense_1536", description="法规向量集合名称") milvus_db_name: str = Field(default="default", description="Milvus数据库名称") - # 嵌入模型配置 - embedding_model: str = Field(default="BAAI/bge-m3", description="嵌入模型名称") - embedding_dim: int = Field(default=1024, description="嵌入向量维度") - embedding_max_length: int = Field(default=8192, description="最大嵌入长度") - embedding_batch_size: int = Field(default=12, description="嵌入批处理大小") - embedding_use_fp16: bool = Field(default=True, description="使用FP16加速") + # Keep configuration setup explicit so runtime behavior is easy to reason about. + embedding_model: str = Field(default="text-embedding-v3", description="嵌入模型名称") + embedding_dim: int = Field(default=1536, description="嵌入向量维度") + embedding_api_key: str = Field(default="", description="Embedding API密钥") + embedding_base_url: str = Field(default="http://6.86.80.4:30080/v1", description="Embedding API地址") + embedding_timeout_seconds: int = Field(default=120, description="Embedding API超时时间(秒)") + alibaba_access_key_id: str = Field(default="", description="阿里云文档解析 Access Key ID") + alibaba_access_key_secret: str = Field(default="", description="阿里云文档解析 Access Key Secret") + alibaba_endpoint: str = Field(default="docmind-api.cn-hangzhou.aliyuncs.com", description="阿里云文档解析 endpoint") - # MinIO对象存储配置 + # Keep configuration setup explicit so runtime behavior is easy to reason about. minio_endpoint: str = Field(default="localhost:9000", description="MinIO服务地址") minio_access_key: str = Field(default="minioadmin", description="MinIO访问密钥") minio_secret_key: str = Field(default="minioadmin123", description="MinIO秘密密钥") minio_bucket: str = Field(default="upload-files", description="文档存储桶名称") minio_secure: bool = Field(default=False, description="是否使用HTTPS") - # Redis配置 + # Keep configuration setup explicit so runtime behavior is easy to reason about. redis_host: str = Field(default="localhost", description="Redis服务地址") redis_port: int = Field(default=6379, description="Redis服务端口") redis_password: str = Field(default="", description="Redis密码") redis_db: int = Field(default=0, description="Redis数据库编号") - # PostgreSQL配置 + # Keep configuration setup explicit so runtime behavior is easy to reason about. postgres_host: str = Field(default="localhost", description="PostgreSQL服务地址") postgres_port: int = Field(default=5432, description="PostgreSQL服务端口") postgres_user: str = Field(default="compliance", description="PostgreSQL用户名") postgres_password: str = Field(default="compliance123", description="PostgreSQL密码") postgres_db: str = Field(default="compliance_db", description="PostgreSQL数据库名称") - # 文档处理配置 + # Keep configuration setup explicit so runtime behavior is easy to reason about. chunk_size: int = Field(default=512, description="分块大小(字符数)") chunk_overlap: int = Field(default=50, description="分块重叠大小") max_file_size_mb: int = Field(default=100, description="最大文件大小(MB)") + document_metadata_path: str = Field(default="backend/data/documents.json", description="文档元数据存储路径") + parser_backend: str = Field(default="local", description="解析后端(local/aliyun)") + chunk_backend: str = Field(default="local", description="分块后端(local/aliyun)") - # API配置 + # Keep configuration setup explicit so runtime behavior is easy to reason about. api_host: str = Field(default="0.0.0.0", description="API服务地址") api_port: int = Field(default=8000, description="API服务端口") - # LLM配置 + # Keep configuration setup explicit so runtime behavior is easy to reason about. llm_provider: str = Field(default="deepseek", description="LLM提供商 (deepseek/qwen/qwen_vl)") llm_model: str = Field(default="deepseek-v4-flash", description="LLM模型名称") llm_max_tokens: int = Field(default=4096, description="LLM最大输出token数") llm_temperature: float = Field(default=0.7, description="LLM温度参数") - # DeepSeek配置 + # Keep configuration setup explicit so runtime behavior is easy to reason about. deepseek_api_key: str = Field(default="", description="DeepSeek API密钥") deepseek_base_url: str = Field(default="http://6.86.80.4:30080/v1", description="DeepSeek API地址") deepseek_model: str = Field(default="deepseek-v4-flash", description="DeepSeek模型") - # Qwen配置(通过统一代理API) + # Keep configuration setup explicit so runtime behavior is easy to reason about. qwen_api_key: str = Field(default="", description="Qwen API密钥") qwen_base_url: str = Field(default="http://6.86.80.4:30080/v1", description="Qwen API地址") qwen_model: str = Field(default="qwen3.5-flash", description="Qwen文本模型") qwen_vl_model: str = Field(default="qwen3-vl-plus", description="Qwen视觉模型") - # RAG配置 + # Keep configuration setup explicit so runtime behavior is easy to reason about. rag_top_k: int = Field(default=5, description="检索召回数量") rag_max_context_tokens: int = Field(default=2000, description="RAG最大上下文token数") rag_summary_max_tokens: int = Field(default=10240, description="文档摘要最大token数") - class Config: - env_file = ".env" - env_file_encoding = "utf-8" - extra = "ignore" + # Keep configuration setup explicit so runtime behavior is easy to reason about. + milvus_index_type: str = Field(default="IVF_FLAT", description="Milvus索引类型") + milvus_nlist: int = Field(default=128, description="Milvus nlist参数") + milvus_nprobe: int = Field(default=16, description="Milvus nprobe参数") + # Keep configuration setup explicit so runtime behavior is easy to reason about. + session_max_sessions: int = Field(default=100, description="最大会话数量") + session_timeout_minutes: int = Field(default=30, description="会话超时时间(分钟)") @lru_cache def get_settings() -> Settings: - """获取配置实例(缓存)""" + """Return settings.""" return Settings() -# 导出默认配置实例 +# Keep configuration setup explicit so runtime behavior is easy to reason about. settings = get_settings() diff --git a/backend/app/core/__init__.py b/backend/app/core/__init__.py index e849b30..b138f3f 100644 --- a/backend/app/core/__init__.py +++ b/backend/app/core/__init__.py @@ -1,3 +1,7 @@ +"""Initialize the app.core package.""" + from .config import settings, Settings +# Keep package boundaries explicit so backend imports stay predictable. + __all__ = ["settings", "Settings"] \ No newline at end of file diff --git a/backend/app/core/config.py b/backend/app/core/config.py index 099a31d..a1102db 100644 --- a/backend/app/core/config.py +++ b/backend/app/core/config.py @@ -1,41 +1,54 @@ -from pydantic_settings import BaseSettings -from typing import Optional +"""Legacy-compatible config used by older utility modules.""" + +from pathlib import Path + +from pydantic_settings import BaseSettings, SettingsConfigDict + +# Keep legacy settings aligned with the root-level env loading rules. + + +ROOT_DIR = Path(__file__).resolve().parents[3] +ROOT_ENV_FILES = tuple(str(path) for path in (ROOT_DIR / ".env", ROOT_DIR / ".env.development")) class Settings(BaseSettings): # DashScope API + """Define configuration for settings.""" + + model_config = SettingsConfigDict( + env_file=ROOT_ENV_FILES, + env_file_encoding="utf-8", + case_sensitive=False, + extra="ignore", + ) + dashscope_api_key: str = "" # Milvus milvus_host: str = "localhost" milvus_port: int = 19530 + milvus_collection: str = "regulations_dense_1536" - # LLM配置 + # LLM / embedding defaults aligned with the migrated backend path. llm_model: str = "qwen-max" embedding_model: str = "text-embedding-v3" embedding_dim: int = 1536 - # 检索配置 + # Legacy workflow compatibility only. vector_top_k: int = 10 - bm25_top_k: int = 10 final_top_k: int = 5 - # 分块配置 + # Legacy local chunking compatibility only; main ingest now uses Aliyun vector_chunks. chunk_size: int = 800 chunk_overlap: int = 50 - # 服务配置 + # Service config. api_host: str = "0.0.0.0" api_port: int = 8000 - # Collection名称 - regulations_collection: str = "vehicle_regulations" + # Legacy aliases retained for old utility modules. + regulations_collection: str = "regulations_dense_1536" compliance_collection: str = "compliance_cache" - class Config: - env_file = ".env" - env_file_encoding = "utf-8" - case_sensitive = False - - -settings = Settings() \ No newline at end of file +# Preserve the legacy module API while keeping env resolution centralized at the repo root. +settings = Settings() diff --git a/backend/app/domain/__init__.py b/backend/app/domain/__init__.py new file mode 100644 index 0000000..ec845dc --- /dev/null +++ b/backend/app/domain/__init__.py @@ -0,0 +1,5 @@ +"""Initialize the app.domain package.""" +# Keep package boundaries explicit so backend imports stay predictable. + + +__all__ = [] diff --git a/backend/app/domain/conversation/__init__.py b/backend/app/domain/conversation/__init__.py new file mode 100644 index 0000000..3928d5b --- /dev/null +++ b/backend/app/domain/conversation/__init__.py @@ -0,0 +1,15 @@ +"""Initialize the app.domain.conversation package.""" + +from .models import AnswerResult, AnswerSource, ConversationMessage, ConversationSession +from .ports import AnswerGenerator, ConversationStore +# Keep package boundaries explicit so backend imports stay predictable. + + +__all__ = [ + "AnswerGenerator", + "AnswerResult", + "AnswerSource", + "ConversationMessage", + "ConversationSession", + "ConversationStore", +] diff --git a/backend/app/domain/conversation/models.py b/backend/app/domain/conversation/models.py new file mode 100644 index 0000000..114c16b --- /dev/null +++ b/backend/app/domain/conversation/models.py @@ -0,0 +1,53 @@ +"""Define domain models for conversation.""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Any +# Keep module behavior explicit so the backend flow stays easy to audit. + + + +@dataclass +class AnswerSource: + """Represent answer source data.""" + doc_id: str + doc_name: str + chunk_id: str + section_title: str + page_number: int + score: float + content: str + metadata: dict[str, Any] = field(default_factory=dict) + + +@dataclass +class ConversationMessage: + """Represent conversation message data.""" + role: str + content: str + timestamp: int + sources: list[dict[str, Any]] = field(default_factory=list) + + +@dataclass +class ConversationSession: + """Represent conversation session data.""" + session_id: str + messages: list[ConversationMessage] = field(default_factory=list) + created_at: int = 0 + updated_at: int = 0 + metadata: dict[str, Any] = field(default_factory=dict) + + +@dataclass +class AnswerResult: + """Represent answer result data.""" + answer: str + sources: list[AnswerSource] = field(default_factory=list) + model: str = "" + latency_ms: int = 0 + retrieved_count: int = 0 + context_tokens: int = 0 + truncated: bool = False + error: str | None = None diff --git a/backend/app/domain/conversation/ports.py b/backend/app/domain/conversation/ports.py new file mode 100644 index 0000000..922edc5 --- /dev/null +++ b/backend/app/domain/conversation/ports.py @@ -0,0 +1,78 @@ +"""Define domain ports for conversation.""" + +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import Generator + +from app.domain.retrieval.models import RetrievedChunk + +from .models import AnswerResult, ConversationSession +# Keep domain contracts explicit so adapters can swap implementations cleanly. + + + +class AnswerGenerator(ABC): + """Represent the Answer Generator type.""" + @abstractmethod + def generate( + self, + *, + query: str, + retrieved_chunks: list[RetrievedChunk], + history: list[dict[str, str]] | None = None, + provider: str | None = None, + model: str | None = None, + prompt_template: str | None = None, + ) -> AnswerResult: + """Handle generate for the Answer Generator instance.""" + pass + + @abstractmethod + def stream_generate( + self, + *, + query: str, + retrieved_chunks: list[RetrievedChunk], + history: list[dict[str, str]] | None = None, + provider: str | None = None, + model: str | None = None, + prompt_template: str | None = None, + ) -> Generator[dict, None, AnswerResult]: + """Stream generate for the Answer Generator instance.""" + pass + + +class ConversationStore(ABC): + """Provide the Conversation Store store implementation.""" + @abstractmethod + def create_session(self, metadata: dict | None = None) -> ConversationSession: + """Create session for the Conversation Store instance.""" + pass + + @abstractmethod + def get_session(self, session_id: str) -> ConversationSession | None: + """Return session for the Conversation Store instance.""" + pass + + @abstractmethod + def save_message( + self, + session_id: str, + *, + role: str, + content: str, + sources: list[dict] | None = None, + ) -> ConversationSession | None: + """Save message for the Conversation Store instance.""" + pass + + @abstractmethod + def delete_session(self, session_id: str) -> bool: + """Delete session for the Conversation Store instance.""" + pass + + @abstractmethod + def list_sessions(self) -> list[dict]: + """List sessions for the Conversation Store instance.""" + pass diff --git a/backend/app/domain/documents/__init__.py b/backend/app/domain/documents/__init__.py new file mode 100644 index 0000000..d4e36ea --- /dev/null +++ b/backend/app/domain/documents/__init__.py @@ -0,0 +1,17 @@ +"""Initialize the app.domain.documents package.""" + +from .models import Chunk, Document, DocumentStatus, ParsedDocument +from .ports import ChunkBuilder, DocumentBinaryStore, DocumentParser, DocumentRepository +# Keep package boundaries explicit so backend imports stay predictable. + + +__all__ = [ + "Chunk", + "Document", + "DocumentStatus", + "ParsedDocument", + "ChunkBuilder", + "DocumentBinaryStore", + "DocumentParser", + "DocumentRepository", +] diff --git a/backend/app/domain/documents/models.py b/backend/app/domain/documents/models.py new file mode 100644 index 0000000..483231d --- /dev/null +++ b/backend/app/domain/documents/models.py @@ -0,0 +1,77 @@ +"""Define domain models for documents.""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from datetime import UTC, datetime +from enum import Enum +from typing import Any +# Keep module behavior explicit so the backend flow stays easy to audit. + + +def utcnow() -> datetime: + return datetime.now(UTC) + + + +class DocumentStatus(str, Enum): + """Define the Document Status enumeration.""" + PENDING = "pending" + STORED = "stored" + PARSED = "parsed" + INDEXED = "indexed" + FAILED = "failed" + + +@dataclass +class Document: + """Represent the Document type.""" + doc_id: str + doc_name: str + file_name: str + object_name: str + content_type: str + size_bytes: int + status: DocumentStatus = DocumentStatus.PENDING + regulation_type: str = "" + version: str = "" + summary: str = "" + summary_latency_ms: int = 0 + chunk_count: int = 0 + parser_name: str = "" + index_name: str = "" + error_message: str = "" + created_at: datetime = field(default_factory=utcnow) + updated_at: datetime = field(default_factory=utcnow) + metadata: dict[str, Any] = field(default_factory=dict) + + +@dataclass +class ParsedDocument: + """Represent the Parsed Document type.""" + doc_id: str + doc_name: str + structure_nodes: list[dict[str, Any]] + semantic_blocks: list[dict[str, Any]] + vector_chunks: list[dict[str, Any]] + parser_name: str + raw_text: str = "" + metadata: dict[str, Any] = field(default_factory=dict) + + +@dataclass +class Chunk: + """Represent the Chunk type.""" + chunk_id: str + doc_id: str + doc_name: str + content: str + embedding_text: str + section_title: str = "" + section_path: list[str] = field(default_factory=list) + page_number: int = 0 + regulation_type: str = "" + version: str = "" + semantic_id: str = "" + block_type: str = "" + metadata: dict[str, Any] = field(default_factory=dict) diff --git a/backend/app/domain/documents/ports.py b/backend/app/domain/documents/ports.py new file mode 100644 index 0000000..6cbb715 --- /dev/null +++ b/backend/app/domain/documents/ports.py @@ -0,0 +1,96 @@ +"""Define domain ports for documents.""" + +from __future__ import annotations + +from abc import ABC, abstractmethod + +from .models import Chunk, Document, DocumentStatus, ParsedDocument +# Keep domain contracts explicit so adapters can swap implementations cleanly. + + + +class DocumentRepository(ABC): + """Provide the Document Repository repository implementation.""" + @abstractmethod + def create(self, document: Document) -> Document: + """Handle create for the Document Repository instance.""" + pass + + @abstractmethod + def update(self, document: Document) -> Document: + """Handle update for the Document Repository instance.""" + pass + + @abstractmethod + def get(self, doc_id: str) -> Document | None: + """Handle get for the Document Repository instance.""" + pass + + @abstractmethod + def list(self, limit: int | None = None) -> list[Document]: + """Handle list for the Document Repository instance.""" + pass + + @abstractmethod + def update_status( + self, + doc_id: str, + status: DocumentStatus, + *, + error_message: str = "", + chunk_count: int | None = None, + summary: str | None = None, + summary_latency_ms: int | None = None, + parser_name: str | None = None, + index_name: str | None = None, + metadata: dict | None = None, + ) -> Document | None: + """Update status for the Document Repository instance.""" + pass + + +class DocumentBinaryStore(ABC): + """Provide the Document Binary Store store implementation.""" + @abstractmethod + def save( + self, + *, + object_name: str, + data: bytes, + content_type: str, + metadata: dict[str, str] | None = None, + ) -> None: + """Handle save for the Document Binary Store instance.""" + pass + + @abstractmethod + def read(self, object_name: str) -> bytes: + """Handle read for the Document Binary Store instance.""" + pass + + @abstractmethod + def delete(self, object_name: str) -> None: + """Handle delete for the Document Binary Store instance.""" + pass + + +class DocumentParser(ABC): + """Provide the Document Parser parser.""" + @abstractmethod + def parse(self, *, file_path: str, doc_id: str, doc_name: str) -> ParsedDocument: + """Handle parse for the Document Parser instance.""" + pass + + +class ChunkBuilder(ABC): + """Provide the Chunk Builder builder.""" + @abstractmethod + def build( + self, + *, + parsed_document: ParsedDocument, + regulation_type: str, + version: str, + ) -> list[Chunk]: + """Handle build for the Chunk Builder instance.""" + pass diff --git a/backend/app/domain/retrieval/__init__.py b/backend/app/domain/retrieval/__init__.py new file mode 100644 index 0000000..56426f6 --- /dev/null +++ b/backend/app/domain/retrieval/__init__.py @@ -0,0 +1,8 @@ +"""Initialize the app.domain.retrieval package.""" + +from .models import RetrievalQuery, RetrievedChunk +from .ports import EmbeddingProvider, Retriever, VectorIndex +# Keep package boundaries explicit so backend imports stay predictable. + + +__all__ = ["RetrievalQuery", "RetrievedChunk", "EmbeddingProvider", "Retriever", "VectorIndex"] diff --git a/backend/app/domain/retrieval/models.py b/backend/app/domain/retrieval/models.py new file mode 100644 index 0000000..1c87b8a --- /dev/null +++ b/backend/app/domain/retrieval/models.py @@ -0,0 +1,29 @@ +"""Define domain models for retrieval.""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Any +# Keep module behavior explicit so the backend flow stays easy to audit. + + + +@dataclass +class RetrievalQuery: + """Represent the Retrieval Query type.""" + query: str + top_k: int + filters: str | None = None + + +@dataclass +class RetrievedChunk: + """Represent the Retrieved Chunk type.""" + chunk_id: str + doc_id: str + doc_name: str + content: str + score: float + section_title: str = "" + page_number: int = 0 + metadata: dict[str, Any] = field(default_factory=dict) diff --git a/backend/app/domain/retrieval/ports.py b/backend/app/domain/retrieval/ports.py new file mode 100644 index 0000000..8d5e2c1 --- /dev/null +++ b/backend/app/domain/retrieval/ports.py @@ -0,0 +1,60 @@ +"""Define domain ports for retrieval.""" + +from __future__ import annotations + +from abc import ABC, abstractmethod + +from app.domain.documents.models import Chunk + +from .models import RetrievalQuery, RetrievedChunk +# Keep domain contracts explicit so adapters can swap implementations cleanly. + + + +class EmbeddingProvider(ABC): + """Provide the Embedding Provider provider.""" + @abstractmethod + def embed_texts(self, texts: list[str]) -> list[list[float]]: + """Embed texts for the Embedding Provider instance.""" + pass + + @abstractmethod + def embed_query(self, text: str) -> list[float]: + """Embed query for the Embedding Provider instance.""" + pass + + +class VectorIndex(ABC): + """Provide the Vector Index index implementation.""" + @abstractmethod + def upsert(self, chunks: list[Chunk], vectors: list[list[float]]) -> int: + """Handle upsert for the Vector Index instance.""" + pass + + @abstractmethod + def delete_by_document(self, doc_id: str) -> int: + """Delete by document for the Vector Index instance.""" + pass + + @abstractmethod + def search(self, query_vector: list[float], top_k: int, filters: str | None = None) -> list[RetrievedChunk]: + """Handle search for the Vector Index instance.""" + pass + + @abstractmethod + def health(self) -> dict: + """Handle health for the Vector Index instance.""" + pass + + +class Retriever(ABC): + """Provide the Retriever retriever.""" + @abstractmethod + def retrieve(self, query: RetrievalQuery) -> list[RetrievedChunk]: + """Handle retrieve for the Retriever instance.""" + pass + + @abstractmethod + def search(self, query: str, top_k: int, filters: str | None = None) -> list[RetrievedChunk]: + """Handle search for the Retriever instance.""" + pass diff --git a/backend/app/infrastructure/__init__.py b/backend/app/infrastructure/__init__.py new file mode 100644 index 0000000..ae4ab51 --- /dev/null +++ b/backend/app/infrastructure/__init__.py @@ -0,0 +1,5 @@ +"""Initialize the app.infrastructure package.""" +# Keep package boundaries explicit so backend imports stay predictable. + + +__all__ = [] diff --git a/backend/app/infrastructure/embedding/__init__.py b/backend/app/infrastructure/embedding/__init__.py new file mode 100644 index 0000000..d3ae1aa --- /dev/null +++ b/backend/app/infrastructure/embedding/__init__.py @@ -0,0 +1,5 @@ +"""Initialize the app.infrastructure.embedding package.""" +# Keep package boundaries explicit so backend imports stay predictable. + + +__all__ = [] diff --git a/backend/app/infrastructure/embedding/openai_compatible_embedding_provider.py b/backend/app/infrastructure/embedding/openai_compatible_embedding_provider.py new file mode 100644 index 0000000..37a71e4 --- /dev/null +++ b/backend/app/infrastructure/embedding/openai_compatible_embedding_provider.py @@ -0,0 +1,59 @@ +"""Implement infrastructure support for openai compatible embedding provider.""" + +from __future__ import annotations + +import os + +import httpx + +from app.config.settings import settings +from app.domain.retrieval import EmbeddingProvider +# Keep adapter behavior explicit so integration details remain easy to audit. + + + +class OpenAICompatibleEmbeddingProvider(EmbeddingProvider): + """Provide the Open A I Compatible Embedding Provider provider.""" + def __init__(self) -> None: + """Initialize the Open A I Compatible Embedding Provider instance.""" + self.base_url = settings.embedding_base_url.rstrip("/") + self.api_key = ( + settings.embedding_api_key + or os.getenv("OPENAI_API_KEY", "") + or os.getenv("QWEN_API_KEY", "") + or os.getenv("DEEPSEEK_API_KEY", "") + ) + self.model = settings.embedding_model + self.timeout = settings.embedding_timeout_seconds + self.dimension = settings.embedding_dim + + def _request(self, texts: list[str]) -> list[list[float]]: + """Handle request for this module for the Open A I Compatible Embedding Provider instance.""" + if not self.api_key: + raise ValueError("缺少 EMBEDDING_API_KEY / OPENAI_API_KEY") + response = httpx.post( + f"{self.base_url}/embeddings", + headers={ + "Authorization": f"Bearer {self.api_key}", + "Content-Type": "application/json", + }, + json={"model": self.model, "input": texts}, + timeout=self.timeout, + ) + response.raise_for_status() + data = response.json() + vectors = [item["embedding"] for item in sorted(data.get("data", []), key=lambda item: item["index"])] + if any(len(vector) != self.dimension for vector in vectors): + raise ValueError(f"embedding 维度不匹配,期望 {self.dimension}") + return vectors + + def embed_texts(self, texts: list[str]) -> list[list[float]]: + """Embed texts for the Open A I Compatible Embedding Provider instance.""" + if not texts: + return [] + return self._request(texts) + + def embed_query(self, text: str) -> list[float]: + """Embed query for the Open A I Compatible Embedding Provider instance.""" + vectors = self._request([text]) + return vectors[0] diff --git a/backend/app/infrastructure/llm/openai_compatible_answer_generator.py b/backend/app/infrastructure/llm/openai_compatible_answer_generator.py new file mode 100644 index 0000000..9d87c86 --- /dev/null +++ b/backend/app/infrastructure/llm/openai_compatible_answer_generator.py @@ -0,0 +1,144 @@ +"""Implement infrastructure support for openai compatible answer generator.""" + +from __future__ import annotations + +import time +from typing import Generator + +from app.config.settings import settings +from app.domain.conversation import AnswerGenerator, AnswerResult, AnswerSource +from app.domain.retrieval import RetrievedChunk +from app.services.llm.llm_factory import get_llm_client +# Keep adapter behavior explicit so integration details remain easy to audit. + + + +PROMPT_TEMPLATES = { + "default": "你是法规知识问答助手。请仅依据提供的上下文回答;如果上下文不足,明确说明。", + "compliance_qa": "你是法规合规问答助手。优先引用给定法规原文,回答要准确、克制,并注明依据来源。", +} + + +class OpenAICompatibleAnswerGenerator(AnswerGenerator): + """Represent the Open A I Compatible Answer Generator type.""" + def _build_messages( + self, + *, + query: str, + retrieved_chunks: list[RetrievedChunk], + history: list[dict[str, str]] | None, + prompt_template: str | None, + ) -> tuple[list[dict[str, str]], int]: + """Handle build messages for this module for the Open A I Compatible Answer Generator instance.""" + system_prompt = PROMPT_TEMPLATES.get(prompt_template or "compliance_qa", PROMPT_TEMPLATES["default"]) + context_blocks = [] + context_tokens = 0 + for idx, chunk in enumerate(retrieved_chunks, start=1): + block = ( + f"[{idx}] 文档: {chunk.doc_name}\n" + f"章节: {chunk.section_title or '未标注'}\n" + f"页码: {chunk.page_number}\n" + f"内容: {chunk.content}" + ) + context_tokens += len(block) + context_blocks.append(block) + context = "\n\n".join(context_blocks)[: settings.rag_max_context_tokens * 4] + messages = [{"role": "system", "content": system_prompt}] + for item in history or []: + messages.append({"role": item["role"], "content": item["content"]}) + messages.append( + { + "role": "user", + "content": f"问题:{query}\n\n参考上下文:\n{context}\n\n请在回答后给出简要引用编号。", + } + ) + return messages, min(context_tokens, settings.rag_max_context_tokens) + + def _sources(self, chunks: list[RetrievedChunk]) -> list[AnswerSource]: + """Handle sources for this module for the Open A I Compatible Answer Generator instance.""" + return [ + AnswerSource( + doc_id=chunk.doc_id, + doc_name=chunk.doc_name, + chunk_id=chunk.chunk_id, + section_title=chunk.section_title, + page_number=chunk.page_number, + score=chunk.score, + content=chunk.content, + metadata=chunk.metadata, + ) + for chunk in chunks + ] + + def generate( + self, + *, + query: str, + retrieved_chunks: list[RetrievedChunk], + history: list[dict[str, str]] | None = None, + provider: str | None = None, + model: str | None = None, + prompt_template: str | None = None, + ) -> AnswerResult: + """Handle generate for the Open A I Compatible Answer Generator instance.""" + start = time.time() + messages, context_tokens = self._build_messages( + query=query, + retrieved_chunks=retrieved_chunks, + history=history, + prompt_template=prompt_template, + ) + client = get_llm_client(provider=provider or settings.llm_provider, model=model or settings.llm_model) + response = client.chat(messages) + latency_ms = int((time.time() - start) * 1000) + return AnswerResult( + answer=response.content if response.is_success else "", + sources=self._sources(retrieved_chunks), + model=response.model or (model or settings.llm_model), + latency_ms=latency_ms, + retrieved_count=len(retrieved_chunks), + context_tokens=context_tokens, + truncated=False, + error=response.error, + ) + + def stream_generate( + self, + *, + query: str, + retrieved_chunks: list[RetrievedChunk], + history: list[dict[str, str]] | None = None, + provider: str | None = None, + model: str | None = None, + prompt_template: str | None = None, + ) -> Generator[dict, None, AnswerResult]: + """Stream generate for the Open A I Compatible Answer Generator instance.""" + start = time.time() + messages, context_tokens = self._build_messages( + query=query, + retrieved_chunks=retrieved_chunks, + history=history, + prompt_template=prompt_template, + ) + sources = [source.__dict__ for source in self._sources(retrieved_chunks)] + yield {"event": "sources", "data": sources} + client = get_llm_client(provider=provider or settings.llm_provider, model=model or settings.llm_model) + answer_parts: list[str] = [] + if hasattr(client, "stream_chat"): + for chunk in client.stream_chat(messages): + answer_parts.append(chunk) + yield {"event": "content", "data": chunk} + else: + response = client.chat(messages) + answer_parts.append(response.content) + yield {"event": "content", "data": response.content} + full_answer = "".join(answer_parts) + yield { + "event": "done", + "data": { + "latency_ms": int((time.time() - start) * 1000), + "retrieved_count": len(retrieved_chunks), + "context_tokens": context_tokens, + "model": model or settings.llm_model, + }, + } diff --git a/backend/app/infrastructure/parser/__init__.py b/backend/app/infrastructure/parser/__init__.py new file mode 100644 index 0000000..eeeb6c6 --- /dev/null +++ b/backend/app/infrastructure/parser/__init__.py @@ -0,0 +1,5 @@ +"""Initialize the app.infrastructure.parser package.""" +# Keep package boundaries explicit so backend imports stay predictable. + + +__all__ = [] diff --git a/backend/app/infrastructure/parser/aliyun_document_parser.py b/backend/app/infrastructure/parser/aliyun_document_parser.py new file mode 100644 index 0000000..cb696cc --- /dev/null +++ b/backend/app/infrastructure/parser/aliyun_document_parser.py @@ -0,0 +1,55 @@ +"""Implement infrastructure support for aliyun document parser.""" + +from __future__ import annotations + +from app.aliyun_parser.parse_pdf import ( + MAX_CHARS, + OVERLAP_CHARS, + build_semantic_blocks, + build_structure_nodes, + build_vector_chunks, + collect_all_results, + init_client, + submit_job, + wait_for_completion, +) +from app.domain.documents import DocumentParser, ParsedDocument +# Keep adapter behavior explicit so integration details remain easy to audit. + + + +class AliyunDocumentParser(DocumentParser): + """Provide the Aliyun Document Parser parser.""" + parser_name = "aliyun_docmind" + + def parse(self, *, file_path: str, doc_id: str, doc_name: str) -> ParsedDocument: + """Handle parse for the Aliyun Document Parser instance.""" + client = init_client() + task_id = submit_job(client, file_path) + if not wait_for_completion(client, task_id): + raise RuntimeError("阿里云文档解析任务失败") + layouts = collect_all_results(client, task_id) + structure_nodes = build_structure_nodes(layouts) + semantic_blocks = build_semantic_blocks(layouts) + vector_chunks = build_vector_chunks( + semantic_blocks, + doc_id=doc_id, + doc_title=doc_name, + max_chars=MAX_CHARS, + overlap_chars=OVERLAP_CHARS, + ) + raw_text = "\n\n".join( + block.get("text", "") + for block in semantic_blocks + if block.get("text") + ) + return ParsedDocument( + doc_id=doc_id, + doc_name=doc_name, + structure_nodes=structure_nodes, + semantic_blocks=semantic_blocks, + vector_chunks=vector_chunks, + parser_name=self.parser_name, + raw_text=raw_text, + metadata={"task_id": task_id, "layout_count": len(layouts)}, + ) diff --git a/backend/app/infrastructure/parser/local_chunk_builder.py b/backend/app/infrastructure/parser/local_chunk_builder.py new file mode 100644 index 0000000..00732e6 --- /dev/null +++ b/backend/app/infrastructure/parser/local_chunk_builder.py @@ -0,0 +1,66 @@ +"""Local chunk builder adapter for the migrated backend architecture.""" + +from __future__ import annotations + +from app.domain.documents import Chunk, ChunkBuilder, ParsedDocument +from app.services.embedding.text_chunker import RegulationChunker + + +class LocalRegulationChunkBuilder(ChunkBuilder): + """Adapt the existing markdown chunker to the new chunk builder port.""" + + def __init__(self, *, chunk_size: int = 512, chunk_overlap: int = 50) -> None: + self.chunker = RegulationChunker( + chunk_size=chunk_size, + chunk_overlap=chunk_overlap, + ) + + def build( + self, + *, + parsed_document: ParsedDocument, + regulation_type: str, + version: str, + ) -> list[Chunk]: + markdown_text = parsed_document.raw_text.strip() + if not markdown_text: + return [] + + legacy_chunks = self.chunker.chunk_document( + markdown_text, + doc_id=parsed_document.doc_id, + doc_name=parsed_document.doc_name, + regulation_type=regulation_type, + version=version, + ) + + chunks: list[Chunk] = [] + for item in legacy_chunks: + metadata = { + "section_number": item.metadata.section_number, + "section_title": item.metadata.section_title, + "clause_number": item.metadata.clause_number, + "start_position": item.metadata.start_position, + "end_position": item.metadata.end_position, + "token_count": item.token_count, + "source": "local_chunker", + } + section_path = [value for value in [item.metadata.section_number, item.metadata.section_title] if value] + chunks.append( + Chunk( + chunk_id=item.metadata.chunk_id, + doc_id=parsed_document.doc_id, + doc_name=parsed_document.doc_name, + content=item.content, + embedding_text=item.content, + section_title=item.metadata.section_title or item.metadata.section_number, + section_path=section_path, + page_number=item.metadata.page_number, + regulation_type=regulation_type, + version=version, + semantic_id=item.metadata.clause_number, + block_type="local_markdown_chunk", + metadata=metadata, + ) + ) + return chunks diff --git a/backend/app/infrastructure/parser/local_document_parser.py b/backend/app/infrastructure/parser/local_document_parser.py new file mode 100644 index 0000000..1379e71 --- /dev/null +++ b/backend/app/infrastructure/parser/local_document_parser.py @@ -0,0 +1,38 @@ +"""Local parser adapter for the migrated backend architecture.""" + +from __future__ import annotations + +from pathlib import Path + +from app.domain.documents import DocumentParser, ParsedDocument +from app.services.parser.docx_parser import parse_docx_to_markdown +from app.services.parser.pdf_parser import parse_pdf_to_markdown + + +class LocalDocumentParser(DocumentParser): + """Adapt the existing local PDF/DOCX parsers to the new parser port.""" + + parser_name = "local_markdown_parser" + + def parse(self, *, file_path: str, doc_id: str, doc_name: str) -> ParsedDocument: + suffix = Path(file_path).suffix.lower() + if suffix == ".pdf": + markdown_text = parse_pdf_to_markdown(file_path) + elif suffix in {".docx", ".doc"}: + markdown_text = parse_docx_to_markdown(file_path) + else: + raise ValueError(f"不支持的文件类型: {suffix}") + + if not markdown_text.strip(): + raise ValueError("本地解析完成但未提取到有效文本") + + return ParsedDocument( + doc_id=doc_id, + doc_name=doc_name, + structure_nodes=[], + semantic_blocks=[], + vector_chunks=[], + parser_name=self.parser_name, + raw_text=markdown_text, + metadata={"source": "local_parser", "file_suffix": suffix}, + ) diff --git a/backend/app/infrastructure/parser/vector_chunk_builder.py b/backend/app/infrastructure/parser/vector_chunk_builder.py new file mode 100644 index 0000000..0e2bce5 --- /dev/null +++ b/backend/app/infrastructure/parser/vector_chunk_builder.py @@ -0,0 +1,48 @@ +"""Implement infrastructure support for vector chunk builder.""" + +from __future__ import annotations + +from app.domain.documents import Chunk, ChunkBuilder, ParsedDocument +# Keep adapter behavior explicit so integration details remain easy to audit. + + + +class AliyunVectorChunkBuilder(ChunkBuilder): + """Provide the Aliyun Vector Chunk Builder builder.""" + def build( + self, + *, + parsed_document: ParsedDocument, + regulation_type: str, + version: str, + ) -> list[Chunk]: + """Handle build for the Aliyun Vector Chunk Builder instance.""" + chunks: list[Chunk] = [] + for index, item in enumerate(parsed_document.vector_chunks): + content = item.get("content") or item.get("text") or "" + embedding_text = item.get("embedding_text") or content + if not embedding_text.strip(): + continue + section_path = item.get("section_path") or [] + section_title = item.get("section_title") or (section_path[-1] if section_path else "") + page_number = item.get("page_start") or item.get("page") or 0 + chunk_id = item.get("chunk_id") or f"{parsed_document.doc_id}-chunk-{index}" + metadata = {k: v for k, v in item.items() if k not in {"content", "embedding_text"}} + chunks.append( + Chunk( + chunk_id=str(chunk_id), + doc_id=parsed_document.doc_id, + doc_name=parsed_document.doc_name, + content=content, + embedding_text=embedding_text, + section_title=section_title, + section_path=section_path, + page_number=int(page_number or 0), + regulation_type=regulation_type, + version=version, + semantic_id=item.get("semantic_id", ""), + block_type=item.get("block_type", ""), + metadata=metadata, + ) + ) + return chunks diff --git a/backend/app/infrastructure/session/__init__.py b/backend/app/infrastructure/session/__init__.py new file mode 100644 index 0000000..6ed5965 --- /dev/null +++ b/backend/app/infrastructure/session/__init__.py @@ -0,0 +1,5 @@ +"""Initialize the app.infrastructure.session package.""" +# Keep package boundaries explicit so backend imports stay predictable. + + +__all__ = [] diff --git a/backend/app/infrastructure/session/in_memory_conversation_store.py b/backend/app/infrastructure/session/in_memory_conversation_store.py new file mode 100644 index 0000000..0852abc --- /dev/null +++ b/backend/app/infrastructure/session/in_memory_conversation_store.py @@ -0,0 +1,95 @@ +"""Implement infrastructure support for in memory conversation store.""" + +from __future__ import annotations + +import time +import uuid + +from app.domain.conversation import ConversationMessage, ConversationSession, ConversationStore +# Keep adapter behavior explicit so integration details remain easy to audit. + + + +class InMemoryConversationStore(ConversationStore): + """Provide the In Memory Conversation Store store implementation.""" + def __init__(self, *, max_sessions: int = 100, timeout_minutes: int = 30) -> None: + """Initialize the In Memory Conversation Store instance.""" + self.max_sessions = max_sessions + self.timeout_seconds = timeout_minutes * 60 + self.sessions: dict[str, ConversationSession] = {} + + def _now(self) -> int: + """Handle now for this module for the In Memory Conversation Store instance.""" + return int(time.time()) + + def _cleanup_expired(self) -> None: + """Handle cleanup expired for this module for the In Memory Conversation Store instance.""" + now = self._now() + expired = [ + session_id + for session_id, session in self.sessions.items() + if (now - session.updated_at) > self.timeout_seconds + ] + for session_id in expired: + self.sessions.pop(session_id, None) + + def create_session(self, metadata: dict | None = None) -> ConversationSession: + """Create session for the In Memory Conversation Store instance.""" + self._cleanup_expired() + if len(self.sessions) >= self.max_sessions: + oldest = min(self.sessions.values(), key=lambda item: item.updated_at) + self.sessions.pop(oldest.session_id, None) + session_id = str(uuid.uuid4())[:8] + session = ConversationSession( + session_id=session_id, + created_at=self._now(), + updated_at=self._now(), + metadata=metadata or {}, + ) + self.sessions[session_id] = session + return session + + def get_session(self, session_id: str) -> ConversationSession | None: + """Return session for the In Memory Conversation Store instance.""" + self._cleanup_expired() + return self.sessions.get(session_id) + + def save_message( + self, + session_id: str, + *, + role: str, + content: str, + sources: list[dict] | None = None, + ) -> ConversationSession | None: + """Save message for the In Memory Conversation Store instance.""" + session = self.get_session(session_id) + if not session: + return None + session.messages.append( + ConversationMessage( + role=role, + content=content, + timestamp=self._now(), + sources=sources or [], + ) + ) + session.updated_at = self._now() + return session + + def delete_session(self, session_id: str) -> bool: + """Delete session for the In Memory Conversation Store instance.""" + return self.sessions.pop(session_id, None) is not None + + def list_sessions(self) -> list[dict]: + """List sessions for the In Memory Conversation Store instance.""" + self._cleanup_expired() + return [ + { + "session_id": session.session_id, + "message_count": len(session.messages), + "created_at": session.created_at, + "updated_at": session.updated_at, + } + for session in self.sessions.values() + ] diff --git a/backend/app/infrastructure/storage/__init__.py b/backend/app/infrastructure/storage/__init__.py new file mode 100644 index 0000000..320d24b --- /dev/null +++ b/backend/app/infrastructure/storage/__init__.py @@ -0,0 +1,5 @@ +"""Initialize the app.infrastructure.storage package.""" +# Keep package boundaries explicit so backend imports stay predictable. + + +__all__ = [] diff --git a/backend/app/infrastructure/storage/json_document_repository.py b/backend/app/infrastructure/storage/json_document_repository.py new file mode 100644 index 0000000..3fc910f --- /dev/null +++ b/backend/app/infrastructure/storage/json_document_repository.py @@ -0,0 +1,109 @@ +"""Implement infrastructure support for json document repository.""" + +from __future__ import annotations + +import json +from datetime import UTC, datetime +from pathlib import Path + +from app.domain.documents import Document, DocumentRepository, DocumentStatus +# Keep adapter behavior explicit so integration details remain easy to audit. + + + +class JsonDocumentRepository(DocumentRepository): + """Provide the Json Document Repository repository implementation.""" + def __init__(self, file_path: str) -> None: + """Initialize the Json Document Repository instance.""" + self.file_path = Path(file_path) + self.file_path.parent.mkdir(parents=True, exist_ok=True) + if not self.file_path.exists(): + self.file_path.write_text("{}", encoding="utf-8") + + def _load(self) -> dict[str, dict]: + """Handle load for this module for the Json Document Repository instance.""" + return json.loads(self.file_path.read_text(encoding="utf-8") or "{}") + + def _save(self, payload: dict[str, dict]) -> None: + """Handle save for this module for the Json Document Repository instance.""" + self.file_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8") + + def _serialize(self, document: Document) -> dict: + """Handle serialize for this module for the Json Document Repository instance.""" + payload = document.__dict__.copy() + payload["status"] = document.status.value + payload["created_at"] = document.created_at.isoformat() + payload["updated_at"] = document.updated_at.isoformat() + return payload + + def _deserialize(self, payload: dict) -> Document: + """Handle deserialize for this module for the Json Document Repository instance.""" + return Document( + **{ + **payload, + "status": DocumentStatus(payload["status"]), + "created_at": datetime.fromisoformat(payload["created_at"]), + "updated_at": datetime.fromisoformat(payload["updated_at"]), + } + ) + + def create(self, document: Document) -> Document: + """Handle create for the Json Document Repository instance.""" + payload = self._load() + payload[document.doc_id] = self._serialize(document) + self._save(payload) + return document + + def update(self, document: Document) -> Document: + """Handle update for the Json Document Repository instance.""" + document.updated_at = datetime.now(UTC) + payload = self._load() + payload[document.doc_id] = self._serialize(document) + self._save(payload) + return document + + def get(self, doc_id: str) -> Document | None: + """Handle get for the Json Document Repository instance.""" + payload = self._load() + item = payload.get(doc_id) + return self._deserialize(item) if item else None + + def list(self, limit: int | None = None) -> list[Document]: + """Handle list for the Json Document Repository instance.""" + payload = self._load() + documents = [self._deserialize(item) for item in payload.values()] + documents.sort(key=lambda item: item.updated_at, reverse=True) + return documents[:limit] if limit is not None else documents + + def update_status( + self, + doc_id: str, + status: DocumentStatus, + *, + error_message: str = "", + chunk_count: int | None = None, + summary: str | None = None, + summary_latency_ms: int | None = None, + parser_name: str | None = None, + index_name: str | None = None, + metadata: dict | None = None, + ) -> Document | None: + """Update status for the Json Document Repository instance.""" + document = self.get(doc_id) + if not document: + return None + document.status = status + document.error_message = error_message + if chunk_count is not None: + document.chunk_count = chunk_count + if summary is not None: + document.summary = summary + if summary_latency_ms is not None: + document.summary_latency_ms = summary_latency_ms + if parser_name is not None: + document.parser_name = parser_name + if index_name is not None: + document.index_name = index_name + if metadata: + document.metadata.update(metadata) + return self.update(document) diff --git a/backend/app/infrastructure/storage/minio_binary_store.py b/backend/app/infrastructure/storage/minio_binary_store.py new file mode 100644 index 0000000..034b46f --- /dev/null +++ b/backend/app/infrastructure/storage/minio_binary_store.py @@ -0,0 +1,47 @@ +"""Implement infrastructure support for minio binary store.""" + +from __future__ import annotations + +from app.domain.documents import DocumentBinaryStore +from app.services.storage.minio_client import MinIOClient +# Keep adapter behavior explicit so integration details remain easy to audit. + + + +class MinioDocumentBinaryStore(DocumentBinaryStore): + """Provide the Minio Document Binary Store store implementation.""" + def __init__(self) -> None: + """Initialize the Minio Document Binary Store instance.""" + self.client = MinIOClient() + self.client.connect() + self.client.ensure_bucket() + + def save( + self, + *, + object_name: str, + data: bytes, + content_type: str, + metadata: dict[str, str] | None = None, + ) -> None: + """Handle save for the Minio Document Binary Store instance.""" + success = self.client.upload_bytes( + data=data, + object_name=object_name, + content_type=content_type, + metadata=metadata, + ) + if not success: + raise RuntimeError("MinIO 保存失败") + + def read(self, object_name: str) -> bytes: + """Handle read for the Minio Document Binary Store instance.""" + data = self.client.get_object_data(object_name) + if data is None: + raise FileNotFoundError(f"对象不存在: {object_name}") + return data + + def delete(self, object_name: str) -> None: + """Handle delete for the Minio Document Binary Store instance.""" + if not self.client.delete_object(object_name): + raise FileNotFoundError(f"对象删除失败: {object_name}") diff --git a/backend/app/infrastructure/vectorstore/__init__.py b/backend/app/infrastructure/vectorstore/__init__.py new file mode 100644 index 0000000..0002344 --- /dev/null +++ b/backend/app/infrastructure/vectorstore/__init__.py @@ -0,0 +1,5 @@ +"""Initialize the app.infrastructure.vectorstore package.""" +# Keep package boundaries explicit so backend imports stay predictable. + + +__all__ = [] diff --git a/backend/app/infrastructure/vectorstore/dense_retriever.py b/backend/app/infrastructure/vectorstore/dense_retriever.py new file mode 100644 index 0000000..e86f022 --- /dev/null +++ b/backend/app/infrastructure/vectorstore/dense_retriever.py @@ -0,0 +1,24 @@ +"""Implement infrastructure support for dense retriever.""" + +from __future__ import annotations + +from app.domain.retrieval import EmbeddingProvider, RetrievalQuery, Retriever, RetrievedChunk, VectorIndex +# Keep adapter behavior explicit so integration details remain easy to audit. + + + +class DenseRetriever(Retriever): + """Provide the Dense Retriever retriever.""" + def __init__(self, *, embedding_provider: EmbeddingProvider, vector_index: VectorIndex) -> None: + """Initialize the Dense Retriever instance.""" + self.embedding_provider = embedding_provider + self.vector_index = vector_index + + def retrieve(self, query: RetrievalQuery) -> list[RetrievedChunk]: + """Handle retrieve for the Dense Retriever instance.""" + query_vector = self.embedding_provider.embed_query(query.query) + return self.vector_index.search(query_vector, query.top_k, query.filters) + + def search(self, query: str, top_k: int, filters: str | None = None) -> list[RetrievedChunk]: + """Handle search for the Dense Retriever instance.""" + return self.retrieve(RetrievalQuery(query=query, top_k=top_k, filters=filters)) diff --git a/backend/app/infrastructure/vectorstore/milvus_vector_index.py b/backend/app/infrastructure/vectorstore/milvus_vector_index.py new file mode 100644 index 0000000..6d7ce55 --- /dev/null +++ b/backend/app/infrastructure/vectorstore/milvus_vector_index.py @@ -0,0 +1,154 @@ +"""Implement infrastructure support for milvus vector index.""" + +from __future__ import annotations + +import json +import time + +from pymilvus import Collection, CollectionSchema, DataType, FieldSchema, connections, utility + +from app.config.settings import settings +from app.domain.documents import Chunk +from app.domain.retrieval import RetrievedChunk, VectorIndex +# Keep adapter behavior explicit so integration details remain easy to audit. + + + +class MilvusVectorIndex(VectorIndex): + """Provide the Milvus Vector Index index implementation.""" + def __init__(self) -> None: + """Initialize the Milvus Vector Index instance.""" + self.collection_name = settings.milvus_collection + self.db_name = settings.milvus_db_name + connections.connect( + alias="default", + host=settings.milvus_host, + port=settings.milvus_port, + db_name=self.db_name, + ) + self.collection = self._ensure_collection() + + def _ensure_collection(self) -> Collection: + """Handle ensure collection for this module for the Milvus Vector Index instance.""" + if utility.has_collection(self.collection_name): + collection = Collection(self.collection_name) + collection.load() + return collection + schema = CollectionSchema( + fields=[ + FieldSchema(name="id", dtype=DataType.VARCHAR, max_length=128, is_primary=True, auto_id=False), + FieldSchema(name="doc_id", dtype=DataType.VARCHAR, max_length=64), + FieldSchema(name="doc_name", dtype=DataType.VARCHAR, max_length=256), + FieldSchema(name="content", dtype=DataType.VARCHAR, max_length=65535), + FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=settings.embedding_dim), + FieldSchema(name="section_title", dtype=DataType.VARCHAR, max_length=512), + FieldSchema(name="section_path", dtype=DataType.VARCHAR, max_length=4096), + FieldSchema(name="page_number", dtype=DataType.INT64), + FieldSchema(name="regulation_type", dtype=DataType.VARCHAR, max_length=128), + FieldSchema(name="version", dtype=DataType.VARCHAR, max_length=64), + FieldSchema(name="semantic_id", dtype=DataType.VARCHAR, max_length=128), + FieldSchema(name="block_type", dtype=DataType.VARCHAR, max_length=64), + FieldSchema(name="metadata_json", dtype=DataType.VARCHAR, max_length=65535), + FieldSchema(name="created_at", dtype=DataType.INT64), + ], + description="Dense-only regulations index", + enable_dynamic_field=False, + ) + collection = Collection(name=self.collection_name, schema=schema) + collection.create_index( + field_name="embedding", + index_params={ + "metric_type": "COSINE", + "index_type": settings.milvus_index_type, + "params": {"nlist": settings.milvus_nlist}, + }, + ) + collection.load() + return collection + + def upsert(self, chunks: list[Chunk], vectors: list[list[float]]) -> int: + """Handle upsert for the Milvus Vector Index instance.""" + if len(chunks) != len(vectors): + raise ValueError("chunks 与 vectors 数量不一致") + data = [] + now = int(time.time()) + for chunk, vector in zip(chunks, vectors): + data.append( + { + "id": chunk.chunk_id, + "doc_id": chunk.doc_id, + "doc_name": chunk.doc_name, + "content": chunk.content[:65535], + "embedding": vector, + "section_title": chunk.section_title[:512], + "section_path": json.dumps(chunk.section_path, ensure_ascii=False)[:4096], + "page_number": chunk.page_number, + "regulation_type": chunk.regulation_type[:128], + "version": chunk.version[:64], + "semantic_id": chunk.semantic_id[:128], + "block_type": chunk.block_type[:64], + "metadata_json": json.dumps(chunk.metadata, ensure_ascii=False)[:65535], + "created_at": now, + } + ) + self.collection.insert(data) + self.collection.flush() + return len(data) + + def delete_by_document(self, doc_id: str) -> int: + """Delete by document for the Milvus Vector Index instance.""" + result = self.collection.delete(f'doc_id == "{doc_id}"') + return len(result.primary_keys) + + def search(self, query_vector: list[float], top_k: int, filters: str | None = None) -> list[RetrievedChunk]: + """Handle search for the Milvus Vector Index instance.""" + results = self.collection.search( + data=[query_vector], + anns_field="embedding", + param={"metric_type": "COSINE", "params": {"nprobe": settings.milvus_nprobe}}, + limit=top_k, + filter=filters, + output_fields=[ + "doc_id", + "doc_name", + "content", + "section_title", + "page_number", + "regulation_type", + "version", + "semantic_id", + "block_type", + "metadata_json", + ], + ) + payload: list[RetrievedChunk] = [] + for hits in results: + for hit in hits: + metadata = {} + raw_metadata = hit.entity.get("metadata_json", "") + if raw_metadata: + try: + metadata = json.loads(raw_metadata) + except json.JSONDecodeError: + metadata = {"raw_metadata": raw_metadata} + payload.append( + RetrievedChunk( + chunk_id=str(hit.id), + doc_id=hit.entity.get("doc_id", ""), + doc_name=hit.entity.get("doc_name", ""), + content=hit.entity.get("content", ""), + score=float(hit.score), + section_title=hit.entity.get("section_title", ""), + page_number=int(hit.entity.get("page_number", 0) or 0), + metadata=metadata, + ) + ) + return payload + + def health(self) -> dict: + """Handle health for the Milvus Vector Index instance.""" + return { + "connected": True, + "collection_name": self.collection_name, + "num_entities": self.collection.num_entities if self.collection else 0, + } diff --git a/backend/app/main.py b/backend/app/main.py index 7616447..54b1b76 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -1,5 +1,7 @@ """Backend application entrypoint.""" from app.api.main import app +# Keep module behavior explicit so the backend flow stays easy to audit. + __all__ = ["app"] diff --git a/backend/app/schemas/__init__.py b/backend/app/schemas/__init__.py index 1ef3d7a..0c01ed5 100644 --- a/backend/app/schemas/__init__.py +++ b/backend/app/schemas/__init__.py @@ -1,3 +1,5 @@ +"""Initialize the app.schemas package.""" + from .doc import ( DocumentUploadResponse, DocumentInfo, @@ -24,6 +26,8 @@ from .compliance import ( ComplianceChatRequest, AnalyzeResponse, ) +# Keep package boundaries explicit so backend imports stay predictable. + __all__ = [ "DocumentUploadResponse", diff --git a/backend/app/schemas/compliance.py b/backend/app/schemas/compliance.py index 220f5c6..9039027 100644 --- a/backend/app/schemas/compliance.py +++ b/backend/app/schemas/compliance.py @@ -1,21 +1,28 @@ +"""Define schema models for compliance.""" + from pydantic import BaseModel from typing import Optional from enum import Enum +# Group related schema definitions so validation rules stay consistent. + class RiskLevel(str, Enum): + """Define the Risk Level enumeration.""" high = "high" medium = "medium" low = "low" class ComplianceStatus(str, Enum): + """Define the Compliance Status enumeration.""" pass_status = "pass" warning = "warning" fail = "fail" class Regulation(BaseModel): + """Define the Regulation API model.""" id: int name: str clause: Optional[str] = None @@ -26,6 +33,7 @@ class Regulation(BaseModel): class ComplianceSegment(BaseModel): + """Define the Compliance Segment API model.""" id: int index: int intent: str @@ -37,6 +45,7 @@ class ComplianceSegment(BaseModel): class RiskDashboard(BaseModel): + """Define the Risk Dashboard API model.""" score: float high_risk_count: int medium_risk_count: int @@ -47,6 +56,7 @@ class RiskDashboard(BaseModel): class PriorityAction(BaseModel): + """Define the Priority Action API model.""" regulation: str issue: str suggestion: str @@ -54,6 +64,7 @@ class PriorityAction(BaseModel): class ComplianceResult(BaseModel): + """Define the Compliance Result API model.""" task_id: str dashboard: RiskDashboard segments: list[ComplianceSegment] @@ -61,9 +72,11 @@ class ComplianceResult(BaseModel): class ComplianceChatRequest(BaseModel): + """Define the Compliance Chat Request API model.""" query: str class AnalyzeResponse(BaseModel): + """Define the Analyze Response API model.""" task_id: str status: str = "processing" \ No newline at end of file diff --git a/backend/app/schemas/doc.py b/backend/app/schemas/doc.py index 5ff7cc1..3fab5ac 100644 --- a/backend/app/schemas/doc.py +++ b/backend/app/schemas/doc.py @@ -1,9 +1,14 @@ +"""Define schema models for doc.""" + from pydantic import BaseModel from typing import Optional from datetime import datetime +# Group related schema definitions so validation rules stay consistent. + class DocumentUploadResponse(BaseModel): + """Define the Document Upload Response API model.""" doc_id: str filename: str size: int @@ -11,6 +16,7 @@ class DocumentUploadResponse(BaseModel): class DocumentInfo(BaseModel): + """Define the Document Info API model.""" id: str name: str chunks: int @@ -19,10 +25,12 @@ class DocumentInfo(BaseModel): class DocumentListResponse(BaseModel): + """Define the Document List Response API model.""" docs: list[DocumentInfo] class ChunkInfo(BaseModel): + """Define the Chunk Info API model.""" chunk_id: str doc_name: str clause_id: Optional[str] = None @@ -33,12 +41,14 @@ class ChunkInfo(BaseModel): class ParseResponse(BaseModel): + """Define the Parse Response API model.""" doc_id: str chunks: int status: str = "parsed" class EmbedResponse(BaseModel): + """Define the Embed Response API model.""" doc_id: str vectors: int status: str = "embedded" \ No newline at end of file diff --git a/backend/app/schemas/rag.py b/backend/app/schemas/rag.py index 9a92e5c..5d2e24f 100644 --- a/backend/app/schemas/rag.py +++ b/backend/app/schemas/rag.py @@ -1,13 +1,19 @@ +"""Define schema models for rag.""" + from pydantic import BaseModel from typing import Optional +# Group related schema definitions so validation rules stay consistent. + class RagChatRequest(BaseModel): + """Define the Rag Chat Request API model.""" query: str top_k: int = 5 class RetrievedDoc(BaseModel): + """Define the Retrieved Doc API model.""" id: str doc_name: str clause_id: Optional[str] = None @@ -17,15 +23,18 @@ class RetrievedDoc(BaseModel): class SourceInfo(BaseModel): + """Define the Source Info API model.""" name: str clause: Optional[str] = None class QuickQuestion(BaseModel): + """Define the Quick Question API model.""" id: str question: str category: str class QuickQuestionsResponse(BaseModel): + """Define the Quick Questions Response API model.""" questions: list[QuickQuestion] \ No newline at end of file diff --git a/backend/app/services/__init__.py b/backend/app/services/__init__.py index 76e0e3a..97f5cdb 100644 --- a/backend/app/services/__init__.py +++ b/backend/app/services/__init__.py @@ -1,3 +1,5 @@ """Backend service package.""" +# Keep package boundaries explicit so backend imports stay predictable. + __all__: list[str] = [] diff --git a/backend/app/services/agent/__init__.py b/backend/app/services/agent/__init__.py index ce69b6a..c38d465 100644 --- a/backend/app/services/agent/__init__.py +++ b/backend/app/services/agent/__init__.py @@ -1,6 +1,8 @@ -"""Agent服务模块""" +"""Initialize the app.services.agent package.""" from .qa_agent import QAAgent, ask_compliance_question from .session_manager import SessionManager, ChatSession +# Keep package boundaries explicit so backend imports stay predictable. + __all__ = ["QAAgent", "ask_compliance_question", "SessionManager", "ChatSession"] diff --git a/backend/app/services/agent/qa_agent.py b/backend/app/services/agent/qa_agent.py index 5225c72..282637d 100644 --- a/backend/app/services/agent/qa_agent.py +++ b/backend/app/services/agent/qa_agent.py @@ -1,21 +1,19 @@ -"""RAG问答Agent - 合规智能问答核心实现""" +"""Provide service-layer logic for qa agent.""" + +from __future__ import annotations -import time -from typing import List, Dict, Optional, Any, Generator from dataclasses import dataclass, field -from loguru import logger +from typing import Dict, Generator, List, Optional -from app.services.llm import get_llm_client, BaseLLMClient, LLMResponse -from app.services.llm.llm_factory import LLMFactory -from app.services.rag.retriever import Retriever, RetrievedDocument -from app.services.rag.context_builder import ContextBuilder, RAGContext -from app.services.rag.prompt_templates import get_prompt_template, PromptTemplate from app.config.settings import settings +from app.shared.bootstrap import get_agent_conversation_service +# Keep service responsibilities explicit so downstream behavior stays predictable. + @dataclass class AgentResponse: - """Agent响应结果""" + """Represent the Agent Response type.""" answer: str sources: List[Dict] = field(default_factory=list) model: str = "" @@ -27,385 +25,73 @@ class AgentResponse: @property def is_success(self) -> bool: + """Return whether success for the Agent Response instance.""" return self.error is None @dataclass class AgentConfig: - """Agent配置""" - llm_provider: str = "deepseek" - llm_model: str = "deepseek-v4-flash" - top_k: int = 5 - min_score: float = 0.3 - max_context_tokens: int = 2000 - temperature: float = 0.7 + """Define configuration for agent config.""" + llm_provider: str = settings.llm_provider + llm_model: str = settings.llm_model + top_k: int = settings.rag_top_k + min_score: float = 0.0 + max_context_tokens: int = settings.rag_max_context_tokens + temperature: float = settings.llm_temperature prompt_template: str = "compliance_qa" include_metadata: bool = True class QAAgent: - """ - 合规问答Agent - - 核心流程: - 1. 接收用户问题 - 2. Milvus混合检索相关法规条款 - 3. 构建RAG上下文 - 4. 调用LLM生成回答 - 5. 返回答案和引用来源 - - 使用示例: - agent = QAAgent() - response = agent.ask("机动车安全技术检验有哪些要求?") - print(response.answer) - for source in response.sources: - print(f"引用: {source['doc_name']} - {source['clause_number']}") - """ - + """Represent the Q A Agent type.""" def __init__(self, config: Optional[AgentConfig] = None): - """ - 初始化问答Agent - - Args: - config: Agent配置(可选,使用默认配置) - """ - self.config = config or AgentConfig( - llm_provider=settings.llm_provider, - llm_model=settings.llm_model, - top_k=settings.rag_top_k, - max_context_tokens=settings.rag_max_context_tokens - ) - - # 初始化组件(延迟加载) - self.llm: Optional[BaseLLMClient] = None - self.retriever: Optional[Retriever] = None - self.context_builder: Optional[ContextBuilder] = None - - logger.info(f"问答Agent初始化: provider={self.config.llm_provider}, model={self.config.llm_model}") - - def _init_llm(self): - """延迟初始化LLM客户端(优先使用全局缓存)""" - if self.llm is None: - # 尝试先获取全局缓存的客户端 - cached = LLMFactory.get_global_client(self.config.llm_provider, self.config.llm_model) - if cached: - self.llm = cached - logger.debug(f"使用全局缓存的LLM客户端: {self.config.llm_provider} - {self.config.llm_model}") - else: - logger.info("创建新的LLM客户端...") - self.llm = get_llm_client( - provider=self.config.llm_provider, - model=self.config.llm_model, - temperature=self.config.temperature - ) - - def _init_retriever(self): - """延迟初始化检索器""" - if self.retriever is None: - logger.info("初始化检索器...") - self.retriever = Retriever( - top_k=self.config.top_k, - min_score=self.config.min_score - ) - - def _init_context_builder(self): - """延迟初始化上下文构建器""" - if self.context_builder is None: - logger.info("初始化上下文构建器...") - self.context_builder = ContextBuilder( - max_context_tokens=self.config.max_context_tokens, - include_metadata=self.config.include_metadata - ) + """Initialize the Q A Agent instance.""" + self.config = config or AgentConfig() def ask( self, query: str, filters: Optional[str] = None, - prompt_template: Optional[str] = None + prompt_template: Optional[str] = None, ) -> AgentResponse: - """ - 回答用户问题 - - Args: - query: 用户问题 - filters: 检索过滤条件(如 "regulation_type=='车辆安全'") - prompt_template: Prompt模板名称(可选,覆盖默认配置) - - Returns: - AgentResponse: 包含答案和引用来源的响应对象 - """ - start_time = time.time() - logger.info(f"收到问题: {query}") - - try: - # Step 1: 检索相关法规 - self._init_retriever() - documents = self.retriever.retrieve(query, filters) - retrieved_count = len(documents) - - if retrieved_count == 0: - return AgentResponse( - answer="抱歉,未找到与您的问题相关的法规条款。请尝试用不同的关键词重新提问,或提供更具体的法规名称。", - retrieved_count=0, - error="no_retrieved_documents" - ) - - # Step 2: 构建RAG上下文 - self._init_context_builder() - template_name = prompt_template or self.config.prompt_template - template = get_prompt_template(template_name) - - context = self.context_builder.build( - query=query, - documents=documents, - system_prompt=template.system_prompt - ) - - # Step 3: 构建LLM输入消息 - messages = self._build_messages(template, context) - - # Step 4: 调用LLM生成回答 - self._init_llm() - llm_response = self.llm.chat( - messages=messages, - temperature=self.config.temperature - ) - - if not llm_response.is_success: - return AgentResponse( - answer="", - retrieved_count=retrieved_count, - error=llm_response.error - ) - - latency_ms = int((time.time() - start_time) * 1000) - - # Step 5: 返回结果 - logger.success(f"问答完成: {latency_ms}ms, {retrieved_count}条引用") - - return AgentResponse( - answer=llm_response.content, - sources=context.sources, - model=llm_response.model, - latency_ms=latency_ms, - retrieved_count=retrieved_count, - context_tokens=context.total_tokens, - truncated=context.truncated - ) - - except Exception as e: - logger.error(f"问答失败: {e}") - return AgentResponse( - answer="", - error=str(e) - ) - - def ask_with_context( - self, - query: str, - documents: List[RetrievedDocument], - prompt_template: Optional[str] = None - ) -> AgentResponse: - """ - 使用提供的文档回答问题(不执行检索) - - Args: - query: 用户问题 - documents: 已检索的文档列表 - prompt_template: Prompt模板名称 - - Returns: - AgentResponse: 响应结果 - """ - start_time = time.time() - - try: - self._init_context_builder() - self._init_llm() - - template_name = prompt_template or self.config.prompt_template - template = get_prompt_template(template_name) - - context = self.context_builder.build( - query=query, - documents=documents, - system_prompt=template.system_prompt - ) - - messages = self._build_messages(template, context) - - llm_response = self.llm.chat(messages) - - latency_ms = int((time.time() - start_time) * 1000) - - return AgentResponse( - answer=llm_response.content, - sources=context.sources, - model=llm_response.model, - latency_ms=latency_ms, - retrieved_count=len(documents), - context_tokens=context.total_tokens, - truncated=context.truncated - ) - - except Exception as e: - logger.error(f"问答失败: {e}") - return AgentResponse(answer="", error=str(e)) - - def _build_messages( - self, - template: PromptTemplate, - context: RAGContext - ) -> List[Dict[str, str]]: - """构建LLM输入消息""" - user_content = template.user_template.format( - context=context.context_text, - query=context.user_query + """Handle ask for the Q A Agent instance.""" + _, result = get_agent_conversation_service().ask( + query=query, + filters=filters, + provider=self.config.llm_provider, + model=self.config.llm_model, + top_k=self.config.top_k, + prompt_template=prompt_template or self.config.prompt_template, + ) + return AgentResponse( + answer=result.answer, + sources=[source.__dict__ for source in result.sources], + model=result.model, + latency_ms=result.latency_ms, + retrieved_count=result.retrieved_count, + context_tokens=result.context_tokens, + truncated=result.truncated, + error=result.error, ) - return [ - {"role": "system", "content": template.system_prompt}, - {"role": "user", "content": user_content} - ] - - def ask_stream( - self, - query: str, - filters: Optional[str] = None, - prompt_template: Optional[str] = None - ) -> Generator[Dict[str, Any], None, None]: - """ - 流式回答用户问题(SSE模式) - - 返回事件类型: - - {"event": "status", "data": "正在检索..."} - 状态更新 - - {"event": "sources", "data": [...]} - 引用来源 - - {"event": "content", "data": "文本片段"} - 回答内容 - - {"event": "done", "data": {"latency_ms": ..., "model": ...}} - 完成 - - Args: - query: 用户问题 - filters: 检索过滤条件 - prompt_template: Prompt模板名称 - - Yields: - Dict: SSE事件数据 - """ - start_time = time.time() - logger.info(f"收到流式问题: {query}") - - try: - # Step 1: 检索相关法规 - yield {"event": "status", "data": "正在检索相关法规..."} - self._init_retriever() - documents = self.retriever.retrieve(query, filters) - retrieved_count = len(documents) - - if retrieved_count == 0: - yield {"event": "status", "data": "未找到相关法规"} - yield {"event": "content", "data": "抱歉,未找到与您的问题相关的法规条款。请尝试用不同的关键词重新提问。"} - yield {"event": "done", "data": {"latency_ms": 0, "retrieved_count": 0}} - return - - # Step 2: 发送检索结果 - yield {"event": "status", "data": f"找到{retrieved_count}条相关法规,正在生成回答..."} - sources = [ - { - "doc_name": doc.doc_name, - "doc_id": doc.doc_id, - "clause_number": doc.clause_number, - "score": doc.score - } - for doc in documents[:5] # 只返回前5条引用 - ] - yield {"event": "sources", "data": sources} - - # Step 3: 构建RAG上下文 - self._init_context_builder() - template_name = prompt_template or self.config.prompt_template - template = get_prompt_template(template_name) - context = self.context_builder.build( - query=query, - documents=documents, - system_prompt=template.system_prompt - ) - - # Step 4: 构建LLM输入消息 - messages = self._build_messages(template, context) - - # Step 5: 流式调用LLM生成回答 - self._init_llm() - full_answer = "" - - # 检查LLM是否支持流式输出 - if hasattr(self.llm, 'stream_chat'): - yield {"event": "status", "data": "思考中..."} - for chunk in self.llm.stream_chat( - messages=messages, - temperature=self.config.temperature - ): - full_answer += chunk - yield {"event": "content", "data": chunk} - else: - # 如果不支持流式,回退到普通调用 - yield {"event": "status", "data": "生成回答中..."} - llm_response = self.llm.chat( - messages=messages, - temperature=self.config.temperature - ) - if llm_response.is_success: - full_answer = llm_response.content - yield {"event": "content", "data": full_answer} - - # Step 6: 发送完成事件 - latency_ms = int((time.time() - start_time) * 1000) - logger.success(f"流式问答完成: {latency_ms}ms, {retrieved_count}条引用") - - yield { - "event": "done", - "data": { - "latency_ms": latency_ms, - "model": self.config.llm_model, - "retrieved_count": retrieved_count, - "context_tokens": context.total_tokens - } - } - - except Exception as e: - logger.error(f"流式问答失败: {e}") - yield {"event": "error", "data": str(e)} + def ask_stream(self, query: str, filters: Optional[str] = None) -> Generator[dict, None, None]: + """Handle ask stream for the Q A Agent instance.""" + _, stream = get_agent_conversation_service().stream_chat( + query=query, + filters=filters, + provider=self.config.llm_provider, + model=self.config.llm_model, + top_k=self.config.top_k, + prompt_template=self.config.prompt_template, + ) + for event in stream: + yield event def close(self): - """关闭Agent资源(不关闭LLM客户端,因为它全局缓存)""" - if self.retriever: - self.retriever.close() - logger.info("问答Agent已关闭") + """Release the resources held by this component.""" + return None -def ask_compliance_question( - query: str, - provider: str = "deepseek", - model: str = "deepseek-v4-flash", - top_k: int = 10 -) -> AgentResponse: - """ - 便捷函数:问答合规问题 - - Args: - query: 用户问题 - provider: LLM提供商 - model: LLM模型 - top_k: 检索数量 - - Returns: - AgentResponse: 响应结果 - """ - config = AgentConfig( - llm_provider=provider, - llm_model=model, - top_k=top_k - ) - agent = QAAgent(config) - response = agent.ask(query) - agent.close() - return response +def ask_compliance_question(query: str, top_k: int = 5) -> AgentResponse: + """Handle ask compliance question.""" + return QAAgent(AgentConfig(top_k=top_k)).ask(query) diff --git a/backend/app/services/agent/session_manager.py b/backend/app/services/agent/session_manager.py index a57c807..10a9ecf 100644 --- a/backend/app/services/agent/session_manager.py +++ b/backend/app/services/agent/session_manager.py @@ -1,4 +1,4 @@ -"""多轮对话会话管理""" +"""Provide service-layer logic for session manager.""" import time import uuid @@ -9,7 +9,7 @@ from loguru import logger @dataclass class ChatMessage: - """对话消息""" + """Represent the Chat Message type.""" role: str # "user" / "assistant" / "system" content: str timestamp: int @@ -19,7 +19,7 @@ class ChatMessage: @dataclass class ChatSession: - """对话会话""" + """Represent the Chat Session type.""" session_id: str messages: List[ChatMessage] = field(default_factory=list) created_at: int = field(default_factory=lambda: int(time.time())) @@ -27,7 +27,7 @@ class ChatSession: metadata: Dict = field(default_factory=dict) def add_user_message(self, content: str) -> ChatMessage: - """添加用户消息""" + """Handle add user message for the Chat Session instance.""" message = ChatMessage( role="user", content=content, @@ -42,7 +42,7 @@ class ChatSession: content: str, sources: List[Dict] = None ) -> ChatMessage: - """添加助手消息""" + """Handle add assistant message for the Chat Session instance.""" message = ChatMessage( role="assistant", content=content, @@ -54,9 +54,9 @@ class ChatSession: return message def get_history(self, max_turns: int = 5) -> List[Dict[str, str]]: - """获取历史对话(用于LLM上下文)""" + """Return history for the Chat Session instance.""" history = [] - # 获取最近N轮对话(每轮包含user + assistant) + # Keep service responsibilities explicit so downstream behavior stays predictable. recent_messages = self.messages[-(max_turns * 2):] for msg in recent_messages: @@ -68,81 +68,47 @@ class ChatSession: return history def clear_history(self): - """清空对话历史""" + """Handle clear history for the Chat Session instance.""" self.messages = [] self.updated_at = int(time.time()) logger.info(f"会话历史已清空: {self.session_id}") @property def message_count(self) -> int: - """消息数量""" + """Handle message count for the Chat Session instance.""" return len(self.messages) @property def is_empty(self) -> bool: - """是否为空会话""" + """Return whether empty for the Chat Session instance.""" return len(self.messages) == 0 class SessionManager: - """ - 会话管理器 - - 功能: - - 创建/获取/删除会话 - - 会话超时清理 - - 会话历史记录管理 - - 使用示例: - manager = SessionManager() - - # 创建会话 - session = manager.create_session() - - # 添加消息 - session.add_user_message("什么是机动车安全技术检验?") - session.add_assistant_message("根据GB 7258...", sources=[...]) - - # 获取历史(用于LLM多轮对话) - history = session.get_history(max_turns=3) - """ + """Represent the Session Manager type.""" def __init__( self, max_sessions: int = 100, session_timeout_minutes: int = 30 ): - """ - 初始化会话管理器 - - Args: - max_sessions: 最大会话数量 - session_timeout_minutes: 会话超时时间(分钟) - """ + """Initialize the Session Manager instance.""" self.max_sessions = max_sessions self.session_timeout = session_timeout_minutes * 60 - # 会话存储(内存) + # Keep service responsibilities explicit so downstream behavior stays predictable. self._sessions: Dict[str, ChatSession] = {} logger.info(f"会话管理器初始化: max_sessions={max_sessions}, timeout={session_timeout_minutes}min") def create_session(self, metadata: Dict = None) -> ChatSession: - """ - 创建新会话 - - Args: - metadata: 会话元数据(可选) - - Returns: - ChatSession: 新创建的会话 - """ - # 检查会话数量限制 + """Create session for the Session Manager instance.""" + # Keep service responsibilities explicit so downstream behavior stays predictable. if len(self._sessions) >= self.max_sessions: - # 清理过期会话 + # Keep service responsibilities explicit so downstream behavior stays predictable. self._cleanup_expired_sessions() - # 如果仍然超出限制,删除最老的会话 + # Keep service responsibilities explicit so downstream behavior stays predictable. if len(self._sessions) >= self.max_sessions: oldest_id = min( self._sessions.keys(), @@ -163,19 +129,11 @@ class SessionManager: return session def get_session(self, session_id: str) -> Optional[ChatSession]: - """ - 获取会话 - - Args: - session_id: 会话ID - - Returns: - ChatSession: 会话对象(如不存在返回None) - """ + """Return session for the Session Manager instance.""" session = self._sessions.get(session_id) if session: - # 检查是否过期 + # Keep service responsibilities explicit so downstream behavior stays predictable. if self._is_session_expired(session): self.delete_session(session_id) logger.info(f"会话已过期,已删除: {session_id}") @@ -184,15 +142,7 @@ class SessionManager: return session def delete_session(self, session_id: str) -> bool: - """ - 删除会话 - - Args: - session_id: 会话ID - - Returns: - bool: 是否成功删除 - """ + """Delete session for the Session Manager instance.""" if session_id in self._sessions: del self._sessions[session_id] logger.info(f"删除会话: {session_id}") @@ -200,12 +150,7 @@ class SessionManager: return False def list_sessions(self) -> List[Dict]: - """ - 列出所有会话 - - Returns: - List[Dict]: 会话列表摘要 - """ + """List sessions for the Session Manager instance.""" return [ { "session_id": s.session_id, @@ -217,12 +162,12 @@ class SessionManager: ] def _is_session_expired(self, session: ChatSession) -> bool: - """检查会话是否过期""" + """Handle is session expired for this module for the Session Manager instance.""" current_time = int(time.time()) return (current_time - session.updated_at) > self.session_timeout def _cleanup_expired_sessions(self) -> int: - """清理过期会话""" + """Handle cleanup expired sessions for this module for the Session Manager instance.""" expired_ids = [ sid for sid, session in self._sessions.items() if self._is_session_expired(session) @@ -237,10 +182,10 @@ class SessionManager: return len(expired_ids) def get_session_count(self) -> int: - """获取当前会话数量""" + """Return session count for the Session Manager instance.""" return len(self._sessions) def clear_all_sessions(self): - """清空所有会话""" + """Handle clear all sessions for the Session Manager instance.""" self._sessions.clear() logger.info("所有会话已清空") diff --git a/backend/app/services/document_processor.py b/backend/app/services/document_processor.py index e2be84a..921298e 100644 --- a/backend/app/services/document_processor.py +++ b/backend/app/services/document_processor.py @@ -1,24 +1,19 @@ -"""文档处理主流程 - 解析→摘要→分块→嵌入→入库""" +"""Provide service-layer logic for document processor.""" + +from __future__ import annotations -import os -from typing import List, Dict, Optional from dataclasses import dataclass -from loguru import logger -import uuid +from pathlib import Path +from typing import Optional + +from app.shared.bootstrap import get_document_command_service, get_retrieval_service +# Keep service responsibilities explicit so downstream behavior stays predictable. -from .parser.pdf_parser import PDFParser -from .parser.docx_parser import DocxParser -from .parser.mineru_parser import ParserOrchestrator -from .embedding.text_chunker import RegulationChunker, TextChunk -from .embedding.bge_m3_embedder import BGEM3Embedder, EmbeddingResult -from .storage.milvus_client import MilvusClient -from .llm.document_summarizer import DocumentSummarizer, DocumentSummary -from app.config.settings import settings @dataclass class ProcessingResult: - """文档处理结果""" + """Represent the Processing Result type.""" doc_id: str doc_name: str success: bool @@ -30,87 +25,10 @@ class ProcessingResult: class DocumentProcessor: - """ - 文档处理服务 - 完整处理流程 - - 流程: - 1. 文档解析(PDF/DOCX → Markdown) - 2. 智能分块(章节级+条款级) - 3. LLM摘要生成(可选) - 4. 向量嵌入(BGE-M3 Dense+Sparse) - 5. 存储入库(Milvus向量数据库) - """ - - def __init__( - self, - chunk_size: int = None, - embedding_model: str = None, - use_mineru: bool = True, - generate_summary: bool = False, # 默认不生成摘要,节省约60秒 - llm_provider: str = None, - llm_model: str = None - ): - """ - 初始化文档处理器 - - Args: - chunk_size: 分块大小 - embedding_model: 嵌入模型名称 - use_mineru: 是否优先使用MinerU解析 - generate_summary: 是否生成文档摘要(默认False,可节省约60秒处理时间) - llm_provider: LLM提供商 - llm_model: LLM模型名称 - """ - self.chunk_size = chunk_size or settings.chunk_size - self.embedding_model = embedding_model or settings.embedding_model - self.use_mineru = use_mineru + """Represent the Document Processor type.""" + def __init__(self, *args, generate_summary: bool = False, **kwargs): + """Initialize the Document Processor instance.""" self.generate_summary = generate_summary - self.llm_provider = llm_provider or settings.llm_provider - self.llm_model = llm_model or settings.llm_model - - # 初始化各组件 - logger.info("初始化文档处理组件...") - - # 解析器 - self.parser = ParserOrchestrator() - - # 分块器 - self.chunker = RegulationChunker(chunk_size=self.chunk_size) - - # 嵌入模型(延迟加载) - self.embedder: Optional[BGEM3Embedder] = None - - # Milvus客户端(延迟连接) - self.milvus: Optional[MilvusClient] = None - - # 摘要生成器(延迟加载) - self.summarizer: Optional[DocumentSummarizer] = None - - logger.success("文档处理器初始化完成") - - def _init_embedder(self): - """延迟初始化嵌入模型""" - if self.embedder is None: - logger.info("加载嵌入模型...") - self.embedder = BGEM3Embedder(model_name=self.embedding_model) - - def _init_milvus(self): - """延迟初始化Milvus连接""" - if self.milvus is None: - logger.info("连接Milvus...") - self.milvus = MilvusClient() - self.milvus.connect() - self.milvus.create_collection(recreate=False) - self.milvus.load_collection() - - def _init_summarizer(self): - """延迟初始化摘要生成器""" - if self.summarizer is None: - logger.info("初始化摘要生成器...") - self.summarizer = DocumentSummarizer( - provider=self.llm_provider, - model=self.llm_model - ) def process( self, @@ -118,286 +36,51 @@ class DocumentProcessor: doc_id: Optional[str] = None, doc_name: Optional[str] = None, regulation_type: str = "", - version: str = "" + version: str = "", ) -> ProcessingResult: - """ - 处理单个文档 + """Handle process for the Document Processor instance.""" + path = Path(file_path) + content = path.read_bytes() + result = get_document_command_service().upload_and_process( + doc_id=doc_id, + file_name=path.name, + content=content, + content_type="application/octet-stream", + doc_name=doc_name or path.name, + regulation_type=regulation_type, + version=version, + generate_summary=self.generate_summary, + ) + return ProcessingResult( + doc_id=result.doc_id, + doc_name=result.doc_name, + success=result.status != "failed", + num_chunks=result.num_chunks, + message=result.message, + summary=result.summary, + summary_latency_ms=result.summary_latency_ms, + ) - Args: - file_path: 文档文件路径 - doc_id: 文档ID(可选,默认自动生成) - doc_name: 文档名称(可选,默认从文件名获取) - regulation_type: 法规类型 - version: 文档版本 - - Returns: - ProcessingResult: 处理结果 - """ - # 生成或使用传入的文档ID - if doc_id is None: - doc_id = str(uuid.uuid4())[:8] - - # 获取文档名称 - if doc_name is None: - doc_name = os.path.basename(file_path) - - logger.info(f"开始处理文档: {doc_name} (ID: {doc_id})") - - # 初始化结果变量 - summary = "" - summary_latency_ms = 0 - - try: - # 1. 文档解析 - logger.info("Step 1: 文档解析") - markdown_text = self._parse_document(file_path) - - if not markdown_text: - return ProcessingResult( - doc_id=doc_id, - doc_name=doc_name, - success=False, - message="文档解析失败,内容为空" - ) - - # 2. LLM摘要生成(可选) - if self.generate_summary: - logger.info("Step 2: LLM摘要生成") - self._init_summarizer() - summary_result = self.summarizer.summarize( - doc_name, - markdown_text, - regulation_type - ) - if summary_result.is_success: - summary = summary_result.summary - summary_latency_ms = summary_result.latency_ms - logger.success(f"摘要生成完成: {summary_latency_ms}ms") - else: - logger.warning(f"摘要生成失败: {summary_result.error}") - else: - logger.info("Step 2: 跳过摘要生成(未勾选,节省约60秒)") - - # 3. 智能分块 - logger.info("Step 3: 智能分块") - chunks = self._chunk_document( - markdown_text, - doc_id, - doc_name, - regulation_type, - version - ) - - if not chunks: - return ProcessingResult( - doc_id=doc_id, - doc_name=doc_name, - success=False, - message="分块失败,无有效内容", - markdown_text=markdown_text, - summary=summary - ) - - # 4. 向量嵌入 - logger.info("Step 4: 向量嵌入") - embeddings = self._embed_chunks(chunks) - - if embeddings is None: - return ProcessingResult( - doc_id=doc_id, - doc_name=doc_name, - success=False, - message="向量嵌入失败", - markdown_text=markdown_text, - summary=summary - ) - - # 5. 存储入库 - logger.info("Step 5: 存储入库") - inserted_ids = self._insert_to_milvus(chunks, embeddings) - - logger.success(f"文档处理完成: {doc_name}, 共{len(inserted_ids)}条记录") - - return ProcessingResult( - doc_id=doc_id, - doc_name=doc_name, - success=True, - num_chunks=len(inserted_ids), - message="处理成功", - markdown_text=markdown_text, - summary=summary, - summary_latency_ms=summary_latency_ms - ) - - except Exception as e: - logger.error(f"文档处理失败: {e}") - return ProcessingResult( - doc_id=doc_id, - doc_name=doc_name, - success=False, - message=f"处理失败: {str(e)}" - ) - - def _parse_document(self, file_path: str) -> str: - """解析文档""" - ext = os.path.splitext(file_path)[1].lower() - - try: - if ext == ".pdf": - # PDF文档解析(优先MinerU,回退PyMuPDF) - markdown_text = self.parser.parse_pdf(file_path, prefer_mineru=self.use_mineru) - elif ext in [".docx", ".doc"]: - # Word文档解析 - markdown_text = self.parser.parse_docx(file_path) - else: - logger.warning(f"不支持的文件类型: {ext}") - return "" - - logger.success(f"文档解析完成,内容长度: {len(markdown_text)}字符") - return markdown_text - - except Exception as e: - logger.error(f"文档解析失败: {e}") - return "" - - def _chunk_document( - self, - markdown_text: str, - doc_id: str, - doc_name: str, - regulation_type: str, - version: str - ) -> List[TextChunk]: - """分块文档""" - try: - chunks = self.chunker.chunk_document( - markdown_text, - doc_id=doc_id, - doc_name=doc_name, - regulation_type=regulation_type, - version=version - ) - logger.success(f"分块完成,共{len(chunks)}个chunk") - return chunks - - except Exception as e: - logger.error(f"分块失败: {e}") - return [] - - def _embed_chunks(self, chunks: List[TextChunk]) -> Optional[EmbeddingResult]: - """嵌入分块""" - try: - # 延迟初始化嵌入模型 - self._init_embedder() - - # 提取文本内容 - texts = [chunk.content for chunk in chunks] - - # 执行嵌入 - embeddings = self.embedder.embed(texts) - - logger.success(f"嵌入完成,向量数: {len(embeddings.dense_embeddings)}") - return embeddings - - except Exception as e: - logger.error(f"嵌入失败: {e}") - return None - - def _insert_to_milvus( - self, - chunks: List[TextChunk], - embeddings: EmbeddingResult - ) -> List[int]: - """插入Milvus""" - try: - # 延迟初始化Milvus - self._init_milvus() - - # 执行插入 - inserted_ids = self.milvus.insert_chunks(chunks, embeddings) - - logger.success(f"入库完成,共{len(inserted_ids)}条记录") - return inserted_ids - - except Exception as e: - logger.error(f"入库失败: {e}") - return [] - - def search( - self, - query: str, - top_k: int = 10, - filters: Optional[str] = None - ) -> List[Dict]: - """ - 检索法规内容 - - Args: - query: 查询文本 - top_k: 返回结果数量 - filters: 过滤条件 - - Returns: - List[Dict]: 检索结果 - """ - logger.info(f"执行检索: {query}") - - try: - # 延迟初始化 - self._init_embedder() - self._init_milvus() - - # 生成查询向量 - query_embedding = self.embedder.embed_single(query) - - # 执行混合检索 - results = self.milvus.hybrid_search( - query_dense=query_embedding['dense'].tolist(), - query_sparse=query_embedding['sparse'], - top_k=top_k, - filters=filters - ) - - # 转换为字典格式 - result_dicts = [] - for r in results: - result_dicts.append({ - "id": r.id, - "content": r.content, - "score": r.score, - "metadata": r.metadata - }) - - logger.success(f"检索完成,返回{len(result_dicts)}条结果") - return result_dicts - - except Exception as e: - logger.error(f"检索失败: {e}") - return [] + def search(self, query: str, top_k: int = 10, filters: str | None = None) -> list[dict]: + """Handle search for the Document Processor instance.""" + results = get_retrieval_service().retrieve(query=query, top_k=top_k, filters=filters) + return [ + { + "id": item.chunk_id, + "content": item.content, + "score": item.score, + "metadata": { + "doc_id": item.doc_id, + "doc_name": item.doc_name, + "chunk_id": item.chunk_id, + "section_title": item.section_title, + "page_number": item.page_number, + **item.metadata, + }, + } + for item in results + ] def close(self): - """关闭连接""" - if self.milvus: - self.milvus.disconnect() - logger.info("文档处理器已关闭") - - -def process_document( - file_path: str, - doc_name: Optional[str] = None, - regulation_type: str = "", - version: str = "" -) -> ProcessingResult: - """便捷函数:处理单个文档""" - processor = DocumentProcessor() - result = processor.process(file_path, doc_name, regulation_type, version) - processor.close() - return result - - -def search_regulations(query: str, top_k: int = 10) -> List[Dict]: - """便捷函数:检索法规""" - processor = DocumentProcessor() - results = processor.search(query, top_k) - processor.close() - return results + """Release the resources held by this component.""" + return None diff --git a/backend/app/services/embedding/__init__.py b/backend/app/services/embedding/__init__.py index a071381..e2db803 100644 --- a/backend/app/services/embedding/__init__.py +++ b/backend/app/services/embedding/__init__.py @@ -1,6 +1,18 @@ -"""嵌入和分块服务""" +"""Initialize the app.services.embedding package.""" +# Keep package boundaries explicit so backend imports stay predictable. -from .text_chunker import RegulationChunker -from .bge_m3_embedder import BGEM3Embedder __all__ = ["RegulationChunker", "BGEM3Embedder"] + + +def __getattr__(name: str): + """Handle getattr for this module.""" + if name == "RegulationChunker": + from .text_chunker import RegulationChunker + + return RegulationChunker + if name == "BGEM3Embedder": + from .bge_m3_embedder import BGEM3Embedder + + return BGEM3Embedder + raise AttributeError(name) diff --git a/backend/app/services/embedding/bge_m3_embedder.py b/backend/app/services/embedding/bge_m3_embedder.py index 57d0463..321ce0b 100644 --- a/backend/app/services/embedding/bge_m3_embedder.py +++ b/backend/app/services/embedding/bge_m3_embedder.py @@ -1,4 +1,4 @@ -"""BGE-M3嵌入服务 - Dense+Sparse双路向量生成""" +"""Provide service-layer logic for bge m3 embedder.""" import numpy as np from typing import List, Dict, Optional, Union @@ -6,43 +6,31 @@ from dataclasses import dataclass, field from loguru import logger import torch import os +# Keep service responsibilities explicit so downstream behavior stays predictable. -# 设置HuggingFace镜像(国内网络) + +# Keep service responsibilities explicit so downstream behavior stays predictable. if 'HF_ENDPOINT' not in os.environ: os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com' -# 本地模型路径(按优先级检查) +# Keep service responsibilities explicit so downstream behavior stays predictable. LOCAL_MODEL_PATHS = [ - os.path.expanduser("~/.cache/modelscope/Xorbits/bge-m3"), # ModelScope下载路径 - os.path.expanduser("~/.cache/huggingface/hub/models--BAAI--bge-m3/snapshots/main"), # HuggingFace本地路径 + os.path.expanduser("~/.cache/modelscope/Xorbits/bge-m3"), # Keep service responsibilities explicit so downstream behavior stays predictable. + os.path.expanduser("~/.cache/huggingface/hub/models--BAAI--bge-m3/snapshots/main"), # Keep service responsibilities explicit so downstream behavior stays predictable. ] @dataclass class EmbeddingResult: - """嵌入结果""" - dense_embeddings: np.ndarray # Dense向量(语义检索) - sparse_embeddings: List[Dict[int, float]] # Sparse向量(关键词匹配) + """Represent the Embedding Result type.""" + dense_embeddings: np.ndarray # Keep service responsibilities explicit so downstream behavior stays predictable. + sparse_embeddings: List[Dict[int, float]] # Keep service responsibilities explicit so downstream behavior stays predictable. texts: List[str] dim: int = 1024 class BGEM3Embedder: - """ - BGE-M3多语言嵌入模型服务 - - BGE-M3是BAAI发布的多语言嵌入模型,支持: - - Dense向量:用于语义相似度检索 - - Sparse向量:用于关键词精确匹配(BM25风格) - - ColBERT向量:用于细粒度交互匹配(可选) - - 特点: - - 支持100+语言(中英双语优化) - - 8192 tokens超长上下文 - - Dense+Sparse双路检索能力 - - GitHub: https://github.com/FlagOpen/FlagEmbedding - """ + """Represent the B G E M3 Embedder type.""" def __init__( self, @@ -53,28 +41,18 @@ class BGEM3Embedder: max_length: int = 8192, local_model_path: Optional[str] = None ): - """ - 初始化BGE-M3嵌入模型 - - Args: - model_name: 模型名称(如果使用本地路径,此参数会被忽略) - use_fp16: 是否使用FP16加速 - device: 设备类型(cuda/cpu),默认自动选择 - batch_size: 批处理大小 - max_length: 最大序列长度 - local_model_path: 本地模型路径(可选,优先使用) - """ + """Initialize the B G E M3 Embedder instance.""" self.use_fp16 = use_fp16 self.batch_size = batch_size self.max_length = max_length - # 确定模型路径(优先使用本地路径) + # Keep service responsibilities explicit so downstream behavior stays predictable. if local_model_path and os.path.exists(local_model_path): self.model_path = local_model_path self.model_name = "local" logger.info(f"使用本地模型路径: {local_model_path}") else: - # 检查多个可能的本地路径 + # Keep service responsibilities explicit so downstream behavior stays predictable. found_local = False for path in LOCAL_MODEL_PATHS: if os.path.exists(path) and os.path.exists(os.path.join(path, "config.json")): @@ -89,7 +67,7 @@ class BGEM3Embedder: self.model_name = model_name logger.info(f"使用远程模型: {model_name}") - # 自动选择设备 + # Keep service responsibilities explicit so downstream behavior stays predictable. if device is None: self.device = "cuda" if torch.cuda.is_available() else "cpu" else: @@ -101,7 +79,7 @@ class BGEM3Embedder: self._load_model() def _load_model(self): - """加载嵌入模型""" + """Handle load model for this module for the B G E M3 Embedder instance.""" try: from FlagEmbedding import BGEM3FlagModel @@ -127,18 +105,7 @@ class BGEM3Embedder: return_sparse: bool = True, return_colbert_vecs: bool = False ) -> EmbeddingResult: - """ - 对文本列表生成嵌入向量 - - Args: - texts: 文本列表 - return_dense: 是否返回Dense向量 - return_sparse: 是否返回Sparse向量 - return_colbert_vecs: 是否返回ColBERT向量 - - Returns: - EmbeddingResult: 嵌入结果 - """ + """Handle embed for the B G E M3 Embedder instance.""" if not texts: logger.warning("输入文本列表为空") return EmbeddingResult( @@ -151,7 +118,7 @@ class BGEM3Embedder: logger.info(f"开始嵌入{len(texts)}个文本块") try: - # 执行嵌入 + # Keep service responsibilities explicit so downstream behavior stays predictable. embeddings = self.model.encode( texts, batch_size=self.batch_size, @@ -161,11 +128,11 @@ class BGEM3Embedder: return_colbert_vecs=return_colbert_vecs ) - # 提取结果 + # Keep service responsibilities explicit so downstream behavior stays predictable. dense_embeddings = embeddings.get('dense_vecs', np.array([])) sparse_embeddings = embeddings.get('lexical_weights', []) - # 获取维度 + # Keep service responsibilities explicit so downstream behavior stays predictable. dim = dense_embeddings.shape[1] if len(dense_embeddings) > 0 else 1024 logger.success(f"嵌入完成,向量维度: {dim}") @@ -182,15 +149,7 @@ class BGEM3Embedder: raise def embed_single(self, text: str) -> Dict[str, Union[np.ndarray, Dict]]: - """ - 对单个文本生成嵌入向量 - - Args: - text: 输入文本 - - Returns: - Dict: 包含dense和sparse向量 - """ + """Embed single for the B G E M3 Embedder instance.""" result = self.embed([text]) return { 'dense': result.dense_embeddings[0], @@ -199,25 +158,17 @@ class BGEM3Embedder: } def embed_dense(self, texts: List[str]) -> np.ndarray: - """只生成Dense向量""" + """Embed dense for the B G E M3 Embedder instance.""" result = self.embed(texts, return_sparse=False, return_colbert_vecs=False) return result.dense_embeddings def embed_sparse(self, texts: List[str]) -> List[Dict[int, float]]: - """只生成Sparse向量""" + """Embed sparse for the B G E M3 Embedder instance.""" result = self.embed(texts, return_dense=False, return_colbert_vecs=False) return result.sparse_embeddings def embed_query(self, query: str) -> Dict: - """ - 对查询文本生成嵌入(用于检索) - - Args: - query: 查询文本 - - Returns: - Dict: 包含dense和sparse向量 - """ + """Embed query for the B G E M3 Embedder instance.""" return self.embed_single(query) def compute_similarity( @@ -226,26 +177,16 @@ class BGEM3Embedder: doc_embeddings: np.ndarray, metric: str = "cosine" ) -> np.ndarray: - """ - 计算查询与文档的相似度 - - Args: - query_embedding: 查询向量 - doc_embeddings: 文档向量矩阵 - metric: 相似度度量(cosine/dot) - - Returns: - np.ndarray: 相似度分数数组 - """ + """Handle compute similarity for the B G E M3 Embedder instance.""" if metric == "cosine": - # 余弦相似度 + # Keep service responsibilities explicit so downstream behavior stays predictable. query_norm = np.linalg.norm(query_embedding) doc_norms = np.linalg.norm(doc_embeddings, axis=1) similarities = np.dot(doc_embeddings, query_embedding) / (doc_norms * query_norm) elif metric == "dot": - # 点积相似度 + # Keep service responsibilities explicit so downstream behavior stays predictable. similarities = np.dot(doc_embeddings, query_embedding) else: @@ -258,17 +199,8 @@ class BGEM3Embedder: query_sparse: Dict[int, float], doc_sparse: Dict[int, float] ) -> float: - """ - 计算Sparse向量的相似度(BM25风格) - - Args: - query_sparse: 查询的Sparse向量(词ID -> 权重) - doc_sparse: 文档的Sparse向量 - - Returns: - float: 相似度分数 - """ - # 计算交集词的点积 + """Handle sparse similarity for the B G E M3 Embedder instance.""" + # Keep service responsibilities explicit so downstream behavior stays predictable. common_keys = set(query_sparse.keys()) & set(doc_sparse.keys()) score = sum(query_sparse[k] * doc_sparse[k] for k in common_keys) @@ -280,7 +212,7 @@ def embed_texts( model_name: str = "BAAI/bge-m3", **kwargs ) -> EmbeddingResult: - """便捷函数:对文本列表生成嵌入""" + """Embed texts.""" embedder = BGEM3Embedder(model_name=model_name, **kwargs) return embedder.embed(texts) @@ -290,6 +222,6 @@ def embed_single_text( model_name: str = "BAAI/bge-m3", **kwargs ) -> Dict: - """便捷函数:对单个文本生成嵌入""" + """Embed single text.""" embedder = BGEM3Embedder(model_name=model_name, **kwargs) return embedder.embed_single(text) diff --git a/backend/app/services/embedding/text_chunker.py b/backend/app/services/embedding/text_chunker.py index 6b29398..291b366 100644 --- a/backend/app/services/embedding/text_chunker.py +++ b/backend/app/services/embedding/text_chunker.py @@ -1,51 +1,46 @@ -"""智能分块器 - 章节级+条款级双粒度切割""" +"""Provide service-layer logic for text chunker.""" import re from typing import List, Dict, Optional, Tuple from dataclasses import dataclass, field from loguru import logger +# Keep service responsibilities explicit so downstream behavior stays predictable. + @dataclass class ChunkMetadata: - """分块元数据""" + """Represent the Chunk Metadata type.""" doc_id: str = "" doc_name: str = "" chunk_id: str = "" - section_number: str = "" # 章节编号(如 "第一章") - section_title: str = "" # 章节标题 - clause_number: str = "" # 条款编号(如 "第一条") + section_number: str = "" # Keep service responsibilities explicit so downstream behavior stays predictable. + section_title: str = "" # Keep service responsibilities explicit so downstream behavior stays predictable. + clause_number: str = "" # Keep service responsibilities explicit so downstream behavior stays predictable. page_number: int = 0 - start_position: int = 0 # 在原文中的起始位置 - end_position: int = 0 # 在原文中的结束位置 - regulation_type: str = "" # 法规类型 + start_position: int = 0 # Keep service responsibilities explicit so downstream behavior stays predictable. + end_position: int = 0 # Keep service responsibilities explicit so downstream behavior stays predictable. + regulation_type: str = "" # Keep service responsibilities explicit so downstream behavior stays predictable. version: str = "" @dataclass class TextChunk: - """文本分块""" + """Represent the Text Chunk type.""" content: str metadata: ChunkMetadata - token_count: int = 0 # 估算的token数量 + token_count: int = 0 # Keep service responsibilities explicit so downstream behavior stays predictable. class RegulationChunker: - """ - 法规文档智能分块器 + """Represent the Regulation Chunker type.""" - 实现章节级/条款级双粒度切割,适配国标GB文档结构: - - 国标文档通常有明确的层级结构:章 > 节 > 条 - - 每个条款应作为一个独立的语义单元 - - 保留条款完整性,避免跨条款截断 - """ - - # 法规标题模式 + # Keep service responsibilities explicit so downstream behavior stays predictable. CHAPTER_PATTERN = re.compile(r'^第[一二三四五六七八九十百]+章\s+[^\n]+') SECTION_PATTERN = re.compile(r'^第[一二三四五六七八九十百]+节\s+[^\n]+') CLAUSE_PATTERN = re.compile(r'^第[一二三四五六七八九十百]+条\s') - # 条款子项模式 + # Keep service responsibilities explicit so downstream behavior stays predictable. SUB_ITEM_PATTERN = re.compile(r'^[\((][一二三四五六七八九十]+[\))]\s') NUMBER_ITEM_PATTERN = re.compile(r'^[\d]+[\.、]\s') @@ -56,15 +51,7 @@ class RegulationChunker: max_chunk_size: int = 2048, min_chunk_size: int = 100 ): - """ - 初始化分块器 - - Args: - chunk_size: 默认分块大小(字符数) - chunk_overlap: 分块重叠大小 - max_chunk_size: 最大分块大小(防止单个条款过长) - min_chunk_size: 最小分块大小(防止碎片化) - """ + """Initialize the Regulation Chunker instance.""" self.chunk_size = chunk_size self.chunk_overlap = chunk_overlap self.max_chunk_size = max_chunk_size @@ -78,30 +65,18 @@ class RegulationChunker: regulation_type: str = "", version: str = "" ) -> List[TextChunk]: - """ - 对法规文档进行智能分块 - - Args: - markdown_text: Markdown格式的文档内容 - doc_id: 文档ID - doc_name: 文档名称 - regulation_type: 法规类型 - version: 文档版本 - - Returns: - List[TextChunk]: 分块列表 - """ + """Handle chunk document for the Regulation Chunker instance.""" logger.info(f"开始分块文档: {doc_name}") - # 1. 按章节分割(一级分块) + # Keep service responsibilities explicit so downstream behavior stays predictable. sections = self._split_by_sections(markdown_text) - # 2. 在每个章节内按条款分割(二级分块) + # Keep service responsibilities explicit so downstream behavior stays predictable. chunks = [] global_position = 0 for section_num, section_title, section_content, section_start in sections: - # 在章节内按条款分割 + # Keep service responsibilities explicit so downstream behavior stays predictable. clause_chunks = self._split_by_clauses( section_content, section_num, @@ -110,7 +85,7 @@ class RegulationChunker: ) for chunk_content, clause_num, clause_title, start_pos, end_pos in clause_chunks: - # 处理过长的条款(进一步细分) + # Keep service responsibilities explicit so downstream behavior stays predictable. if len(chunk_content) > self.max_chunk_size: sub_chunks = self._split_long_clause( chunk_content, @@ -150,12 +125,7 @@ class RegulationChunker: return chunks def _split_by_sections(self, markdown_text: str) -> List[Tuple[str, str, str, int]]: - """ - 按章节分割文档 - - Returns: - List of (section_number, section_title, section_content, start_position) - """ + """Handle split by sections for this module for the Regulation Chunker instance.""" sections = [] lines = markdown_text.split('\n') @@ -165,12 +135,12 @@ class RegulationChunker: current_section_start = 0 for i, line in enumerate(lines): - # 检测章节标题 + # Keep service responsibilities explicit so downstream behavior stays predictable. chapter_match = self.CHAPTER_PATTERN.match(line.strip()) section_match = self.SECTION_PATTERN.match(line.strip()) if chapter_match or section_match: - # 保存上一个章节 + # Keep service responsibilities explicit so downstream behavior stays predictable. if current_section_content: content = '\n'.join(current_section_content) sections.append(( @@ -180,7 +150,7 @@ class RegulationChunker: current_section_start )) - # 开始新章节 + # Keep service responsibilities explicit so downstream behavior stays predictable. current_section_start = sum(len(l) + 1 for l in lines[:i]) current_section_content = [] @@ -193,7 +163,7 @@ class RegulationChunker: current_section_content.append(line) - # 保存最后一个章节 + # Keep service responsibilities explicit so downstream behavior stays predictable. if current_section_content: content = '\n'.join(current_section_content) sections.append(( @@ -203,7 +173,7 @@ class RegulationChunker: current_section_start )) - # 如果没有检测到章节,将整个文档作为一个大章节 + # Keep service responsibilities explicit so downstream behavior stays predictable. if not sections: sections.append(( "", @@ -221,12 +191,7 @@ class RegulationChunker: section_title: str, section_start: int ) -> List[Tuple[str, str, str, int, int]]: - """ - 在章节内按条款分割 - - Returns: - List of (content, clause_number, clause_title, start_position, end_position) - """ + """Handle split by clauses for this module for the Regulation Chunker instance.""" clauses = [] lines = section_content.split('\n') @@ -236,11 +201,11 @@ class RegulationChunker: current_clause_start = section_start for i, line in enumerate(lines): - # 检测条款标题 + # Keep service responsibilities explicit so downstream behavior stays predictable. clause_match = self.CLAUSE_PATTERN.match(line.strip()) if clause_match: - # 保存上一个条款 + # Keep service responsibilities explicit so downstream behavior stays predictable. if current_clause_content: content = '\n'.join(current_clause_content) end_pos = current_clause_start + len(content) @@ -252,7 +217,7 @@ class RegulationChunker: end_pos )) - # 开始新条款 + # Keep service responsibilities explicit so downstream behavior stays predictable. current_clause_start = section_start + sum(len(l) + 1 for l in lines[:i]) current_clause_content = [] current_clause_num = self._extract_clause_number(line.strip()) @@ -260,7 +225,7 @@ class RegulationChunker: current_clause_content.append(line) - # 保存最后一个条款 + # Keep service responsibilities explicit so downstream behavior stays predictable. if current_clause_content: content = '\n'.join(current_clause_content) end_pos = current_clause_start + len(content) @@ -272,7 +237,7 @@ class RegulationChunker: end_pos )) - # 如果没有检测到条款,将整个章节作为一个条款 + # Keep service responsibilities explicit so downstream behavior stays predictable. if not clauses: clauses.append(( section_content, @@ -290,15 +255,11 @@ class RegulationChunker: clause_num: str, clause_title: str ) -> List[Tuple[str, int, int]]: - """ - 分割过长的条款内容 - - 按条款子项或段落分割,保持语义完整性 - """ + """Handle split long clause for this module for the Regulation Chunker instance.""" sub_chunks = [] lines = content.split('\n') - # 检测是否有子项结构 + # Keep service responsibilities explicit so downstream behavior stays predictable. has_sub_items = any( self.SUB_ITEM_PATTERN.match(line.strip()) or self.NUMBER_ITEM_PATTERN.match(line.strip()) @@ -306,7 +267,7 @@ class RegulationChunker: ) if has_sub_items: - # 按子项分割 + # Keep service responsibilities explicit so downstream behavior stays predictable. current_sub_content = [] current_sub_start = 0 @@ -326,14 +287,14 @@ class RegulationChunker: current_sub_content.append(line) - # 保存最后一个子项 + # Keep service responsibilities explicit so downstream behavior stays predictable. if current_sub_content: sub_content = '\n'.join(current_sub_content) sub_end = current_sub_start + len(sub_content) sub_chunks.append((sub_content, current_sub_start, sub_end)) else: - # 按段落分割(滑动窗口) + # Keep service responsibilities explicit so downstream behavior stays predictable. paragraphs = [] current_para = [] @@ -348,7 +309,7 @@ class RegulationChunker: if current_para: paragraphs.append('\n'.join(current_para)) - # 合并段落直到达到chunk_size + # Keep service responsibilities explicit so downstream behavior stays predictable. current_chunk = [] current_length = 0 chunk_start = 0 @@ -365,7 +326,7 @@ class RegulationChunker: current_chunk.append(para) current_length += len(para) - # 保存最后一个chunk + # Keep service responsibilities explicit so downstream behavior stays predictable. if current_chunk: chunk_content = '\n'.join(current_chunk) chunk_end = chunk_start + len(chunk_content) @@ -374,13 +335,13 @@ class RegulationChunker: return sub_chunks def _extract_title(self, header_line: str) -> str: - """从标题行提取标题内容""" - # 移除"第X章"、"第X节"前缀 + """Handle extract title for this module for the Regulation Chunker instance.""" + # Keep service responsibilities explicit so downstream behavior stays predictable. title = re.sub(r'^第[一二三四五六七八九十百]+[章节]\s+', '', header_line) return title.strip() def _extract_clause_number(self, clause_line: str) -> str: - """从条款行提取条款编号""" + """Handle extract clause number for this module for the Regulation Chunker instance.""" match = self.CLAUSE_PATTERN.match(clause_line) if match: return match.group(0).strip() @@ -399,14 +360,14 @@ class RegulationChunker: regulation_type: str, version: str ) -> TextChunk: - """创建文本分块""" - # 清理内容 + """Handle create chunk for this module for the Regulation Chunker instance.""" + # Keep service responsibilities explicit so downstream behavior stays predictable. content = content.strip() - # 计算估算token数(中文约1.5字符/token) - token_count = int(len(content) * 0.7) # 简化估算 + # Keep service responsibilities explicit so downstream behavior stays predictable. + token_count = int(len(content) * 0.7) # Keep service responsibilities explicit so downstream behavior stays predictable. - # 生成chunk_id + # Keep service responsibilities explicit so downstream behavior stays predictable. chunk_id = f"{doc_id}_{section_num}_{clause_num}_{start_pos}" metadata = ChunkMetadata( @@ -437,7 +398,7 @@ def chunk_regulation_document( version: str = "", chunk_size: int = 512 ) -> List[TextChunk]: - """便捷函数:对法规文档进行分块""" + """Handle chunk regulation document.""" chunker = RegulationChunker(chunk_size=chunk_size) return chunker.chunk_document( markdown_text, diff --git a/backend/app/services/llm/__init__.py b/backend/app/services/llm/__init__.py index fe48360..3ad2605 100644 --- a/backend/app/services/llm/__init__.py +++ b/backend/app/services/llm/__init__.py @@ -1,14 +1,36 @@ -"""LLM服务模块""" +"""Initialize the app.services.llm package.""" -from .llm_factory import LLMFactory, get_llm_client -from .base_client import BaseLLMClient, LLMResponse, LLMConfig, LLMProvider +from .base_client import BaseLLMClient, LLMConfig, LLMProvider, LLMResponse from .deepseek_client import DeepSeekClient +from .llm_factory import LLMFactory, get_llm_client from .qwen_client import QwenClient, QwenVLClient -from .document_summarizer import DocumentSummarizer, summarize_document, DocumentSummary +# Keep package boundaries explicit so backend imports stay predictable. + __all__ = [ - "LLMFactory", "get_llm_client", - "BaseLLMClient", "LLMResponse", "LLMConfig", "LLMProvider", - "DeepSeekClient", "QwenClient", "QwenVLClient", - "DocumentSummarizer", "summarize_document", "DocumentSummary" + "LLMFactory", + "get_llm_client", + "BaseLLMClient", + "LLMResponse", + "LLMConfig", + "LLMProvider", + "DeepSeekClient", + "QwenClient", + "QwenVLClient", + "DocumentSummarizer", + "summarize_document", + "DocumentSummary", ] + + +def __getattr__(name: str): + """Handle getattr for this module.""" + if name in {"DocumentSummarizer", "summarize_document", "DocumentSummary"}: + from .document_summarizer import DocumentSummarizer, DocumentSummary, summarize_document + + return { + "DocumentSummarizer": DocumentSummarizer, + "summarize_document": summarize_document, + "DocumentSummary": DocumentSummary, + }[name] + raise AttributeError(name) diff --git a/backend/app/services/llm/base_client.py b/backend/app/services/llm/base_client.py index cf004b5..3070a97 100644 --- a/backend/app/services/llm/base_client.py +++ b/backend/app/services/llm/base_client.py @@ -1,13 +1,15 @@ -"""LLM客户端基类 - 统一接口定义""" +"""Provide service-layer logic for base client.""" from abc import ABC, abstractmethod from dataclasses import dataclass, field from typing import List, Dict, Optional, Any from enum import Enum +# Keep provider-specific behavior explicit so debugging stays straightforward. + class LLMProvider(Enum): - """LLM提供商""" + """Define the L L M Provider enumeration.""" DEEPSEEK = "deepseek" QWEN = "qwen" QWEN_VL = "qwen_vl" @@ -15,7 +17,7 @@ class LLMProvider(Enum): @dataclass class LLMResponse: - """LLM响应结果""" + """Represent the L L M Response type.""" content: str model: str usage: Dict[str, int] = field(default_factory=dict) @@ -25,12 +27,13 @@ class LLMResponse: @property def is_success(self) -> bool: + """Return whether success for the L L M Response instance.""" return self.error is None @dataclass class LLMConfig: - """LLM配置""" + """Define configuration for l l m config.""" provider: LLMProvider model: str api_key: str @@ -38,19 +41,20 @@ class LLMConfig: max_tokens: int = 4096 temperature: float = 0.7 top_p: float = 0.9 - timeout: int = 300 # 默认超时300秒(摘要/Skills生成可能需要较长时间) + timeout: int = 300 # Keep provider-specific behavior explicit so debugging stays straightforward. class BaseLLMClient(ABC): - """LLM客户端基类""" + """Represent the Base L L M Client type.""" def __init__(self, config: LLMConfig): + """Initialize the Base L L M Client instance.""" self.config = config self._client = None @abstractmethod def _init_client(self): - """初始化客户端""" + """Handle init client for this module for the Base L L M Client instance.""" pass @abstractmethod @@ -61,18 +65,7 @@ class BaseLLMClient(ABC): temperature: Optional[float] = None, **kwargs ) -> LLMResponse: - """ - 对话补全 - - Args: - messages: 对话消息列表 [{"role": "user/assistant/system", "content": "..."}] - max_tokens: 最大输出token数 - temperature: 温度参数 - **kwargs: 其他参数 - - Returns: - LLMResponse: 响应结果 - """ + """Handle chat for the Base L L M Client instance.""" pass def complete( @@ -83,18 +76,7 @@ class BaseLLMClient(ABC): temperature: Optional[float] = None, **kwargs ) -> LLMResponse: - """ - 单轮补全(便捷方法) - - Args: - prompt: 用户输入 - system_prompt: 系统提示词 - max_tokens: 最大输出token数 - temperature: 温度参数 - - Returns: - LLMResponse: 响应结果 - """ + """Handle complete for the Base L L M Client instance.""" messages = [] if system_prompt: messages.append({"role": "system", "content": system_prompt}) @@ -104,12 +86,12 @@ class BaseLLMClient(ABC): @abstractmethod def get_available_models(self) -> List[str]: - """获取可用模型列表""" + """Return available models for the Base L L M Client instance.""" pass def estimate_tokens(self, text: str) -> int: - """估算文本token数(粗略估计)""" - # 中文字符约1.5 token,英文约0.25 token + """Handle estimate tokens for the Base L L M Client instance.""" + # Keep provider-specific behavior explicit so debugging stays straightforward. chinese_chars = sum(1 for c in text if '一' <= c <= '鿿') other_chars = len(text) - chinese_chars return int(chinese_chars * 1.5 + other_chars * 0.25) diff --git a/backend/app/services/llm/deepseek_client.py b/backend/app/services/llm/deepseek_client.py index 8efe467..a457062 100644 --- a/backend/app/services/llm/deepseek_client.py +++ b/backend/app/services/llm/deepseek_client.py @@ -1,4 +1,4 @@ -"""DeepSeek LLM客户端 - OpenAI兼容API""" +"""Provide service-layer logic for deepseek client.""" import time from typing import List, Dict, Optional @@ -6,20 +6,12 @@ from loguru import logger import httpx from .base_client import BaseLLMClient, LLMResponse, LLMConfig, LLMProvider +# Keep provider-specific behavior explicit so debugging stays straightforward. + class DeepSeekClient(BaseLLMClient): - """ - DeepSeek API客户端(OpenAI兼容格式) - - 支持模型: - - deepseek-chat - - deepseek-coder - - deepseek-reasoner - - deepseek-v3 - - deepseek-v3.2 - - deepseek-v4-flash - """ + """Represent the Deep Seek Client type.""" SUPPORTED_MODELS = [ "deepseek-chat", @@ -31,13 +23,14 @@ class DeepSeekClient(BaseLLMClient): ] def __init__(self, config: LLMConfig): + """Initialize the Deep Seek Client instance.""" if config.provider != LLMProvider.DEEPSEEK: raise ValueError(f"配置provider应为DEEPSEEK,实际为{config.provider}") super().__init__(config) self._init_client() def _init_client(self): - """初始化HTTP客户端""" + """Handle init client for this module for the Deep Seek Client instance.""" self._client = httpx.Client( base_url=self.config.base_url, headers={ @@ -55,7 +48,7 @@ class DeepSeekClient(BaseLLMClient): temperature: Optional[float] = None, **kwargs ) -> LLMResponse: - """对话补全""" + """Handle chat for the Deep Seek Client instance.""" start_time = time.time() try: @@ -103,11 +96,11 @@ class DeepSeekClient(BaseLLMClient): ) def get_available_models(self) -> List[str]: - """获取可用模型列表""" + """Return available models for the Deep Seek Client instance.""" return self.SUPPORTED_MODELS def close(self): - """关闭客户端""" + """Release the resources held by this component.""" if self._client: self._client.close() @@ -118,7 +111,7 @@ def create_deepseek_client( base_url: str = "http://6.86.80.4:30080/v1", **kwargs ) -> DeepSeekClient: - """便捷函数:创建DeepSeek客户端""" + """Create deepseek client.""" config = LLMConfig( provider=LLMProvider.DEEPSEEK, model=model, diff --git a/backend/app/services/llm/document_summarizer.py b/backend/app/services/llm/document_summarizer.py index c430a17..74ed3e9 100644 --- a/backend/app/services/llm/document_summarizer.py +++ b/backend/app/services/llm/document_summarizer.py @@ -1,17 +1,20 @@ -"""文档摘要生成服务 - LLM生成法规文档摘要""" +"""Provide service-layer logic for document summarizer.""" from typing import Dict, Optional from dataclasses import dataclass from loguru import logger -from app.services.llm import get_llm_client, BaseLLMClient +from app.services.llm.base_client import BaseLLMClient +from app.services.llm.llm_factory import get_llm_client from app.services.rag.prompt_templates import get_prompt_template from app.config.settings import settings +# Keep provider-specific behavior explicit so debugging stays straightforward. + @dataclass class DocumentSummary: - """文档摘要结果""" + """Represent the Document Summary type.""" doc_name: str summary: str applicable_scope: str @@ -24,24 +27,12 @@ class DocumentSummary: @property def is_success(self) -> bool: + """Return whether success for the Document Summary instance.""" return self.error is None class DocumentSummarizer: - """ - 文档摘要生成器 - - 功能: - - 生成法规文档的核心要点摘要 - - 提取适用范围 - - 突出关键条款 - - 列出合规要点 - - 使用示例: - summarizer = DocumentSummarizer() - result = summarizer.summarize("GB 7258-2017", markdown_content) - print(result.summary) - """ + """Represent the Document Summarizer type.""" def __init__( self, @@ -49,25 +40,18 @@ class DocumentSummarizer: model: str = None, max_tokens: int = None ): - """ - 初始化摘要生成器 - - Args: - provider: LLM提供商 - model: LLM模型名称 - max_tokens: 最大输出token数 - """ + """Initialize the Document Summarizer instance.""" self.provider = provider or settings.llm_provider self.model = model or settings.llm_model self.max_tokens = max_tokens or settings.rag_summary_max_tokens - # LLM客户端(延迟加载) + # Keep provider-specific behavior explicit so debugging stays straightforward. self.llm: Optional[BaseLLMClient] = None logger.info(f"摘要生成器初始化: provider={self.provider}, model={self.model}") def _init_llm(self): - """延迟初始化LLM""" + """Handle init llm for this module for the Document Summarizer instance.""" if self.llm is None: self.llm = get_llm_client( provider=self.provider, @@ -81,18 +65,7 @@ class DocumentSummarizer: regulation_type: str = "", max_tokens: Optional[int] = None ) -> DocumentSummary: - """ - 生成文档摘要 - - Args: - doc_name: 文档名称 - content: 文档内容(Markdown格式) - regulation_type: 法规类型 - max_tokens: 最大输出token数 - - Returns: - DocumentSummary: 摘要结果 - """ + """Handle summarize for the Document Summarizer instance.""" import time start_time = time.time() @@ -101,23 +74,23 @@ class DocumentSummarizer: try: self._init_llm() - # 使用摘要模板 + # Keep provider-specific behavior explicit so debugging stays straightforward. template = get_prompt_template("document_summary") - # 构建用户消息 + # Keep provider-specific behavior explicit so debugging stays straightforward. user_content = template.user_template.format( doc_name=doc_name, - content=content[:8000] # 截取前8000字符(避免超出token限制) + content=content[:8000] # Keep provider-specific behavior explicit so debugging stays straightforward. ) - # 调用LLM + # Keep provider-specific behavior explicit so debugging stays straightforward. response = self.llm.chat( messages=[ {"role": "system", "content": template.system_prompt}, {"role": "user", "content": user_content} ], max_tokens=max_tokens or self.max_tokens, - temperature=0.3 # 低温度保证摘要准确性 + temperature=0.3 # Keep provider-specific behavior explicit so debugging stays straightforward. ) latency_ms = int((time.time() - start_time) * 1000) @@ -135,7 +108,7 @@ class DocumentSummarizer: error=response.error ) - # 解析摘要结构 + # Keep provider-specific behavior explicit so debugging stays straightforward. summary_data = self._parse_summary(response.content) logger.success(f"摘要生成完成: {doc_name}, {latency_ms}ms") @@ -166,7 +139,7 @@ class DocumentSummarizer: ) def _parse_summary(self, content: str) -> Dict: - """解析摘要内容(提取结构化信息)""" + """Handle parse summary for this module for the Document Summarizer instance.""" result = { "summary": content, "applicable_scope": "", @@ -175,26 +148,26 @@ class DocumentSummarizer: "compliance_points": [] } - # 简单解析(提取关键信息) + # Keep provider-specific behavior explicit so debugging stays straightforward. lines = content.split("\n") for line in lines: line = line.strip() - # 提取适用范围 + # Keep provider-specific behavior explicit so debugging stays straightforward. if "适用范围" in line or "适用对象" in line: result["applicable_scope"] = line.split(":")[-1].strip() if ":" in line else line.split(":")[-1].strip() - # 提取关键条款 + # Keep provider-specific behavior explicit so debugging stays straightforward. if line.startswith("- 【条款") or line.startswith("【条款"): result["key_clauses"].append(line) - # 提取关键术语 + # Keep provider-specific behavior explicit so debugging stays straightforward. if "关键术语" in line or "术语定义" in line: - # 继续读取后续几行 + # Keep provider-specific behavior explicit so debugging stays straightforward. pass - # 提取合规要点 + # Keep provider-specific behavior explicit so debugging stays straightforward. if "合规要点" in line or "必须满足" in line: pass @@ -204,15 +177,7 @@ class DocumentSummarizer: self, documents: list ) -> list: - """ - 批量生成摘要 - - Args: - documents: 文档列表 [{"doc_name": str, "content": str}, ...] - - Returns: - list: 摘要结果列表 - """ + """Handle batch summarize for the Document Summarizer instance.""" results = [] for doc in documents: result = self.summarize(doc["doc_name"], doc["content"]) @@ -225,6 +190,6 @@ def summarize_document( content: str, **kwargs ) -> DocumentSummary: - """便捷函数:生成文档摘要""" + """Handle summarize document.""" summarizer = DocumentSummarizer(**kwargs) return summarizer.summarize(doc_name, content) diff --git a/backend/app/services/llm/llm_factory.py b/backend/app/services/llm/llm_factory.py index 7da006b..093439d 100644 --- a/backend/app/services/llm/llm_factory.py +++ b/backend/app/services/llm/llm_factory.py @@ -1,4 +1,4 @@ -"""LLM工厂 - 统一创建和管理LLM客户端""" +"""Provide service-layer logic for llm factory.""" from typing import Optional, Dict, Any from loguru import logger @@ -7,16 +7,18 @@ from functools import lru_cache from .base_client import BaseLLMClient, LLMConfig, LLMProvider, LLMResponse from .deepseek_client import DeepSeekClient from .qwen_client import QwenClient, QwenVLClient +# Keep provider-specific behavior explicit so debugging stays straightforward. -# 默认模型映射 + +# Keep provider-specific behavior explicit so debugging stays straightforward. DEFAULT_MODELS = { LLMProvider.DEEPSEEK: "deepseek-v4-flash", LLMProvider.QWEN: "qwen3.5-flash", LLMProvider.QWEN_VL: "qwen3-vl-plus" } -# API基础URL(使用统一代理服务) +# Keep provider-specific behavior explicit so debugging stays straightforward. DEFAULT_BASE_URLS = { LLMProvider.DEEPSEEK: "http://6.86.80.4:30080/v1", LLMProvider.QWEN: "http://6.86.80.4:30080/v1", @@ -25,31 +27,13 @@ DEFAULT_BASE_URLS = { class LLMFactory: - """ - LLM客户端工厂(支持全局缓存) + """Represent the L L M Factory type.""" - 支持的提供商和模型: - - DeepSeek: deepseek-chat (DeepSeek-V3), deepseek-coder - - Qwen: qwen-turbo, qwen-plus, qwen-max, qwen-long - - QwenVL: qwen-vl-plus, qwen-vl-max (多模态) - - 使用示例: - factory = LLMFactory() - - # 使用默认配置 - client = factory.create("deepseek") - - # 自定义配置 - client = factory.create("qwen", model="qwen-max", temperature=0.5) - - # 调用LLM - response = client.complete("你好,介绍一下自己") - """ - - # 全局客户端缓存(类级别,跨实例共享) + # Keep provider-specific behavior explicit so debugging stays straightforward. _global_instances: Dict[str, BaseLLMClient] = {} def __init__(self): + """Initialize the L L M Factory instance.""" self._config_cache: Dict[str, Any] = {} def create( @@ -62,24 +46,10 @@ class LLMFactory: temperature: float = 0.7, **kwargs ) -> BaseLLMClient: - """ - 创建LLM客户端 - - Args: - provider: 提供商名称 ("deepseek", "qwen", "qwen_vl") - api_key: API密钥(如未提供,从环境变量获取) - model: 模型名称(如未提供,使用默认模型) - base_url: API基础URL - max_tokens: 最大输出token数 - temperature: 温度参数 - **kwargs: 其他配置参数 - - Returns: - BaseLLMClient: LLM客户端实例 - """ + """Handle create for the L L M Factory instance.""" provider_enum = self._parse_provider(provider) - # 获取配置 + # Keep provider-specific behavior explicit so debugging stays straightforward. api_key = api_key or self._get_api_key(provider_enum) model = model or DEFAULT_MODELS.get(provider_enum) base_url = base_url or DEFAULT_BASE_URLS.get(provider_enum) @@ -87,7 +57,7 @@ class LLMFactory: if not api_key: raise ValueError(f"缺少API密钥,请设置环境变量或传入api_key参数") - # 检查全局缓存 + # Keep provider-specific behavior explicit so debugging stays straightforward. cache_key = f"{provider}_{model}" if cache_key in LLMFactory._global_instances: logger.debug(f"使用缓存的LLM客户端: {cache_key}") @@ -103,17 +73,17 @@ class LLMFactory: **kwargs ) - # 创建客户端 + # Keep provider-specific behavior explicit so debugging stays straightforward. client = self._create_client(config) - # 缓存到全局实例 + # Keep provider-specific behavior explicit so debugging stays straightforward. LLMFactory._global_instances[cache_key] = client logger.info(f"LLM客户端创建成功并缓存: {provider} - {model}") return client def _parse_provider(self, provider: str) -> LLMProvider: - """解析提供商名称""" + """Handle parse provider for this module for the L L M Factory instance.""" provider_map = { "deepseek": LLMProvider.DEEPSEEK, "deepseek-v3": LLMProvider.DEEPSEEK, @@ -137,7 +107,7 @@ class LLMFactory: return provider_map[provider_lower] def _get_api_key(self, provider: LLMProvider) -> Optional[str]: - """从环境变量获取API密钥""" + """Handle get api key for this module for the L L M Factory instance.""" import os key_map = { @@ -154,7 +124,7 @@ class LLMFactory: return None def _create_client(self, config: LLMConfig) -> BaseLLMClient: - """创建具体客户端""" + """Handle create client for this module for the L L M Factory instance.""" client_map = { LLMProvider.DEEPSEEK: DeepSeekClient, LLMProvider.QWEN: QwenClient, @@ -168,14 +138,14 @@ class LLMFactory: return client_class(config) def get_cached(self, provider: str, model: Optional[str] = None) -> Optional[BaseLLMClient]: - """获取缓存的客户端""" + """Return cached for the L L M Factory instance.""" provider_enum = self._parse_provider(provider) model = model or DEFAULT_MODELS.get(provider_enum) cache_key = f"{provider}_{model}" return LLMFactory._global_instances.get(cache_key) def list_available_providers(self) -> Dict[str, list]: - """列出可用的提供商和模型""" + """List available providers for the L L M Factory instance.""" return { "deepseek": DeepSeekClient.SUPPORTED_MODELS, "qwen": QwenClient.SUPPORTED_MODELS, @@ -184,12 +154,7 @@ class LLMFactory: @classmethod def preload_clients(cls, providers: list = None): - """ - 预加载LLM客户端(应用启动时调用) - - Args: - providers: 要预加载的提供商列表,默认加载qwen和deepseek - """ + """Handle preload clients for the L L M Factory instance.""" if providers is None: providers = ["qwen", "deepseek"] @@ -203,9 +168,9 @@ class LLMFactory: @classmethod def get_global_client(cls, provider: str, model: Optional[str] = None) -> Optional[BaseLLMClient]: - """获取全局缓存的客户端""" + """Return global client for the L L M Factory instance.""" provider_lower = provider.lower() - # 处理模型名作为provider的情况(如 qwen3.5-flash) + # Keep provider-specific behavior explicit so debugging stays straightforward. if provider_lower.startswith("qwen"): provider_lower = "qwen" model = model or DEFAULT_MODELS.get(LLMProvider.QWEN if provider_lower == "qwen" else LLMProvider.DEEPSEEK) @@ -214,7 +179,7 @@ class LLMFactory: @classmethod def cleanup(cls): - """清理所有缓存的客户端""" + """Handle cleanup for the L L M Factory instance.""" for cache_key, client in cls._global_instances.items(): try: client.close() @@ -227,7 +192,7 @@ class LLMFactory: @lru_cache def get_llm_factory() -> LLMFactory: - """获取LLM工厂实例(缓存)""" + """Return llm factory.""" return LLMFactory() @@ -236,20 +201,10 @@ def get_llm_client( model: Optional[str] = None, **kwargs ) -> BaseLLMClient: - """ - 便捷函数:获取LLM客户端(优先使用缓存) - - Args: - provider: 提供商名称 - model: 模型名称 - **kwargs: 其他配置 - - Returns: - BaseLLMClient: LLM客户端实例 - """ + """Return llm client.""" factory = get_llm_factory() - # 先尝试获取缓存的实例 + # Keep provider-specific behavior explicit so debugging stays straightforward. cached = factory.get_cached(provider, model) if cached: return cached diff --git a/backend/app/services/llm/qwen_client.py b/backend/app/services/llm/qwen_client.py index 7f44f84..b3bd5d6 100644 --- a/backend/app/services/llm/qwen_client.py +++ b/backend/app/services/llm/qwen_client.py @@ -1,4 +1,4 @@ -"""Qwen LLM客户端 - 支持OpenAI兼容API格式""" +"""Provide service-layer logic for qwen client.""" import time import json @@ -7,21 +7,12 @@ from loguru import logger import httpx from .base_client import BaseLLMClient, LLMResponse, LLMConfig, LLMProvider +# Keep provider-specific behavior explicit so debugging stays straightforward. + class QwenClient(BaseLLMClient): - """ - Qwen API客户端(OpenAI兼容格式) - - 支持通过new-api等代理服务调用: - - qwen-turbo - - qwen-plus - - qwen-max - - qwen3.5-flash (推荐:快速响应) - - qwen3.5-plus - - qwen-long - - qwen2.5系列 - """ + """Represent the Qwen Client type.""" SUPPORTED_MODELS = [ "qwen-turbo", @@ -39,14 +30,15 @@ class QwenClient(BaseLLMClient): ] def __init__(self, config: LLMConfig): + """Initialize the Qwen Client instance.""" if config.provider not in [LLMProvider.QWEN, LLMProvider.QWEN_VL]: raise ValueError(f"配置provider应为Qwen,实际为{config.provider}") super().__init__(config) self._init_client() def _init_client(self): - """初始化HTTP客户端""" - # OpenAI兼容API格式 + """Handle init client for this module for the Qwen Client instance.""" + # Keep provider-specific behavior explicit so debugging stays straightforward. self._client = httpx.Client( base_url=self.config.base_url, headers={ @@ -64,11 +56,11 @@ class QwenClient(BaseLLMClient): temperature: Optional[float] = None, **kwargs ) -> LLMResponse: - """对话补全(OpenAI兼容格式)""" + """Handle chat for the Qwen Client instance.""" start_time = time.time() try: - # OpenAI兼容格式的请求体 + # Keep provider-specific behavior explicit so debugging stays straightforward. payload = { "model": self.config.model, "messages": messages, @@ -78,7 +70,7 @@ class QwenClient(BaseLLMClient): "stream": False } - # OpenAI兼容接口路径 + # Keep provider-specific behavior explicit so debugging stays straightforward. response = self._client.post("/chat/completions", json=payload) response.raise_for_status() @@ -86,7 +78,7 @@ class QwenClient(BaseLLMClient): latency_ms = int((time.time() - start_time) * 1000) - # OpenAI兼容格式的响应解析 + # Keep provider-specific behavior explicit so debugging stays straightforward. choices = data.get("choices", [{}]) message = choices[0].get("message", {}) @@ -121,42 +113,33 @@ class QwenClient(BaseLLMClient): temperature: Optional[float] = None, **kwargs ) -> Generator[str, None, None]: - """ - 流式对话补全(SSE格式) - - Yields: - str: 每次返回一个文本片段 - - 使用示例: - for chunk in client.stream_chat(messages): - print(chunk, end="", flush=True) - """ + """Stream chat for the Qwen Client instance.""" try: - # OpenAI兼容格式的请求体,启用流式输出 + # Keep provider-specific behavior explicit so debugging stays straightforward. payload = { "model": self.config.model, "messages": messages, "max_tokens": max_tokens or self.config.max_tokens, "temperature": temperature or self.config.temperature, "top_p": kwargs.get("top_p", self.config.top_p), - "stream": True # 启用流式输出 + "stream": True # Keep provider-specific behavior explicit so debugging stays straightforward. } - # 使用stream模式发送请求 + # Keep provider-specific behavior explicit so debugging stays straightforward. with self._client.stream("POST", "/chat/completions", json=payload) as response: for line in response.iter_lines(): if line: line = line.strip() - # SSE格式: data: {...} + # Keep provider-specific behavior explicit so debugging stays straightforward. if line.startswith("data: "): - data_str = line[6:] # 移除 "data: " 前缀 + data_str = line[6:] # Keep provider-specific behavior explicit so debugging stays straightforward. if data_str == "[DONE]": break try: data = json.loads(data_str) choices = data.get("choices", []) if not choices: - continue # 跳过空的choices + continue # Keep provider-specific behavior explicit so debugging stays straightforward. delta = choices[0].get("delta", {}) content = delta.get("content", "") if content: @@ -179,41 +162,27 @@ class QwenClient(BaseLLMClient): temperature: Optional[float] = None, **kwargs ) -> AsyncGenerator[str, None]: - """ - 异步流式对话补全(用于FastAPI SSE响应) - - Yields: - str: 每次返回一个文本片段 - """ + """Handle async stream chat for the Qwen Client instance.""" import asyncio - # 使用同步流式方法,包装为异步 + # Keep provider-specific behavior explicit so debugging stays straightforward. for chunk in self.stream_chat(messages, max_tokens, temperature, **kwargs): yield chunk - # 给async循环一个小延迟,让其他任务有机会执行 + # Keep provider-specific behavior explicit so debugging stays straightforward. await asyncio.sleep(0) def get_available_models(self) -> List[str]: - """获取可用模型列表""" + """Return available models for the Qwen Client instance.""" return self.SUPPORTED_MODELS def close(self): - """关闭客户端""" + """Release the resources held by this component.""" if self._client: self._client.close() class QwenVLClient(BaseLLMClient): - """ - Qwen VL多模态客户端(OpenAI兼容格式) - - 支持模型: - - qwen-vl-plus - - qwen-vl-max - - qwen3-vl-plus - - qwen2-vl-7b-instruct - - qwen2-vl-72b-instruct - """ + """Represent the Qwen V L Client type.""" SUPPORTED_MODELS = [ "qwen-vl-plus", @@ -224,13 +193,14 @@ class QwenVLClient(BaseLLMClient): ] def __init__(self, config: LLMConfig): + """Initialize the Qwen V L Client instance.""" if config.provider != LLMProvider.QWEN_VL: raise ValueError(f"配置provider应为QWEN_VL,实际为{config.provider}") super().__init__(config) self._init_client() def _init_client(self): - """初始化HTTP客户端""" + """Handle init client for this module for the Qwen V L Client instance.""" self._client = httpx.Client( base_url=self.config.base_url, headers={ @@ -248,21 +218,11 @@ class QwenVLClient(BaseLLMClient): temperature: Optional[float] = None, **kwargs ) -> LLMResponse: - """多模态对话补全(OpenAI兼容格式) - - 支持图片输入,消息格式: - { - "role": "user", - "content": [ - {"type": "image_url", "image_url": {"url": "https://example.com/image.jpg"}}, - {"type": "text", "text": "描述这张图片"} - ] - } - """ + """Handle chat for the Qwen V L Client instance.""" start_time = time.time() try: - # OpenAI兼容格式的请求体 + # Keep provider-specific behavior explicit so debugging stays straightforward. payload = { "model": self.config.model, "messages": messages, @@ -312,7 +272,7 @@ class QwenVLClient(BaseLLMClient): temperature: Optional[float] = None, **kwargs ) -> Generator[str, None, None]: - """流式多模态对话补全""" + """Stream chat for the Qwen V L Client instance.""" try: payload = { "model": self.config.model, @@ -335,7 +295,7 @@ class QwenVLClient(BaseLLMClient): data = json.loads(data_str) choices = data.get("choices", []) if not choices: - continue # 跳过空的choices + continue # Keep provider-specific behavior explicit so debugging stays straightforward. delta = choices[0].get("delta", {}) content = delta.get("content", "") if content: @@ -348,11 +308,11 @@ class QwenVLClient(BaseLLMClient): yield f"[ERROR: {str(e)}]" def get_available_models(self) -> List[str]: - """获取可用模型列表""" + """Return available models for the Qwen V L Client instance.""" return self.SUPPORTED_MODELS def close(self): - """关闭客户端""" + """Release the resources held by this component.""" if self._client: self._client.close() @@ -363,7 +323,7 @@ def create_qwen_client( base_url: str = "http://6.86.80.4:30080/v1", **kwargs ) -> QwenClient: - """便捷函数:创建Qwen客户端""" + """Create qwen client.""" config = LLMConfig( provider=LLMProvider.QWEN, model=model, @@ -380,7 +340,7 @@ def create_qwen_vl_client( base_url: str = "http://6.86.80.4:30080/v1", **kwargs ) -> QwenVLClient: - """便捷函数:创建QwenVL客户端""" + """Create qwen vl client.""" config = LLMConfig( provider=LLMProvider.QWEN_VL, model=model, diff --git a/backend/app/services/mock_data.py b/backend/app/services/mock_data.py index d065d77..ab8d002 100644 --- a/backend/app/services/mock_data.py +++ b/backend/app/services/mock_data.py @@ -1,12 +1,12 @@ -""" -Mock数据服务 - 提供预设假数据供前后端对接测试 -""" +"""Provide service-layer logic for mock data.""" from datetime import datetime from typing import Dict, List, Any import uuid +# Keep service responsibilities explicit so downstream behavior stays predictable. -# 预设法规文档列表 + +# Keep service responsibilities explicit so downstream behavior stays predictable. MOCK_DOCUMENTS: List[Dict[str, Any]] = [ { "id": "doc-001", @@ -45,7 +45,7 @@ MOCK_DOCUMENTS: List[Dict[str, Any]] = [ }, ] -# 预设快捷问题 +# Keep service responsibilities explicit so downstream behavior stays predictable. MOCK_QUICK_QUESTIONS: List[Dict[str, str]] = [ {"id": "q1", "question": "电动自行车需要上牌照吗?", "category": "车辆登记"}, {"id": "q2", "question": "新能源汽车有哪些补贴政策?", "category": "新能源"}, @@ -53,7 +53,7 @@ MOCK_QUICK_QUESTIONS: List[Dict[str, str]] = [ {"id": "q4", "question": "驾驶证过期了怎么处理?", "category": "驾驶证"}, ] -# 预设检索结果 +# Keep service responsibilities explicit so downstream behavior stays predictable. MOCK_RETRIEVAL_RESULTS: List[Dict[str, Any]] = [ { "id": "chunk-001", @@ -97,7 +97,7 @@ MOCK_RETRIEVAL_RESULTS: List[Dict[str, Any]] = [ }, ] -# 预设RAG问答答案模板(按关键词匹配) +# Keep service responsibilities explicit so downstream behavior stays predictable. MOCK_RAG_ANSWERS: Dict[str, Dict[str, Any]] = { "电动自行车": { "text": "根据《道路交通安全法》及相关规范,电动自行车上路需满足以下条件:\n\n1. 符合国家标准 GB17761-2018\n2. 经公安机关交通管理部门登记\n3. 最高设计车速不超过 25km/h\n4. 整车质量不超过 55kg\n5. 具有脚踏骑行能力\n6. 蓄电池标称电压不超过 48V\n\n行驶时还需佩戴安全头盔,不得逆向行驶或在机动车道内行驶。", @@ -133,7 +133,7 @@ MOCK_RAG_ANSWERS: Dict[str, Dict[str, Any]] = { }, } -# 预设合规分析结果 +# Keep service responsibilities explicit so downstream behavior stays predictable. MOCK_COMPLIANCE_RESULT: Dict[str, Any] = { "task_id": "task-001", "dashboard": { @@ -310,7 +310,7 @@ MOCK_COMPLIANCE_RESULT: Dict[str, Any] = { ], } -# 预设合规对话响应模板 +# Keep service responsibilities explicit so downstream behavior stays predictable. MOCK_COMPLIANCE_CHAT_RESPONSES: Dict[str, Dict[str, str]] = { "车身结构设计": { "compliance": "根据当前分析,车身结构设计部分存在以下合规问题:\n\n1. GB 26112-2010要求车顶承受1.5倍整备质量载荷,目前设计声明满足要求但缺少测试数据\n2. C-NCAP正面碰撞后车门应能打开,需提供碰撞测试报告\n\n建议补充相关测试数据以提升合规评分。", @@ -329,7 +329,7 @@ MOCK_COMPLIANCE_CHAT_RESPONSES: Dict[str, Dict[str, str]] = { }, } -# 预设系统统计数据 +# Keep service responsibilities explicit so downstream behavior stays predictable. MOCK_SYSTEM_STATS: Dict[str, int] = { "docs": 5, "chunks": 510, @@ -337,7 +337,7 @@ MOCK_SYSTEM_STATS: Dict[str, int] = { "segments": 0, } -# 预设系统配置 +# Keep service responsibilities explicit so downstream behavior stays predictable. MOCK_SYSTEM_CONFIG: Dict[str, Any] = { "llm": { "model": "qwen-max", @@ -358,17 +358,17 @@ MOCK_SYSTEM_CONFIG: Dict[str, Any] = { def get_mock_documents() -> List[Dict[str, Any]]: - """获取预设法规文档列表""" + """Return mock documents.""" return MOCK_DOCUMENTS def get_mock_quick_questions() -> List[Dict[str, str]]: - """获取预设快捷问题""" + """Return mock quick questions.""" return MOCK_QUICK_QUESTIONS def get_mock_retrieval(query: str, top_k: int = 5) -> List[Dict[str, Any]]: - """根据查询关键词返回预设检索结果""" + """Return mock retrieval.""" results = [] for keyword, data in MOCK_RAG_ANSWERS.items(): if keyword in query: @@ -389,7 +389,7 @@ def get_mock_retrieval(query: str, top_k: int = 5) -> List[Dict[str, Any]]: def get_mock_rag_answer(query: str) -> str: - """根据查询关键词返回预设答案""" + """Return mock rag answer.""" for keyword, data in MOCK_RAG_ANSWERS.items(): if keyword in query: return data["text"] @@ -397,14 +397,14 @@ def get_mock_rag_answer(query: str) -> str: def get_mock_compliance_result(task_id: str) -> Dict[str, Any]: - """获取预设合规分析结果""" + """Return mock compliance result.""" result = MOCK_COMPLIANCE_RESULT.copy() result["task_id"] = task_id return result def get_mock_compliance_chat_response(intent: str, query: str) -> str: - """获取预设合规对话响应""" + """Return mock compliance chat response.""" responses = MOCK_COMPLIANCE_CHAT_RESPONSES.get(intent, {}) if "合规" in query or "符合" in query: return responses.get("compliance", "根据相关法规分析,该段落的合规性需进一步评估。") @@ -416,10 +416,10 @@ def get_mock_compliance_chat_response(intent: str, query: str) -> str: def generate_task_id() -> str: - """生成任务ID""" + """Handle generate task id.""" return f"task-{uuid.uuid4().hex[:8]}" def generate_doc_id() -> str: - """生成文档ID""" + """Handle generate doc id.""" return f"doc-{uuid.uuid4().hex[:8]}" \ No newline at end of file diff --git a/backend/app/services/parser/__init__.py b/backend/app/services/parser/__init__.py index 8e8fe2f..0d0d4c4 100644 --- a/backend/app/services/parser/__init__.py +++ b/backend/app/services/parser/__init__.py @@ -1,6 +1,8 @@ -"""文档解析服务""" +"""Initialize the app.services.parser package.""" from .pdf_parser import PDFParser from .docx_parser import DocxParser +# Keep package boundaries explicit so backend imports stay predictable. + __all__ = ["PDFParser", "DocxParser"] diff --git a/backend/app/services/parser/docx_parser.py b/backend/app/services/parser/docx_parser.py index f3adea9..8dc2d9d 100644 --- a/backend/app/services/parser/docx_parser.py +++ b/backend/app/services/parser/docx_parser.py @@ -1,4 +1,4 @@ -"""Word文档解析 - 使用python-docx""" +"""Provide service-layer logic for docx parser.""" from docx import Document from docx.enum.text import WD_ALIGN_PARAGRAPH @@ -6,27 +6,29 @@ from typing import List, Dict, Optional from dataclasses import dataclass, field from loguru import logger import re +# Keep service responsibilities explicit so downstream behavior stays predictable. + @dataclass class DocxParagraph: - """段落内容""" + """Represent the Docx Paragraph type.""" text: str - level: int = 0 # 标题级别,0表示正文 + level: int = 0 # Keep service responsibilities explicit so downstream behavior stays predictable. is_list: bool = False list_number: Optional[str] = None @dataclass class DocxTable: - """表格内容""" + """Represent the Docx Table type.""" rows: List[List[str]] markdown: str = "" @dataclass class DocxDocumentContent: - """Word文档完整内容""" + """Represent the Docx Document Content type.""" file_path: str paragraphs: List[DocxParagraph] tables: List[DocxTable] @@ -35,21 +37,14 @@ class DocxDocumentContent: class DocxParser: - """Word文档解析器 - 基于python-docx""" + """Provide the Docx Parser parser.""" def __init__(self): + """Initialize the Docx Parser instance.""" self.document = None def parse(self, file_path: str) -> DocxDocumentContent: - """ - 解析Word文档 - - Args: - file_path: Word文档路径 - - Returns: - DocxDocumentContent: 解析后的文档内容 - """ + """Handle parse for the Docx Parser instance.""" logger.info(f"开始解析Word文档: {file_path}") try: @@ -60,16 +55,16 @@ class DocxParser: tables=[] ) - # 提取文档元数据 + # Keep service responsibilities explicit so downstream behavior stays predictable. doc_content.metadata = self._extract_metadata() - # 提取段落 + # Keep service responsibilities explicit so downstream behavior stays predictable. doc_content.paragraphs = self._extract_paragraphs() - # 提取表格 + # Keep service responsibilities explicit so downstream behavior stays predictable. doc_content.tables = self._extract_tables() - # 生成Markdown格式文本 + # Keep service responsibilities explicit so downstream behavior stays predictable. doc_content.markdown_text = self._generate_markdown(doc_content) logger.success(f"Word文档解析完成,共{len(doc_content.paragraphs)}个段落") @@ -81,7 +76,7 @@ class DocxParser: raise def _extract_metadata(self) -> Dict[str, str]: - """提取文档元数据""" + """Handle extract metadata for this module for the Docx Parser instance.""" metadata = {} try: core_props = self.document.core_properties @@ -98,7 +93,7 @@ class DocxParser: return metadata def _extract_paragraphs(self) -> List[DocxParagraph]: - """提取所有段落""" + """Handle extract paragraphs for this module for the Docx Parser instance.""" paragraphs = [] for para in self.document.paragraphs: @@ -106,10 +101,10 @@ class DocxParser: if not text: continue - # 判断标题级别 + # Keep service responsibilities explicit so downstream behavior stays predictable. level = self._get_paragraph_level(para) - # 判断是否是列表项 + # Keep service responsibilities explicit so downstream behavior stays predictable. is_list, list_number = self._detect_list_item(para) paragraph = DocxParagraph( @@ -123,66 +118,61 @@ class DocxParser: return paragraphs def _get_paragraph_level(self, para) -> int: - """ - 判断段落标题级别 - - Returns: - int: 标题级别,0表示正文 - """ - # 方法1:检查段落样式 + """Handle get paragraph level for this module for the Docx Parser instance.""" + # Keep service responsibilities explicit so downstream behavior stays predictable. style_name = para.style.name if para.style else "" if "Heading" in style_name or "标题" in style_name: - # 从样式名称中提取级别 + # Keep service responsibilities explicit so downstream behavior stays predictable. match = re.search(r'Heading\s*(\d)|标题\s*(\d)', style_name) if match: level = int(match.group(1) or match.group(2)) return level - # 方法2:检查段落格式(字号) - # 标题通常字号较大 + # Keep service responsibilities explicit so downstream behavior stays predictable. + # Keep service responsibilities explicit so downstream behavior stays predictable. if para.paragraph_format: - # 可以根据字号判断,这里简化处理 + # Keep service responsibilities explicit so downstream behavior stays predictable. pass - # 方法3:根据内容模式判断(法规文档特征) + # Keep service responsibilities explicit so downstream behavior stays predictable. text = para.text.strip() - # 第一章、第X章 -> 二级标题 + # Keep service responsibilities explicit so downstream behavior stays predictable. if re.match(r'^第[一二三四五六七八九十百]+章\s', text): return 2 - # 第X节 -> 三级标题 + # Keep service responsibilities explicit so downstream behavior stays predictable. elif re.match(r'^第[一二三四五六七八九十百]+节\s', text): return 3 - # 第X条 -> 四级标题 + # Keep service responsibilities explicit so downstream behavior stays predictable. elif re.match(r'^第[一二三四五六七八九十百]+条\s', text): return 4 - return 0 # 正文 + return 0 # Keep service responsibilities explicit so downstream behavior stays predictable. def _detect_list_item(self, para) -> tuple[bool, Optional[str]]: - """检测是否是列表项""" + """Handle detect list item for this module for the Docx Parser instance.""" text = para.text.strip() - # 数字列表:1.、2.、(1)、[1]等 + # Keep service responsibilities explicit so downstream behavior stays predictable. if re.match(r'^[\d]+[.、)\]]\s', text): match = re.match(r'^([\d]+[.、)\]])\s', text) return True, match.group(1) if match else None - # 中文数字列表:一、二、(一)等 + # Keep service responsibilities explicit so downstream behavior stays predictable. if re.match(r'^[一二三四五六七八九十]+[、.)]\s', text): match = re.match(r'^([一二三四五六七八九十]+[、.)])\s', text) return True, match.group(1) if match else None - # 检查段落格式中的列表编号 + # Keep service responsibilities explicit so downstream behavior stays predictable. if para.paragraph_format and hasattr(para.paragraph_format, 'left_indent'): - # 有缩进的可能是列表项 + # Keep service responsibilities explicit so downstream behavior stays predictable. pass return False, None def _extract_tables(self) -> List[DocxTable]: - """提取所有表格""" + """Handle extract tables for this module for the Docx Parser instance.""" tables = [] for table in self.document.tables: @@ -193,7 +183,7 @@ class DocxParser: cells.append(cell.text.strip()) rows.append(cells) - # 转换为Markdown表格 + # Keep service responsibilities explicit so downstream behavior stays predictable. markdown = self._table_to_markdown(rows) table_content = DocxTable(rows=rows, markdown=markdown) @@ -202,34 +192,34 @@ class DocxParser: return tables def _table_to_markdown(self, rows: List[List[str]]) -> str: - """将表格转换为Markdown格式""" + """Handle table to markdown for this module for the Docx Parser instance.""" if not rows or len(rows) < 1: return "" lines = [] - # 表头 + # Keep service responsibilities explicit so downstream behavior stays predictable. if len(rows) >= 1: header = rows[0] lines.append("| " + " | ".join(cell for cell in header) + " |") lines.append("| " + " | ".join("---" for _ in header) + " |") - # 数据行 + # Keep service responsibilities explicit so downstream behavior stays predictable. for row in rows[1:]: lines.append("| " + " | ".join(cell for cell in row) + " |") return "\n".join(lines) def _generate_markdown(self, doc_content: DocxDocumentContent) -> str: - """生成Markdown格式文本""" + """Handle generate markdown for this module for the Docx Parser instance.""" lines = [] - # 文档标题 + # Keep service responsibilities explicit so downstream behavior stays predictable. title = doc_content.metadata.get("title", "") if title: lines.append(f"# {title}\n") else: - # 从第一个段落获取标题(如果是标题样式) + # Keep service responsibilities explicit so downstream behavior stays predictable. for para in doc_content.paragraphs[:5]: if para.level == 1: lines.append(f"# {para.text}\n") @@ -237,29 +227,29 @@ class DocxParser: else: lines.append(f"# {doc_content.file_path}\n") - # 元数据信息 + # Keep service responsibilities explicit so downstream behavior stays predictable. lines.append("\n## 文档信息\n") for key, value in doc_content.metadata.items(): if value: lines.append(f"- **{key}**: {value}") - # 正文内容 + # Keep service responsibilities explicit so downstream behavior stays predictable. lines.append("\n## 正文\n") table_index = 0 for para in doc_content.paragraphs: if para.level > 0: - # 标题 + # Keep service responsibilities explicit so downstream behavior stays predictable. prefix = "#" * para.level lines.append(f"\n{prefix} {para.text}\n") elif para.is_list: - # 列表项 + # Keep service responsibilities explicit so downstream behavior stays predictable. lines.append(f"- {para.text}") else: - # 正文 + # Keep service responsibilities explicit so downstream behavior stays predictable. lines.append(para.text) - # 添加表格 + # Keep service responsibilities explicit so downstream behavior stays predictable. if doc_content.tables: lines.append("\n## 表格\n") for i, table in enumerate(doc_content.tables): @@ -269,18 +259,18 @@ class DocxParser: return "\n".join(lines) def parse_to_markdown(self, file_path: str) -> str: - """直接解析并返回Markdown文本""" + """Parse to markdown for the Docx Parser instance.""" doc_content = self.parse(file_path) return doc_content.markdown_text def parse_docx(file_path: str) -> DocxDocumentContent: - """便捷函数:解析Word文档""" + """Parse docx.""" parser = DocxParser() return parser.parse(file_path) def parse_docx_to_markdown(file_path: str) -> str: - """便捷函数:解析Word并返回Markdown""" + """Parse docx to markdown.""" parser = DocxParser() return parser.parse_to_markdown(file_path) diff --git a/backend/app/services/parser/mineru_parser.py b/backend/app/services/parser/mineru_parser.py index 327a951..8703c7e 100644 --- a/backend/app/services/parser/mineru_parser.py +++ b/backend/app/services/parser/mineru_parser.py @@ -1,14 +1,16 @@ -"""MinerU多模态PDF解析 - 版面感知解析""" +"""Provide service-layer logic for mineru parser.""" from typing import Optional, Dict from dataclasses import dataclass, field from loguru import logger import os +# Keep service responsibilities explicit so downstream behavior stays predictable. + @dataclass class MinerUResult: - """MinerU解析结果""" + """Represent the Miner U Result type.""" file_path: str markdown_text: str metadata: Dict[str, str] = field(default_factory=dict) @@ -17,21 +19,14 @@ class MinerUResult: class MinerUParser: - """ - MinerU多模态PDF解析器 - - MinerU (magic-pdf) 是一个开源的高质量PDF解析工具, - 支持版面感知解析,能够识别文档中的标题、正文、表格、图片等元素, - 并输出结构化的Markdown格式。 - - GitHub: https://github.com/opendatalab/MinerU - """ + """Provide the Miner U Parser parser.""" def __init__(self): + """Initialize the Miner U Parser instance.""" self.available = self._check_mineru_available() def _check_mineru_available(self) -> bool: - """检查MinerU是否可用""" + """Handle check mineru available for this module for the Miner U Parser instance.""" try: from magic_pdf.pipe.UNIPipe import UNIPipe return True @@ -40,16 +35,7 @@ class MinerUParser: return False def parse(self, file_path: str, output_dir: Optional[str] = None) -> MinerUResult: - """ - 使用MinerU解析PDF文档 - - Args: - file_path: PDF文件路径 - output_dir: 输出目录(可选,用于保存解析产物) - - Returns: - MinerUResult: 解析结果 - """ + """Handle parse for the Miner U Parser instance.""" logger.info(f"尝试使用MinerU解析: {file_path}") if not self.available: @@ -64,19 +50,19 @@ class MinerUParser: from magic_pdf.pipe.UNIPipe import UNIPipe from magic_pdf.libs.MakeContentConfig import DropMode - # 设置输出目录 + # Keep service responsibilities explicit so downstream behavior stays predictable. if output_dir is None: output_dir = os.path.dirname(file_path) - # 创建解析管道 - # OCR模式可以根据PDF类型选择 - # auto: 自动判断是否需要OCR - # txt: 纯文本PDF(无OCR) - # ocr: 扫描件PDF(OCR) + # Keep service responsibilities explicit so downstream behavior stays predictable. + # Keep service responsibilities explicit so downstream behavior stays predictable. + # Keep service responsibilities explicit so downstream behavior stays predictable. + # Keep service responsibilities explicit so downstream behavior stays predictable. + # Keep service responsibilities explicit so downstream behavior stays predictable. pipe = UNIPipe(file_path, output_dir) - # 执行解析 - # pipe_mk() 返回Markdown格式文本 + # Keep service responsibilities explicit so downstream behavior stays predictable. + # Keep service responsibilities explicit so downstream behavior stays predictable. markdown_content = pipe.pipe_mk() logger.success(f"MinerU解析成功") @@ -98,13 +84,13 @@ class MinerUParser: ) def _extract_metadata(self, pipe) -> Dict[str, str]: - """从解析管道提取元数据""" + """Handle extract metadata for this module for the Miner U Parser instance.""" metadata = {} try: - # MinerU解析管道中可能包含的元数据信息 + # Keep service responsibilities explicit so downstream behavior stays predictable. if hasattr(pipe, 'pdf_mid_data') and pipe.pdf_mid_data: mid_data = pipe.pdf_mid_data - # 提取可能的元数据字段 + # Keep service responsibilities explicit so downstream behavior stays predictable. metadata = { "page_count": str(mid_data.get("page_count", "")), "language": str(mid_data.get("language", "")), @@ -116,41 +102,27 @@ class MinerUParser: return metadata def parse_to_markdown(self, file_path: str) -> str: - """直接解析并返回Markdown文本""" + """Parse to markdown for the Miner U Parser instance.""" result = self.parse(file_path) return result.markdown_text if result.success else "" class ParserOrchestrator: - """ - 解析服务编排 - 按优先级选择解析器 - - 解析策略: - 1. 优先尝试MinerU(版面感知能力强) - 2. MinerU失败时回退到基础PyMuPDF解析 - """ + """Represent the Parser Orchestrator type.""" def __init__(self): + """Initialize the Parser Orchestrator instance.""" from .pdf_parser import PDFParser self.mineru_parser = MinerUParser() self.pdf_parser = PDFParser() self.mineru_available = self.mineru_parser.available def parse_pdf(self, file_path: str, prefer_mineru: bool = True) -> str: - """ - 解析PDF文档,按优先级选择解析器 - - Args: - file_path: PDF文件路径 - prefer_mineru: 是否优先使用MinerU - - Returns: - str: Markdown格式文本 - """ + """Parse pdf for the Parser Orchestrator instance.""" markdown_text = "" if prefer_mineru and self.mineru_available: - # 优先尝试MinerU + # Keep service responsibilities explicit so downstream behavior stays predictable. result = self.mineru_parser.parse(file_path) if result.success: markdown_text = result.markdown_text @@ -159,28 +131,20 @@ class ParserOrchestrator: else: logger.warning(f"MinerU解析失败,回退到PyMuPDF: {result.error_message}") - # 回退到PyMuPDF基础解析 + # Keep service responsibilities explicit so downstream behavior stays predictable. logger.info("使用PyMuPDF基础解析") markdown_text = self.pdf_parser.parse_to_markdown(file_path) return markdown_text def parse_docx(self, file_path: str) -> str: - """解析Word文档""" + """Parse docx for the Parser Orchestrator instance.""" from .docx_parser import DocxParser docx_parser = DocxParser() return docx_parser.parse_to_markdown(file_path) def parse(self, file_path: str) -> str: - """ - 根据文件类型选择解析器 - - Args: - file_path: 文件路径 - - Returns: - str: Markdown格式文本 - """ + """Handle parse for the Parser Orchestrator instance.""" ext = os.path.splitext(file_path)[1].lower() if ext == ".pdf": @@ -192,12 +156,12 @@ class ParserOrchestrator: def parse_with_mineru(file_path: str) -> MinerUResult: - """便捷函数:使用MinerU解析""" + """Parse with mineru.""" parser = MinerUParser() return parser.parse(file_path) def parse_pdf_smart(file_path: str) -> str: - """便捷函数:智能解析PDF(自动选择最佳解析器)""" + """Parse pdf smart.""" orchestrator = ParserOrchestrator() return orchestrator.parse_pdf(file_path) diff --git a/backend/app/services/parser/pdf_parser.py b/backend/app/services/parser/pdf_parser.py index 2d8eff6..72e7d65 100644 --- a/backend/app/services/parser/pdf_parser.py +++ b/backend/app/services/parser/pdf_parser.py @@ -1,4 +1,4 @@ -"""PDF文档解析 - 使用PyMuPDF基础解析""" +"""Provide service-layer logic for pdf parser.""" import fitz # PyMuPDF from typing import List, Dict, Optional, Tuple @@ -9,17 +9,17 @@ import re @dataclass class PDFPageContent: - """PDF页面内容""" + """Represent the P D F Page Content type.""" page_number: int text: str tables: List[str] = field(default_factory=list) - images: List[str] = field(default_factory=list) # 图片路径列表 + images: List[str] = field(default_factory=list) # Keep service responsibilities explicit so downstream behavior stays predictable. blocks: List[Dict] = field(default_factory=list) @dataclass class PDFDocumentContent: - """PDF文档完整内容""" + """Represent the P D F Document Content type.""" file_path: str total_pages: int pages: List[PDFPageContent] @@ -28,23 +28,14 @@ class PDFDocumentContent: class PDFParser: - """PDF文档解析器 - 基于PyMuPDF""" + """Provide the P D F Parser parser.""" def __init__(self): + """Initialize the P D F Parser instance.""" self.pdf = None def parse(self, file_path: str, extract_tables: bool = True, extract_images: bool = False) -> PDFDocumentContent: - """ - 解析PDF文档 - - Args: - file_path: PDF文件路径 - extract_tables: 是否提取表格 - extract_images: 是否提取图片 - - Returns: - PDFDocumentContent: 解析后的文档内容 - """ + """Handle parse for the P D F Parser instance.""" logger.info(f"开始解析PDF文档: {file_path}") try: @@ -55,16 +46,16 @@ class PDFParser: pages=[] ) - # 提取文档元数据 + # Keep service responsibilities explicit so downstream behavior stays predictable. doc_content.metadata = self._extract_metadata() - # 逐页解析 + # Keep service responsibilities explicit so downstream behavior stays predictable. for page_num in range(self.pdf.page_count): page = self.pdf[page_num] page_content = self._parse_page(page, page_num + 1, extract_tables, extract_images) doc_content.pages.append(page_content) - # 生成Markdown格式文本 + # Keep service responsibilities explicit so downstream behavior stays predictable. doc_content.markdown_text = self._generate_markdown(doc_content) self.pdf.close() @@ -77,7 +68,7 @@ class PDFParser: raise def _extract_metadata(self) -> Dict[str, str]: - """提取PDF元数据""" + """Handle extract metadata for this module for the P D F Parser instance.""" metadata = {} try: meta = self.pdf.metadata @@ -97,23 +88,23 @@ class PDFParser: def _parse_page(self, page: fitz.Page, page_num: int, extract_tables: bool, extract_images: bool) -> PDFPageContent: - """解析单页内容""" + """Handle parse page for this module for the P D F Parser instance.""" page_content = PDFPageContent(page_number=page_num, text="") - # 提取文本块(保留结构) + # Keep service responsibilities explicit so downstream behavior stays predictable. blocks = page.get_text("dict", flags=fitz.TEXT_PRESERVE_WHITESPACE)["blocks"] page_content.blocks = blocks - # 提取纯文本 + # Keep service responsibilities explicit so downstream behavior stays predictable. text = page.get_text("text", flags=fitz.TEXT_PRESERVE_WHITESPACE) page_content.text = text.strip() - # 提取表格(使用PyMuPDF的表格提取功能) + # Keep service responsibilities explicit so downstream behavior stays predictable. if extract_tables: tables = self._extract_tables_from_page(page) page_content.tables = tables - # 提取图片 + # Keep service responsibilities explicit so downstream behavior stays predictable. if extract_images: images = self._extract_images_from_page(page, page_num) page_content.images = images @@ -121,25 +112,22 @@ class PDFParser: return page_content def _extract_tables_from_page(self, page: fitz.Page) -> List[str]: - """ - 从页面提取表格(基于文本块分析) - 注意:PyMuPDF基础版表格提取能力有限,复杂表格建议使用MinerU - """ + """Handle extract tables from page for this module for the P D F Parser instance.""" tables = [] try: - # 使用PyMuPDF的表格提取方法(2.4+版本) - # 对于更复杂的表格,需要在mineru_parser中使用更高级的方法 + # Keep service responsibilities explicit so downstream behavior stays predictable. + # Keep service responsibilities explicit so downstream behavior stays predictable. tabs = page.find_tables() if tabs: for tab in tabs: table_text = tab.extract() - # 将表格转换为Markdown格式 + # Keep service responsibilities explicit so downstream behavior stays predictable. markdown_table = self._table_to_markdown(table_text) tables.append(markdown_table) except AttributeError: - # 旧版本PyMuPDF没有表格提取功能 + # Keep service responsibilities explicit so downstream behavior stays predictable. logger.warning("PyMuPDF版本不支持表格提取,请升级到2.4+版本") except Exception as e: logger.warning(f"表格提取失败: {e}") @@ -147,28 +135,28 @@ class PDFParser: return tables def _table_to_markdown(self, table_data: List[List[str]]) -> str: - """将表格数据转换为Markdown格式""" + """Handle table to markdown for this module for the P D F Parser instance.""" if not table_data or len(table_data) < 1: return "" lines = [] - # 表头 + # Keep service responsibilities explicit so downstream behavior stays predictable. if len(table_data) >= 1: header = table_data[0] lines.append("| " + " | ".join(str(cell).strip() for cell in header) + " |") lines.append("| " + " | ".join("---" for _ in header) + " |") - # 数据行 + # Keep service responsibilities explicit so downstream behavior stays predictable. for row in table_data[1:]: lines.append("| " + " | ".join(str(cell).strip() for cell in row) + " |") return "\n".join(lines) def _extract_images_from_page(self, page: fitz.Page, page_num: int) -> List[str]: - """提取页面图片""" + """Handle extract images from page for this module for the P D F Parser instance.""" images = [] - # 图片提取功能(可选实现) - # 这里仅记录图片信息,实际图片需要额外保存 + # Keep service responsibilities explicit so downstream behavior stays predictable. + # Keep service responsibilities explicit so downstream behavior stays predictable. try: image_list = page.get_images() for img_index, img in enumerate(image_list): @@ -179,52 +167,52 @@ class PDFParser: return images def _generate_markdown(self, doc_content: PDFDocumentContent) -> str: - """生成Markdown格式文本""" + """Handle generate markdown for this module for the P D F Parser instance.""" lines = [] - # 文档标题 + # Keep service responsibilities explicit so downstream behavior stays predictable. title = doc_content.metadata.get("title", "") if title: lines.append(f"# {title}\n") else: lines.append(f"# {doc_content.file_path}\n") - # 元数据信息 + # Keep service responsibilities explicit so downstream behavior stays predictable. lines.append("\n## 文档信息\n") for key, value in doc_content.metadata.items(): if value and key in ["author", "subject", "keywords", "creation_date"]: lines.append(f"- **{key}**: {value}") - # 正文内容 + # Keep service responsibilities explicit so downstream behavior stays predictable. lines.append("\n## 正文\n") for page in doc_content.pages: - # 页码标记 + # Keep service responsibilities explicit so downstream behavior stays predictable. lines.append(f"\n---\n**第 {page.page_number} 页**\n") - # 处理文本内容,识别标题结构 + # Keep service responsibilities explicit so downstream behavior stays predictable. text = self._process_page_text(page.text, page.blocks) lines.append(text) - # 添加表格 + # Keep service responsibilities explicit so downstream behavior stays predictable. for table in page.tables: lines.append("\n" + table + "\n") return "\n".join(lines) def _process_page_text(self, text: str, blocks: List[Dict]) -> str: - """处理页面文本,识别标题结构""" - # 基于字体大小识别标题 + """Handle process page text for this module for the P D F Parser instance.""" + # Keep service responsibilities explicit so downstream behavior stays predictable. processed_text = text - # 尝试识别标题(基于字号) - # 法规文档通常有明确的层级结构:章、节、条 + # Keep service responsibilities explicit so downstream behavior stays predictable. + # Keep service responsibilities explicit so downstream behavior stays predictable. processed_text = self._detect_headers(text, blocks) return processed_text def _detect_headers(self, text: str, blocks: List[Dict]) -> str: - """检测并标记标题(基于字号或内容模式)""" + """Handle detect headers for this module for the P D F Parser instance.""" lines = text.split("\n") processed_lines = [] @@ -233,8 +221,8 @@ class PDFParser: if not line: continue - # 法规标题模式检测 - # 第一章、第X章、第X节、第X条等 + # Keep service responsibilities explicit so downstream behavior stays predictable. + # Keep service responsibilities explicit so downstream behavior stays predictable. if re.match(r'^第[一二三四五六七八九十百]+章\s', line): processed_lines.append(f"\n## {line}\n") elif re.match(r'^第[一二三四五六七八九十百]+节\s', line): @@ -242,7 +230,7 @@ class PDFParser: elif re.match(r'^第[一二三四五六七八九十百]+条\s', line): processed_lines.append(f"\n#### {line}\n") elif re.match(r'^[一二三四五六七八九十]+\s*[、.]', line): - # 条款子项 + # Keep service responsibilities explicit so downstream behavior stays predictable. processed_lines.append(f"- {line}") else: processed_lines.append(line) @@ -250,18 +238,18 @@ class PDFParser: return "\n".join(processed_lines) def parse_to_markdown(self, file_path: str) -> str: - """直接解析并返回Markdown文本""" + """Parse to markdown for the P D F Parser instance.""" doc_content = self.parse(file_path) return doc_content.markdown_text def parse_pdf(file_path: str, **kwargs) -> PDFDocumentContent: - """便捷函数:解析PDF文档""" + """Parse pdf.""" parser = PDFParser() return parser.parse(file_path, **kwargs) def parse_pdf_to_markdown(file_path: str) -> str: - """便捷函数:解析PDF并返回Markdown""" + """Parse pdf to markdown.""" parser = PDFParser() return parser.parse_to_markdown(file_path) diff --git a/backend/app/services/rag/__init__.py b/backend/app/services/rag/__init__.py index 0493d71..9520174 100644 --- a/backend/app/services/rag/__init__.py +++ b/backend/app/services/rag/__init__.py @@ -1,11 +1,29 @@ -"""RAG服务模块""" +"""Initialize the app.services.rag package.""" +# Keep package boundaries explicit so backend imports stay predictable. -from .retriever import Retriever, retrieve_regulations -from .context_builder import ContextBuilder, build_rag_context -from .prompt_templates import PromptTemplates, get_prompt_template __all__ = [ - "Retriever", "retrieve_regulations", - "ContextBuilder", "build_rag_context", - "PromptTemplates", "get_prompt_template" + "Retriever", + "retrieve_regulations", + "ContextBuilder", + "build_rag_context", + "PromptTemplates", + "get_prompt_template", ] + + +def __getattr__(name: str): + """Handle getattr for this module.""" + if name in {"Retriever", "retrieve_regulations"}: + from .retriever import Retriever, retrieve_regulations + + return {"Retriever": Retriever, "retrieve_regulations": retrieve_regulations}[name] + if name in {"ContextBuilder", "build_rag_context"}: + from .context_builder import ContextBuilder, build_rag_context + + return {"ContextBuilder": ContextBuilder, "build_rag_context": build_rag_context}[name] + if name in {"PromptTemplates", "get_prompt_template"}: + from .prompt_templates import PromptTemplates, get_prompt_template + + return {"PromptTemplates": PromptTemplates, "get_prompt_template": get_prompt_template}[name] + raise AttributeError(name) diff --git a/backend/app/services/rag/context_builder.py b/backend/app/services/rag/context_builder.py index 8904972..b4dd3b7 100644 --- a/backend/app/services/rag/context_builder.py +++ b/backend/app/services/rag/context_builder.py @@ -1,4 +1,4 @@ -"""RAG上下文构建服务 - 构建LLM输入上下文""" +"""Provide service-layer logic for context builder.""" from typing import List, Dict, Optional from dataclasses import dataclass @@ -6,11 +6,13 @@ from loguru import logger from .retriever import RetrievedDocument from app.config.settings import settings +# Keep service responsibilities explicit so downstream behavior stays predictable. + @dataclass class RAGContext: - """RAG构建的上下文""" + """Represent the R A G Context type.""" system_prompt: str context_text: str user_query: str @@ -20,14 +22,7 @@ class RAGContext: class ContextBuilder: - """ - RAG上下文构建器 - - 功能: - - 格式化检索结果为上下文文本 - - 控制上下文长度(token限制) - - 构建完整的LLM输入格式 - """ + """Provide the Context Builder builder.""" def __init__( self, @@ -35,14 +30,7 @@ class ContextBuilder: include_metadata: bool = True, citation_format: str = "【条款{clause}】" ): - """ - 初始化上下文构建器 - - Args: - max_context_tokens: 最大上下文token数 - include_metadata: 是否包含元数据(文档名、条款号等) - citation_format: 引用格式模板 - """ + """Initialize the Context Builder instance.""" self.max_context_tokens = max_context_tokens or settings.rag_max_context_tokens self.include_metadata = include_metadata self.citation_format = citation_format @@ -56,30 +44,19 @@ class ContextBuilder: system_prompt: Optional[str] = None, max_tokens: Optional[int] = None ) -> RAGContext: - """ - 构建RAG上下文 - - Args: - query: 用户查询 - documents: 检索到的文档列表 - system_prompt: 系统提示词(可选) - max_tokens: 最大token数(可选,覆盖默认值) - - Returns: - RAGContext: 构建的上下文对象 - """ + """Handle build for the Context Builder instance.""" max_tokens = max_tokens or self.max_context_tokens - # 格式化文档内容 + # Keep service responsibilities explicit so downstream behavior stays predictable. context_text, sources, truncated = self._format_documents( documents, max_tokens ) - # 构建系统提示词 + # Keep service responsibilities explicit so downstream behavior stays predictable. system_prompt = system_prompt or self._default_system_prompt() - # 估算总token数 + # Keep service responsibilities explicit so downstream behavior stays predictable. total_tokens = self._estimate_tokens(system_prompt + context_text + query) logger.info(f"上下文构建完成: {len(documents)}条文档, {total_tokens}tokens, truncated={truncated}") @@ -98,29 +75,20 @@ class ContextBuilder: documents: List[RetrievedDocument], max_tokens: int ) -> tuple: - """ - 格式化文档内容 - - Args: - documents: 文档列表 - max_tokens: 最大token数 - - Returns: - (context_text, sources, truncated) - """ + """Handle format documents for this module for the Context Builder instance.""" context_parts = [] sources = [] current_tokens = 0 truncated = False for i, doc in enumerate(documents): - # 格式化单个文档 + # Keep service responsibilities explicit so downstream behavior stays predictable. formatted = self._format_single_doc(doc, i + 1) - # 估算token数 + # Keep service responsibilities explicit so downstream behavior stays predictable. doc_tokens = self._estimate_tokens(formatted) - # 检查是否超出限制 + # Keep service responsibilities explicit so downstream behavior stays predictable. if current_tokens + doc_tokens > max_tokens: truncated = True logger.warning(f"上下文截断: 已达到{max_tokens}tokens限制") @@ -129,7 +97,7 @@ class ContextBuilder: context_parts.append(formatted) current_tokens += doc_tokens - # 记录来源 + # Keep service responsibilities explicit so downstream behavior stays predictable. sources.append({ "index": i + 1, "doc_id": doc.doc_id, @@ -148,13 +116,13 @@ class ContextBuilder: doc: RetrievedDocument, index: int ) -> str: - """格式化单个文档""" + """Handle format single doc for this module for the Context Builder instance.""" parts = [] - # 索引编号 + # Keep service responsibilities explicit so downstream behavior stays predictable. parts.append(f"[{index}]") - # 元数据(可选) + # Keep service responsibilities explicit so downstream behavior stays predictable. if self.include_metadata: meta_parts = [] @@ -171,13 +139,13 @@ class ContextBuilder: if meta_parts: parts.append(" | ".join(meta_parts)) - # 内容 + # Keep service responsibilities explicit so downstream behavior stays predictable. parts.append(doc.content) return "\n".join(parts) def _default_system_prompt(self) -> str: - """默认系统提示词""" + """Handle default system prompt for this module for the Context Builder instance.""" return """你是合规专家助手,基于提供的法规条款回答问题。 回答要求: @@ -192,8 +160,8 @@ class ContextBuilder: - 最后给出合规建议""" def _estimate_tokens(self, text: str) -> int: - """估算文本token数""" - # 中文字符约1.5 token,英文约0.25 token + """Handle estimate tokens for this module for the Context Builder instance.""" + # Keep service responsibilities explicit so downstream behavior stays predictable. chinese_chars = sum(1 for c in text if '一' <= c <= '鿿') other_chars = len(text) - chinese_chars return int(chinese_chars * 1.5 + other_chars * 0.25) @@ -202,15 +170,7 @@ class ContextBuilder: self, context: RAGContext ) -> List[Dict[str, str]]: - """ - 构建LLM消息格式 - - Args: - context: RAG上下文对象 - - Returns: - List[Dict]: [{"role": "system/user/assistant", "content": "..."}] - """ + """Build messages for the Context Builder instance.""" messages = [ {"role": "system", "content": context.system_prompt}, {"role": "user", "content": f"参考以下法规条款回答问题。\n\n{context.context_text}\n\n问题:{context.user_query}"} @@ -224,6 +184,6 @@ def build_rag_context( documents: List[RetrievedDocument], **kwargs ) -> RAGContext: - """便捷函数:构建RAG上下文""" + """Build rag context.""" builder = ContextBuilder() return builder.build(query, documents, **kwargs) diff --git a/backend/app/services/rag/prompt_templates.py b/backend/app/services/rag/prompt_templates.py index 59c4380..ced6399 100644 --- a/backend/app/services/rag/prompt_templates.py +++ b/backend/app/services/rag/prompt_templates.py @@ -1,12 +1,14 @@ -"""RAG Prompt模板 - 合规问答专用Prompt""" +"""Provide service-layer logic for prompt templates.""" from typing import Dict, Optional from dataclasses import dataclass +# Keep service responsibilities explicit so downstream behavior stays predictable. + @dataclass class PromptTemplate: - """Prompt模板""" + """Represent the Prompt Template type.""" name: str system_prompt: str user_template: str @@ -14,18 +16,9 @@ class PromptTemplate: class PromptTemplates: - """ - 合规问答Prompt模板库 + """Represent the Prompt Templates type.""" - 包含多种场景的Prompt模板: - - 合规问答(标准) - - 条款解读(详细解释) - - 合规检查(判断合规状态) - - 差异对比(新旧法规对比) - - 报告生成(合规报告) - """ - - # 合规问答标准模板 + # Keep service responsibilities explicit so downstream behavior stays predictable. COMPLIANCE_QA = PromptTemplate( name="compliance_qa", system_prompt="""你是合规专家助手,专门解答法规合规问题。 @@ -63,7 +56,7 @@ class PromptTemplates: description="标准合规问答模板" ) - # 条款解读模板(详细解释) + # Keep service responsibilities explicit so downstream behavior stays predictable. CLAUSE_INTERPRETATION = PromptTemplate( name="clause_interpretation", system_prompt="""你是法规解读专家,负责详细解释法规条款的含义和应用。 @@ -96,7 +89,7 @@ class PromptTemplates: description="条款详细解读模板" ) - # 合规检查模板(判断合规状态) + # Keep service responsibilities explicit so downstream behavior stays predictable. COMPLIANCE_CHECK = PromptTemplate( name="compliance_check", system_prompt="""你是合规检查专家,负责评估企业行为或产品的合规状态。 @@ -140,7 +133,7 @@ class PromptTemplates: description="合规检查评估模板" ) - # 差异对比模板(新旧法规对比) + # Keep service responsibilities explicit so downstream behavior stays predictable. COMPARISON = PromptTemplate( name="comparison", system_prompt="""你是法规变更分析专家,负责对比新旧法规版本的差异。 @@ -192,7 +185,7 @@ class PromptTemplates: description="法规版本对比模板" ) - # 报告生成模板 + # Keep service responsibilities explicit so downstream behavior stays predictable. REPORT_GENERATION = PromptTemplate( name="report_generation", system_prompt="""你是合规报告撰写专家,负责生成结构化的合规分析报告。 @@ -222,7 +215,7 @@ class PromptTemplates: description="合规报告生成模板" ) - # 文档摘要生成模板 + # Keep service responsibilities explicit so downstream behavior stays predictable. DOCUMENT_SUMMARY = PromptTemplate( name="document_summary", system_prompt="""你是法规文档摘要专家,负责生成法规文档的核心要点摘要。 @@ -263,7 +256,7 @@ class PromptTemplates: @classmethod def get_template(cls, name: str) -> Optional[PromptTemplate]: - """获取指定模板""" + """Return template for the Prompt Templates instance.""" templates = { "compliance_qa": cls.COMPLIANCE_QA, "clause_interpretation": cls.CLAUSE_INTERPRETATION, @@ -276,7 +269,7 @@ class PromptTemplates: @classmethod def list_templates(cls) -> Dict[str, str]: - """列出所有模板""" + """List templates for the Prompt Templates instance.""" return { "compliance_qa": cls.COMPLIANCE_QA.description, "clause_interpretation": cls.CLAUSE_INTERPRETATION.description, @@ -288,7 +281,7 @@ class PromptTemplates: def get_prompt_template(name: str) -> PromptTemplate: - """便捷函数:获取Prompt模板""" + """Return prompt template.""" template = PromptTemplates.get_template(name) if not template: raise ValueError(f"不存在的模板: {name}") diff --git a/backend/app/services/rag/retriever.py b/backend/app/services/rag/retriever.py index 7051a86..b75d980 100644 --- a/backend/app/services/rag/retriever.py +++ b/backend/app/services/rag/retriever.py @@ -1,192 +1,82 @@ -"""RAG检索服务 - 封装Milvus检索""" +"""Provide service-layer logic for retriever.""" + +from __future__ import annotations -from typing import List, Dict, Optional, Any from dataclasses import dataclass, field -from loguru import logger +from typing import Any, Optional + +from app.shared.bootstrap import get_retrieval_service +# Keep service responsibilities explicit so downstream behavior stays predictable. -from app.services.embedding.bge_m3_embedder import BGEM3Embedder -from app.services.storage.milvus_client import MilvusClient, SearchResult -from app.config.settings import settings @dataclass class RetrievedDocument: - """检索到的文档""" + """Represent the Retrieved Document type.""" content: str - doc_id: str # 文档ID,用于下载 + doc_id: str doc_name: str section_title: str clause_number: str page_number: int score: float - metadata: Dict[str, Any] = field(default_factory=dict) + metadata: dict[str, Any] = field(default_factory=dict) class Retriever: - """ - RAG检索器 - - 功能: - - 向量检索(Dense + Sparse混合) - - 重排序(可选) - - 过滤和筛选 - """ - - def __init__( - self, - top_k: int = None, - rerank: bool = False, - min_score: float = 0.3 - ): - """ - 初始化检索器 - - Args: - top_k: 检索召回数量 - rerank: 是否启用重排序 - min_score: 最低相关性分数阈值 - """ - self.top_k = top_k or settings.rag_top_k + """Provide the Retriever retriever.""" + def __init__(self, top_k: int = 5, rerank: bool = False, min_score: float = 0.0): + """Initialize the Retriever instance.""" + self.top_k = top_k self.rerank = rerank self.min_score = min_score - # 嵌入模型(延迟加载) - self.embedder: Optional[BGEM3Embedder] = None - - # Milvus客户端(延迟连接) - self.milvus: Optional[MilvusClient] = None - - logger.info(f"检索器初始化: top_k={self.top_k}, rerank={self.rerank}") - - def _init_embedder(self): - """延迟初始化嵌入模型""" - if self.embedder is None: - logger.info("加载嵌入模型...") - self.embedder = BGEM3Embedder(model_name=settings.embedding_model) - - def _init_milvus(self): - """延迟初始化Milvus""" - if self.milvus is None: - logger.info("连接Milvus...") - self.milvus = MilvusClient() - self.milvus.connect() - self.milvus.create_collection(recreate=False) - self.milvus.load_collection() - - def retrieve( - self, - query: str, - filters: Optional[str] = None, - top_k: Optional[int] = None - ) -> List[RetrievedDocument]: - """ - 检索相关文档 - - Args: - query: 查询文本 - filters: 过滤条件(如 "regulation_type=='车辆安全'") - top_k: 返回数量(可选,覆盖默认值) - - Returns: - List[RetrievedDocument]: 检索结果列表 - """ - logger.info(f"执行检索: {query}") - - # 初始化组件 - self._init_embedder() - self._init_milvus() - - # 生成查询向量 - query_embedding = self.embedder.embed_single(query) - - # 执行混合检索 - results = self.milvus.hybrid_search( - query_dense=query_embedding['dense'].tolist(), - query_sparse=query_embedding['sparse'], - top_k=top_k or self.top_k, - filters=filters - ) - - # 转换为RetrievedDocument格式 - documents = [] - for r in results: - if r.score >= self.min_score: - doc = RetrievedDocument( - content=r.content, - doc_id=r.metadata.get("doc_id", ""), - doc_name=r.metadata.get("doc_name", ""), - section_title=r.metadata.get("section_title", ""), - clause_number=r.metadata.get("clause_number", ""), - page_number=r.metadata.get("page_number", 0), - score=r.score, - metadata=r.metadata - ) - documents.append(doc) - - logger.success(f"检索完成,返回{len(documents)}条结果(阈值过滤后)") - return documents - - def retrieve_with_scores( - self, - query: str, - filters: Optional[str] = None - ) -> List[Dict]: - """ - 检索并返回完整结果(包含分数) - - Args: - query: 查询文本 - filters: 过滤条件 - - Returns: - List[Dict]: 包含分数的检索结果 - """ - documents = self.retrieve(query, filters) + def retrieve(self, query: str, filters: Optional[str] = None, top_k: Optional[int] = None) -> list[RetrievedDocument]: + """Handle retrieve for the Retriever instance.""" + results = get_retrieval_service().retrieve(query=query, top_k=top_k or self.top_k, filters=filters) return [ - { - "content": doc.content, - "doc_id": doc.doc_id, - "doc_name": doc.doc_name, - "section_title": doc.section_title, - "clause_number": doc.clause_number, - "page_number": doc.page_number, - "score": doc.score - } - for doc in documents + RetrievedDocument( + content=item.content, + doc_id=item.doc_id, + doc_name=item.doc_name, + section_title=item.section_title, + clause_number=item.metadata.get("clause_number", ""), + page_number=item.page_number, + score=item.score, + metadata=item.metadata, + ) + for item in results + if item.score >= self.min_score ] - def search_by_doc_name( - self, - query: str, - doc_name: str - ) -> List[RetrievedDocument]: - """按文档名称过滤检索""" - filters = f'doc_name=="{doc_name}"' - return self.retrieve(query, filters) + def retrieve_with_scores(self, query: str, filters: Optional[str] = None) -> list[dict]: + """Handle retrieve with scores for the Retriever instance.""" + return [ + { + "content": item.content, + "doc_id": item.doc_id, + "doc_name": item.doc_name, + "section_title": item.section_title, + "clause_number": item.clause_number, + "page_number": item.page_number, + "score": item.score, + } + for item in self.retrieve(query, filters) + ] - def search_by_regulation_type( - self, - query: str, - regulation_type: str - ) -> List[RetrievedDocument]: - """按法规类型过滤检索""" - filters = f'regulation_type=="{regulation_type}"' - return self.retrieve(query, filters) + def search_by_doc_name(self, query: str, doc_name: str) -> list[RetrievedDocument]: + """Search by doc name for the Retriever instance.""" + return self.retrieve(query, filters=f'doc_name == "{doc_name}"') + + def search_by_regulation_type(self, query: str, regulation_type: str) -> list[RetrievedDocument]: + """Search by regulation type for the Retriever instance.""" + return self.retrieve(query, filters=f'regulation_type == "{regulation_type}"') def close(self): - """关闭连接""" - if self.milvus: - self.milvus.disconnect() - logger.info("检索器已关闭") + """Release the resources held by this component.""" + return None -def retrieve_regulations( - query: str, - top_k: int = 10, - filters: Optional[str] = None -) -> List[RetrievedDocument]: - """便捷函数:检索法规""" - retriever = Retriever(top_k=top_k) - results = retriever.retrieve(query, filters) - retriever.close() - return results +def retrieve_regulations(query: str, top_k: int = 10, filters: Optional[str] = None) -> list[RetrievedDocument]: + """Handle retrieve regulations.""" + return Retriever(top_k=top_k).retrieve(query, filters) diff --git a/backend/app/services/storage/__init__.py b/backend/app/services/storage/__init__.py index ee09ee8..dfa7421 100644 --- a/backend/app/services/storage/__init__.py +++ b/backend/app/services/storage/__init__.py @@ -1,6 +1,18 @@ -"""存储服务""" +"""Initialize the app.services.storage package.""" +# Keep package boundaries explicit so backend imports stay predictable. -from .milvus_client import MilvusClient -from .minio_client import MinIOClient __all__ = ["MilvusClient", "MinIOClient"] + + +def __getattr__(name: str): + """Handle getattr for this module.""" + if name == "MilvusClient": + from .milvus_client import MilvusClient + + return MilvusClient + if name == "MinIOClient": + from .minio_client import MinIOClient + + return MinIOClient + raise AttributeError(name) diff --git a/backend/app/services/storage/milvus_client.py b/backend/app/services/storage/milvus_client.py index 9871843..46fee08 100644 --- a/backend/app/services/storage/milvus_client.py +++ b/backend/app/services/storage/milvus_client.py @@ -1,4 +1,4 @@ -"""Milvus向量数据库客户端 - 存储与检索服务""" +"""Provide service-layer logic for milvus client.""" from pymilvus import ( connections, @@ -17,11 +17,13 @@ import numpy as np from ..embedding.text_chunker import TextChunk from ..embedding.bge_m3_embedder import EmbeddingResult from app.config.settings import settings +# Keep service responsibilities explicit so downstream behavior stays predictable. + @dataclass class SearchResult: - """检索结果""" + """Represent the Search Result type.""" id: int content: str score: float @@ -30,7 +32,7 @@ class SearchResult: @dataclass class MilvusDocument: - """Milvus文档数据结构""" + """Represent the Milvus Document type.""" doc_id: str chunk_id: str content: str @@ -46,7 +48,7 @@ class MilvusDocument: class MilvusClient: - """Milvus向量数据库客户端""" + """Represent the Milvus Client type.""" COLLECTION_NAME = "regulations" @@ -73,6 +75,7 @@ class MilvusClient: collection_name: str = None, db_name: str = None ): + """Initialize the Milvus Client instance.""" self.host = host or settings.milvus_host self.port = port or settings.milvus_port self.collection_name = collection_name or settings.milvus_collection @@ -84,7 +87,7 @@ class MilvusClient: logger.info(f"Milvus客户端配置: {self.host}:{self.port}, Collection: {self.collection_name}") def connect(self) -> bool: - """连接到Milvus服务器""" + """Handle connect for the Milvus Client instance.""" try: connections.connect( alias="default", @@ -101,7 +104,7 @@ class MilvusClient: return False def disconnect(self): - """断开连接""" + """Handle disconnect for the Milvus Client instance.""" try: connections.disconnect("default") self.connected = False @@ -110,7 +113,7 @@ class MilvusClient: logger.warning(f"断开连接时出错: {e}") def create_collection(self, recreate: bool = False) -> bool: - """创建Collection""" + """Create collection for the Milvus Client instance.""" if not self.connected: logger.warning("未连接到Milvus,请先调用connect()") return False @@ -146,7 +149,7 @@ class MilvusClient: return False def _create_indexes(self): - """创建向量索引""" + """Handle create indexes for this module for the Milvus Client instance.""" if not self.collection: return @@ -177,13 +180,13 @@ class MilvusClient: logger.warning(f"创建索引时出错: {e}") def load_collection(self): - """加载Collection到内存""" + """Load collection for the Milvus Client instance.""" if self.collection: self.collection.load() logger.info(f"Collection已加载: {self.collection_name}") def release_collection(self): - """释放Collection内存""" + """Handle release collection for the Milvus Client instance.""" if self.collection: self.collection.release() logger.info(f"Collection已释放: {self.collection_name}") @@ -193,7 +196,7 @@ class MilvusClient: chunks: List[TextChunk], embeddings: EmbeddingResult ) -> List[int]: - """插入文档分块和嵌入向量""" + """Handle insert chunks for the Milvus Client instance.""" if not self.collection: logger.warning("Collection未初始化") return [] @@ -246,7 +249,7 @@ class MilvusClient: top_k: int = 10, filters: Optional[str] = None ) -> List[SearchResult]: - """混合检索:Dense + Sparse""" + """Handle hybrid search for the Milvus Client instance.""" if not self.collection: logger.warning("Collection未初始化") return [] @@ -254,10 +257,10 @@ class MilvusClient: try: self.collection.load() - # 使用简单的Dense检索(兼容所有版本) + # Keep service responsibilities explicit so downstream behavior stays predictable. dense_results = self.dense_search(query_dense, top_k, filters) - # 可选:合并Sparse结果 + # Keep service responsibilities explicit so downstream behavior stays predictable. if query_sparse: sparse_results = self.sparse_search(query_sparse, top_k, filters) merged = self._merge_results(dense_results, sparse_results, top_k) @@ -277,7 +280,7 @@ class MilvusClient: top_k: int, dense_weight: float = 0.6 ) -> List[SearchResult]: - """手动融合Dense和Sparse结果""" + """Handle merge results for this module for the Milvus Client instance.""" sparse_weight = 1 - dense_weight merged_dict = {} @@ -318,7 +321,7 @@ class MilvusClient: top_k: int = 10, filters: Optional[str] = None ) -> List[SearchResult]: - """纯Dense向量检索""" + """Handle dense search for the Milvus Client instance.""" if not self.collection: return [] @@ -375,7 +378,7 @@ class MilvusClient: top_k: int = 10, filters: Optional[str] = None ) -> List[SearchResult]: - """纯Sparse向量检索""" + """Handle sparse search for the Milvus Client instance.""" if not self.collection: return [] @@ -427,7 +430,7 @@ class MilvusClient: return [] def delete_by_doc_id(self, doc_id: str) -> int: - """根据doc_id删除记录""" + """Delete by doc id for the Milvus Client instance.""" if not self.collection: return 0 @@ -441,7 +444,7 @@ class MilvusClient: return 0 def get_collection_stats(self) -> Dict[str, Any]: - """获取Collection统计信息""" + """Return collection stats for the Milvus Client instance.""" if not self.collection: return {} @@ -458,7 +461,7 @@ class MilvusClient: def create_milvus_client() -> MilvusClient: - """便捷函数:创建Milvus客户端""" + """Create milvus client.""" client = MilvusClient() client.connect() client.create_collection(recreate=False) @@ -470,7 +473,7 @@ def insert_documents( chunks: List[TextChunk], embeddings: EmbeddingResult ) -> List[int]: - """便捷函数:插入文档""" + """Handle insert documents.""" return client.insert_chunks(chunks, embeddings) @@ -480,5 +483,5 @@ def search_regulations( query_sparse: Dict[int, float], top_k: int = 10 ) -> List[SearchResult]: - """便捷函数:检索法规""" + """Search regulations.""" return client.hybrid_search(query_dense, query_sparse, top_k) diff --git a/backend/app/services/storage/minio_client.py b/backend/app/services/storage/minio_client.py index 84b6206..fbfe463 100644 --- a/backend/app/services/storage/minio_client.py +++ b/backend/app/services/storage/minio_client.py @@ -1,4 +1,4 @@ -"""MinIO对象存储客户端 - 文档文件存储""" +"""Provide service-layer logic for minio client.""" from minio import Minio from minio.error import S3Error @@ -8,10 +8,12 @@ from io import BytesIO import os from app.config.settings import settings +# Keep service responsibilities explicit so downstream behavior stays predictable. + class MinIOClient: - """MinIO对象存储客户端""" + """Represent the Min I O Client type.""" def __init__( self, @@ -21,16 +23,7 @@ class MinIOClient: bucket: str = None, secure: bool = None ): - """ - 初始化MinIO客户端 - - Args: - endpoint: MinIO服务地址 - access_key: 访问密钥 - secret_key: 秘密密钥 - bucket: 存储桶名称 - secure: 是否使用HTTPS - """ + """Initialize the Min I O Client instance.""" self.endpoint = endpoint or settings.minio_endpoint self.access_key = access_key or settings.minio_access_key self.secret_key = secret_key or settings.minio_secret_key @@ -43,7 +36,7 @@ class MinIOClient: logger.info(f"MinIO客户端配置: {self.endpoint}, bucket={self.bucket}") def connect(self) -> bool: - """连接MinIO服务""" + """Handle connect for the Min I O Client instance.""" try: self.client = Minio( self.endpoint, @@ -60,7 +53,7 @@ class MinIOClient: return False def ensure_bucket(self) -> bool: - """确保存储桶存在""" + """Handle ensure bucket for the Min I O Client instance.""" if not self.connected: logger.warning("未连接MinIO,请先调用connect()") return False @@ -82,17 +75,7 @@ class MinIOClient: object_name: str, metadata: Dict[str, Any] = None ) -> bool: - """ - 上传本地文件到MinIO - - Args: - file_path: 本地文件路径 - object_name: MinIO对象名称 - metadata: 元数据 - - Returns: - bool: 是否成功 - """ + """Handle upload file for the Min I O Client instance.""" if not self.connected: self.connect() self.ensure_bucket() @@ -125,18 +108,7 @@ class MinIOClient: content_type: str = "application/octet-stream", metadata: Dict[str, Any] = None ) -> bool: - """ - 上传字节数据到MinIO - - Args: - data: 文件字节数据 - object_name: MinIO对象名称 - content_type: 内容类型 - metadata: 元数据(注意:MinIO仅支持US-ASCII字符) - - Returns: - bool: 是否成功 - """ + """Handle upload bytes for the Min I O Client instance.""" if not self.connected: self.connect() self.ensure_bucket() @@ -144,18 +116,18 @@ class MinIOClient: try: data_stream = BytesIO(data) - # 处理metadata:仅保留ASCII安全字符 + # Keep service responsibilities explicit so downstream behavior stays predictable. safe_metadata = None if metadata: safe_metadata = {} for key, value in metadata.items(): if isinstance(value, str): - # 只保留ASCII字符或转换为安全格式 + # Keep service responsibilities explicit so downstream behavior stays predictable. try: value.encode('ascii') safe_metadata[key] = value except UnicodeEncodeError: - # 中文字符跳过或用占位符 + # Keep service responsibilities explicit so downstream behavior stays predictable. safe_metadata[key] = "" else: safe_metadata[key] = str(value) @@ -181,16 +153,7 @@ class MinIOClient: object_name: str, file_path: str ) -> bool: - """ - 从MinIO下载文件到本地 - - Args: - object_name: MinIO对象名称 - file_path: 本地保存路径 - - Returns: - bool: 是否成功 - """ + """Handle download file for the Min I O Client instance.""" if not self.connected: self.connect() @@ -212,16 +175,7 @@ class MinIOClient: object_name: str, expires: int = 3600 ) -> Optional[str]: - """ - 获取对象下载URL(临时URL) - - Args: - object_name: MinIO对象名称 - expires: URL有效期(秒) - - Returns: - str: 下载URL - """ + """Return object url for the Min I O Client instance.""" if not self.connected: self.connect() @@ -238,15 +192,7 @@ class MinIOClient: return None def get_object_data(self, object_name: str) -> Optional[bytes]: - """ - 获取对象数据(字节) - - Args: - object_name: MinIO对象名称 - - Returns: - bytes: 文件数据 - """ + """Return object data for the Min I O Client instance.""" if not self.connected: self.connect() @@ -262,15 +208,7 @@ class MinIOClient: return None def delete_object(self, object_name: str) -> bool: - """ - 删除对象 - - Args: - object_name: MinIO对象名称 - - Returns: - bool: 是否成功 - """ + """Delete object for the Min I O Client instance.""" if not self.connected: self.connect() @@ -284,15 +222,7 @@ class MinIOClient: return False def list_objects(self, prefix: str = "") -> list: - """ - 列出存储桶中的对象 - - Args: - prefix: 对象名称前缀 - - Returns: - list: 对象列表 - """ + """List objects for the Min I O Client instance.""" if not self.connected: self.connect() @@ -305,15 +235,7 @@ class MinIOClient: return [] def object_exists(self, object_name: str) -> bool: - """ - 检查对象是否存在 - - Args: - object_name: MinIO对象名称 - - Returns: - bool: 是否存在 - """ + """Handle object exists for the Min I O Client instance.""" if not self.connected: self.connect() @@ -325,7 +247,7 @@ class MinIOClient: return False def _get_content_type(self, file_path: str) -> str: - """根据文件扩展名获取Content-Type""" + """Handle get content type for this module for the Min I O Client instance.""" ext = os.path.splitext(file_path)[1].lower() content_types = { '.pdf': 'application/pdf', @@ -338,13 +260,13 @@ class MinIOClient: return content_types.get(ext, 'application/octet-stream') def close(self): - """关闭连接(MinIO客户端无需显式关闭)""" + """Release the resources held by this component.""" self.connected = False logger.info("MinIO客户端已关闭") def create_minio_client() -> MinIOClient: - """便捷函数:创建MinIO客户端""" + """Create minio client.""" client = MinIOClient() client.connect() client.ensure_bucket() diff --git a/backend/app/shared/__init__.py b/backend/app/shared/__init__.py new file mode 100644 index 0000000..caafe28 --- /dev/null +++ b/backend/app/shared/__init__.py @@ -0,0 +1,5 @@ +"""Initialize the app.shared package.""" +# Keep package boundaries explicit so backend imports stay predictable. + + +__all__ = [] diff --git a/backend/app/shared/bootstrap.py b/backend/app/shared/bootstrap.py new file mode 100644 index 0000000..31d74df --- /dev/null +++ b/backend/app/shared/bootstrap.py @@ -0,0 +1,117 @@ +"""Share backend wiring for bootstrap.""" + +from __future__ import annotations + +from functools import lru_cache + +from app.application.agent import AgentConversationService +from app.application.documents import DocumentCommandService, DocumentQueryService +from app.application.knowledge import KnowledgeRetrievalService +from app.config.settings import settings +from app.infrastructure.embedding.openai_compatible_embedding_provider import OpenAICompatibleEmbeddingProvider +from app.infrastructure.llm.openai_compatible_answer_generator import OpenAICompatibleAnswerGenerator +from app.infrastructure.parser.aliyun_document_parser import AliyunDocumentParser +from app.infrastructure.parser.local_chunk_builder import LocalRegulationChunkBuilder +from app.infrastructure.parser.local_document_parser import LocalDocumentParser +from app.infrastructure.parser.vector_chunk_builder import AliyunVectorChunkBuilder +from app.infrastructure.session.in_memory_conversation_store import InMemoryConversationStore +from app.infrastructure.storage.json_document_repository import JsonDocumentRepository +from app.infrastructure.storage.minio_binary_store import MinioDocumentBinaryStore +from app.infrastructure.vectorstore.dense_retriever import DenseRetriever +from app.infrastructure.vectorstore.milvus_vector_index import MilvusVectorIndex +# Keep shared wiring centralized so dependency construction remains consistent. + + + +@lru_cache +def get_document_repository() -> JsonDocumentRepository: + """Return document repository.""" + return JsonDocumentRepository(settings.document_metadata_path) + + +@lru_cache +def get_binary_store() -> MinioDocumentBinaryStore: + """Return binary store.""" + return MinioDocumentBinaryStore() + + +@lru_cache +def get_parser(): + """Return parser.""" + if settings.parser_backend == "aliyun": + return AliyunDocumentParser() + return LocalDocumentParser() + + +@lru_cache +def get_chunk_builder(): + """Return chunk builder.""" + if settings.chunk_backend == "aliyun": + return AliyunVectorChunkBuilder() + return LocalRegulationChunkBuilder( + chunk_size=settings.chunk_size, + chunk_overlap=settings.chunk_overlap, + ) + + +@lru_cache +def get_embedding_provider() -> OpenAICompatibleEmbeddingProvider: + """Return embedding provider.""" + return OpenAICompatibleEmbeddingProvider() + + +@lru_cache +def get_vector_index() -> MilvusVectorIndex: + """Return vector index.""" + return MilvusVectorIndex() + + +@lru_cache +def get_retrieval_service() -> KnowledgeRetrievalService: + """Return retrieval service.""" + retriever = DenseRetriever( + embedding_provider=get_embedding_provider(), + vector_index=get_vector_index(), + ) + return KnowledgeRetrievalService(retriever=retriever) + + +@lru_cache +def get_document_command_service() -> DocumentCommandService: + """Return document command service.""" + return DocumentCommandService( + document_repository=get_document_repository(), + binary_store=get_binary_store(), + parser=get_parser(), + chunk_builder=get_chunk_builder(), + embedding_provider=get_embedding_provider(), + vector_index=get_vector_index(), + ) + + +@lru_cache +def get_document_query_service() -> DocumentQueryService: + """Return document query service.""" + return DocumentQueryService( + document_repository=get_document_repository(), + binary_store=get_binary_store(), + ) + + +@lru_cache +def get_conversation_store() -> InMemoryConversationStore: + """Return conversation store.""" + return InMemoryConversationStore( + max_sessions=settings.session_max_sessions, + timeout_minutes=settings.session_timeout_minutes, + ) + + +@lru_cache +def get_agent_conversation_service() -> AgentConversationService: + """Return agent conversation service.""" + return AgentConversationService( + retrieval_service=get_retrieval_service(), + answer_generator=OpenAICompatibleAnswerGenerator(), + conversation_store=get_conversation_store(), + ) diff --git a/backend/app/utils/__init__.py b/backend/app/utils/__init__.py index 44884e4..eca82b4 100644 --- a/backend/app/utils/__init__.py +++ b/backend/app/utils/__init__.py @@ -1,4 +1,8 @@ +"""Initialize the app.utils package.""" + from .chunking import TextChunker, chunker from .logger import logger, setup_logging +# Keep package boundaries explicit so backend imports stay predictable. + __all__ = ["TextChunker", "chunker", "logger", "setup_logging"] \ No newline at end of file diff --git a/backend/app/utils/chunking.py b/backend/app/utils/chunking.py index 6fe1d87..b6aa217 100644 --- a/backend/app/utils/chunking.py +++ b/backend/app/utils/chunking.py @@ -1,19 +1,25 @@ +"""Provide utility helpers for chunking.""" + import re from typing import List from app.core.config import settings +# Keep module behavior explicit so the backend flow stays easy to audit. + class TextChunker: + """Represent the Text Chunker type.""" def __init__( self, chunk_size: int = settings.chunk_size, chunk_overlap: int = settings.chunk_overlap, ): + """Initialize the Text Chunker instance.""" self.chunk_size = chunk_size self.chunk_overlap = chunk_overlap def chunk_by_clause(self, text: str) -> List[dict]: - """按条款边界分块(适用于法规文档)""" + """Handle chunk by clause for the Text Chunker instance.""" clause_pattern = r"(第[一二三四五六七八九十百]+条)" parts = re.split(clause_pattern, text) @@ -46,7 +52,7 @@ class TextChunker: return chunks def chunk_by_size(self, text: str) -> List[dict]: - """按固定大小分块""" + """Handle chunk by size for the Text Chunker instance.""" chunks = [] start = 0 chunk_index = 0 @@ -69,7 +75,7 @@ class TextChunker: return chunks def estimate_tokens(self, text: str) -> int: - """估算token数量""" + """Handle estimate tokens for the Text Chunker instance.""" chinese_chars = len(re.findall(r"[^\x00-\xff]", text)) english_chars = len(text) - chinese_chars return int(chinese_chars / 1.5 + english_chars / 4) diff --git a/backend/app/utils/logger.py b/backend/app/utils/logger.py index 366ca57..e7bec22 100644 --- a/backend/app/utils/logger.py +++ b/backend/app/utils/logger.py @@ -1,9 +1,13 @@ +"""Provide utility helpers for logger.""" + import logging import sys +# Keep module behavior explicit so the backend flow stays easy to audit. + def setup_logging() -> logging.Logger: - """配置日志""" + """Handle setup logging.""" logger = logging.getLogger("app") logger.setLevel(logging.INFO) diff --git a/backend/app/workers/__init__.py b/backend/app/workers/__init__.py index d7fcadb..a39770f 100644 --- a/backend/app/workers/__init__.py +++ b/backend/app/workers/__init__.py @@ -1 +1,3 @@ -"""异步任务Worker模块""" +"""Initialize the app.workers package.""" +# Keep package boundaries explicit so backend imports stay predictable. + diff --git a/backend/app/workflows/__init__.py b/backend/app/workflows/__init__.py index 107f16c..5ffa547 100644 --- a/backend/app/workflows/__init__.py +++ b/backend/app/workflows/__init__.py @@ -1,5 +1,9 @@ +"""Initialize the app.workflows package.""" + from .rag_workflow import RagState, rag_workflow, run_rag_workflow, stream_rag_workflow from .compliance_workflow import ComplianceState, compliance_workflow, run_compliance_workflow +# Keep package boundaries explicit so backend imports stay predictable. + __all__ = [ "RagState", diff --git a/backend/app/workflows/compliance_workflow.py b/backend/app/workflows/compliance_workflow.py index 5cafc4a..cfea1d9 100644 --- a/backend/app/workflows/compliance_workflow.py +++ b/backend/app/workflows/compliance_workflow.py @@ -1,8 +1,13 @@ +"""Define workflow state for compliance workflow.""" + from typing import TypedDict, List from langgraph.graph import StateGraph, END +# Keep workflow state definitions compact so transitions stay easy to audit. + class ComplianceState(TypedDict): + """Track workflow state for compliance state.""" document_path: str raw_text: str segments: List[dict] @@ -12,7 +17,7 @@ class ComplianceState(TypedDict): def parse_document(state: ComplianceState) -> dict: - """解析文档""" + """Parse document.""" from app.services import get_document_service doc_service = get_document_service( "/airegulation/demo-mao/backend/data/raw", @@ -23,7 +28,7 @@ def parse_document(state: ComplianceState) -> dict: def segment_document(state: ComplianceState) -> dict: - """AI语义分段""" + """Handle segment document.""" from app.services import llm_service prompt = f"""请分析以下设计方案文档,按照设计意图将其分成若干语义段落。 @@ -39,7 +44,7 @@ def segment_document(state: ComplianceState) -> dict: 输出格式: [{{"intent": "...", "startPos": 0, "endPos": 100, "keywords": [...]}}]""" - # 简化处理:返回基本分段 + # Keep workflow state definitions compact so transitions stay easy to audit. segments = [ { "id": 1, @@ -53,7 +58,7 @@ def segment_document(state: ComplianceState) -> dict: def match_regulations(state: ComplianceState) -> dict: - """法规匹配""" + """Handle match regulations.""" from app.services import embedding_service, milvus_service matched = [] @@ -83,7 +88,7 @@ def match_regulations(state: ComplianceState) -> dict: def calculate_risk(state: ComplianceState) -> dict: - """计算风险等级""" + """Handle calculate risk.""" segments = state["matched_regulations"] high_count = 0 @@ -133,7 +138,7 @@ def calculate_risk(state: ComplianceState) -> dict: def generate_suggestions(state: ComplianceState) -> dict: - """生成优先建议""" + """Handle generate suggestions.""" actions = [] for segment in state["segments"]: @@ -149,7 +154,7 @@ def generate_suggestions(state: ComplianceState) -> dict: return {"priority_actions": actions} -# 构建工作流图 +# Keep workflow state definitions compact so transitions stay easy to audit. compliance_graph = StateGraph(ComplianceState) compliance_graph.add_node("parse", parse_document) @@ -169,7 +174,7 @@ compliance_workflow = compliance_graph.compile() async def run_compliance_workflow(document_path: str) -> ComplianceState: - """运行合规分析工作流""" + """Handle run compliance workflow.""" initial_state: ComplianceState = {"document_path": document_path} result = compliance_workflow.invoke(initial_state) return result \ No newline at end of file diff --git a/backend/app/workflows/rag_workflow.py b/backend/app/workflows/rag_workflow.py index 370d2ce..ac9eb48 100644 --- a/backend/app/workflows/rag_workflow.py +++ b/backend/app/workflows/rag_workflow.py @@ -1,8 +1,13 @@ +"""Define workflow state for rag workflow.""" + from typing import TypedDict, List from langgraph.graph import StateGraph, END +# Keep workflow state definitions compact so transitions stay easy to audit. + class RagState(TypedDict): + """Track workflow state for rag state.""" query: str query_embedding: List[float] retrieved_docs: List[dict] @@ -12,14 +17,14 @@ class RagState(TypedDict): def embed_query(state: RagState) -> dict: - """将查询转为向量""" + """Embed query.""" from app.services import embedding_service embedding = embedding_service.embed_single(state["query"]) return {"query_embedding": embedding} def retrieve_docs(state: RagState) -> dict: - """向量检索""" + """Handle retrieve docs.""" from app.services import milvus_service from app.core.config import settings docs = milvus_service.search( @@ -30,7 +35,7 @@ def retrieve_docs(state: RagState) -> dict: def build_context(state: RagState) -> dict: - """构建上下文""" + """Build context.""" context_parts = [] sources = [] @@ -46,7 +51,7 @@ def build_context(state: RagState) -> dict: def generate_answer(state: RagState) -> dict: - """生成答案""" + """Handle generate answer.""" from app.services import llm_service prompt = f"""请根据以下法规内容回答用户问题,并在回答中标注引用的法规条款。 @@ -64,7 +69,7 @@ def generate_answer(state: RagState) -> dict: return {"answer": answer} -# 构建工作流图 +# Keep workflow state definitions compact so transitions stay easy to audit. rag_graph = StateGraph(RagState) rag_graph.add_node("embed", embed_query) @@ -82,23 +87,23 @@ rag_workflow = rag_graph.compile() async def run_rag_workflow(query: str) -> RagState: - """运行RAG工作流""" + """Handle run rag workflow.""" initial_state: RagState = {"query": query} result = rag_workflow.invoke(initial_state) return result def stream_rag_workflow(query: str): - """流式运行RAG工作流""" + """Stream rag workflow.""" from app.services import llm_service - # 先完成检索阶段 + # Keep workflow state definitions compact so transitions stay easy to audit. state: RagState = {"query": query} state.update(embed_query(state)) state.update(retrieve_docs(state)) state.update(build_context(state)) - # 流式生成阶段 + # Keep workflow state definitions compact so transitions stay easy to audit. prompt = f"""请根据以下法规内容回答用户问题,并在回答中标注引用的法规条款。 法规内容: diff --git a/backend/backend/data/documents.json b/backend/backend/data/documents.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/backend/backend/data/documents.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/backend/data/documents.json b/backend/data/documents.json new file mode 100644 index 0000000..0efab07 --- /dev/null +++ b/backend/data/documents.json @@ -0,0 +1,25 @@ +{ + "69280841": { + "doc_id": "69280841", + "doc_name": "TCT算法接口.pdf", + "file_name": "TCT算法接口.pdf", + "object_name": "69280841/TCT算法接口.pdf", + "content_type": "application/pdf", + "size_bytes": 165557, + "status": "failed", + "regulation_type": "", + "version": "", + "summary": "", + "summary_latency_ms": 0, + "chunk_count": 0, + "parser_name": "local_markdown_parser", + "index_name": "", + "error_message": "embedding 维度不匹配,期望 1536", + "created_at": "2026-05-18T07:12:16.668306+00:00", + "updated_at": "2026-05-18T07:12:19.417142+00:00", + "metadata": { + "generate_summary": true, + "structure_nodes": 0 + } + } +} \ No newline at end of file diff --git a/backend/main.py b/backend/main.py index 79222b5..780f9a3 100644 --- a/backend/main.py +++ b/backend/main.py @@ -3,9 +3,12 @@ import uvicorn from app.config.settings import settings +# Keep module behavior explicit so the backend flow stays easy to audit. + def main() -> None: + """Run the module entrypoint.""" uvicorn.run( "app.main:app", host=settings.api_host, diff --git a/backend/requirements.txt b/backend/requirements.txt index 7171820..0a5f829 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -17,10 +17,10 @@ minio>=7.1.0 pymupdf>=1.24.0 python-docx>=1.1.0 -FlagEmbedding>=1.2.0 -sentence-transformers>=2.2.0 -torch>=2.0.0 numpy>=1.24.0 +alibabacloud-docmind-api20220711>=1.0.6 +alibabacloud-tea-openapi>=0.3.11 +alibabacloud-tea-util>=0.3.13 langchain>=0.1.0 langchain-milvus>=0.1.0 diff --git a/backend/tests/__init__.py b/backend/tests/__init__.py index e69de29..b5de743 100644 --- a/backend/tests/__init__.py +++ b/backend/tests/__init__.py @@ -0,0 +1,4 @@ +"""Initialize the tests package.""" +# Keep package boundaries explicit so backend imports stay predictable. + + diff --git a/dev.bat b/dev.bat index 737bccf..ca40f32 100644 --- a/dev.bat +++ b/dev.bat @@ -605,12 +605,14 @@ echo Python was not found. exit /b 1 :load_env -for %%K in (API_HOST API_PORT FRONTEND_PORT FRONTEND_MODE) do call :read_env %%K -exit /b 0 + for %%K in (API_HOST API_PORT FRONTEND_PORT FRONTEND_MODE) do call :read_env %%K .env + for %%K in (API_HOST API_PORT FRONTEND_PORT FRONTEND_MODE) do call :read_env %%K .env.development + exit /b 0 :read_env -if not exist ".env" exit /b 0 -for /f "usebackq tokens=1,* delims==" %%A in (`findstr /r /b /c:"%~1=" ".env" 2^>nul`) do set "%~1=%%B" +if "%~2"=="" exit /b 0 +if not exist "%~2" exit /b 0 +for /f "usebackq tokens=1,* delims==" %%A in (`findstr /r /b /c:"%~1=" "%~2" 2^>nul`) do set "%~1=%%B" exit /b 0 :pid_exists diff --git a/dev.sh b/dev.sh index 406d413..dea6c1a 100644 --- a/dev.sh +++ b/dev.sh @@ -21,7 +21,19 @@ CYAN='\033[0;36m' NC='\033[0m' load_env() { - if [ -f ".env" ]; then + load_env_file ".env" + load_env_file ".env.development" + + API_HOST="${API_HOST:-0.0.0.0}" + API_PORT="${API_PORT:-8000}" + FRONTEND_PORT="${FRONTEND_PORT:-5173}" + FRONTEND_MODE="${FRONTEND_MODE:-dev}" +} + +load_env_file() { + local env_file="$1" + + if [ -f "$env_file" ]; then while IFS='=' read -r key value; do key="${key%$'\r'}" value="${value%$'\r'}" @@ -34,13 +46,8 @@ load_env() { export "$key=$value" ;; esac - done < ".env" + done < "$env_file" fi - - API_HOST="${API_HOST:-0.0.0.0}" - API_PORT="${API_PORT:-8000}" - FRONTEND_PORT="${FRONTEND_PORT:-5173}" - FRONTEND_MODE="${FRONTEND_MODE:-dev}" } ensure_log_dir() { diff --git a/docs/architecture/backend-project-architecture.md b/docs/architecture/backend-project-architecture.md index 0b5d8e0..6a0d9a2 100644 --- a/docs/architecture/backend-project-architecture.md +++ b/docs/architecture/backend-project-architecture.md @@ -24,7 +24,13 @@ ### 2.1 `DocumentProcessor` 责任过载 -`backend/app/services/document_processor.py` 当前同时承担: +现状判断: + +- `backend/app/services/document_processor.py` 已经不再是当前主要编排点 +- 上传、查询和问答的核心组织逻辑已经明显下沉到 `backend/app/application/*` 以及 `backend/app/shared/bootstrap.py` +- `DocumentProcessor` 当前更接近一个兼容旧调用路径的 legacy façade + +其历史上曾集中承载过以下职责: - 文档解析 - 摘要生成 @@ -33,22 +39,34 @@ - Milvus 入库 - 检索入口 -这使上传处理链路、检索链路与基础设施初始化逻辑耦合在一个大类中。流程编排与具体实现没有边界,后续无论替换 parser、embedding、vector store 还是增加文档状态管理,都会直接影响同一个类。 +当前真正还需要收口的问题不是“继续拆这个大类”,而是把文档元数据、解析、分块、向量化和索引写入的稳定边界进一步收束到 application service 与端口接口中,避免后续重构继续围绕旧实现形态做判断。 ### 2.2 检索逻辑缺少稳定边界 -`backend/app/services/rag/retriever.py` 当前同时管理: +现状判断: + +- `backend/app/services/rag/retriever.py` 已经明显退化为兼容层 +- 真正承担检索编排的是 `KnowledgeRetrievalService` 和 `shared/bootstrap` 中的 wiring +- `Retriever` 应被视为业务侧端口,而不是当前主编排单元 + +其历史上曾集中承载过以下职责: - embedder 初始化 - Milvus 连接与 collection lifecycle - 检索执行 - 结果映射 -这意味着“检索能力”不是一个稳定的业务能力接口,而是一个直接依赖具体 embedding 和 Milvus 实现的复合服务。后续如果从 `BGE-M3 + hybrid search` 切到 `1536 dense-only` 或替换向量索引实现,会直接影响检索服务本身。 +所以这里的重点不是把旧 retriever 当成未来架构核心,而是确认检索能力的稳定边界已经迁移到 application 层,并继续清理仍然残留的 provider-specific 细节。 ### 2.3 `QAAgent` 责任过载 -`backend/app/services/agent/qa_agent.py` 当前同时承担: +现状判断: + +- `backend/app/services/agent/qa_agent.py` 已经不是当前主要问答编排点 +- 当前实际的问答编排已经主要落到 `AgentConversationService` +- `QAAgent` 当前更接近向后兼容的 legacy façade + +其历史上曾集中承载过以下职责: - 检索调用 - 上下文构建 @@ -57,7 +75,7 @@ - SSE 流式问答流程 - 会话 workflow 编排 -这导致 Agent workflow 与检索底座、LLM provider、上下文构造逻辑紧耦合。后续切换 LLM provider、替换 session store、复用 retrieval 能力时,影响面会扩散到整个 Agent 实现。 +剩余风险不在于“它还很大”,而在于仍有少量 API 入口和 session 读写没有完全收口到 application service,导致目标边界和现状之间还存在断点。 ### 2.4 API 层直接编排具体服务 @@ -73,6 +91,10 @@ - 路由层知道过多内部实现细节 - 后续如果内部模块调整,路由层也要跟着改 +现状里这条判断需要修正:大部分主流程已经通过 `backend/app/shared/bootstrap.py` 统一装配,并由 application service 承担编排;真正还没有完全收口的是 `backend/app/api/routes/agent.py` 中的 session/history/feedback 这类接口,它们仍直接访问 `ConversationStore`。 + +因此,当前问题不是“路由层仍然是主编排中心”,而是“少数会话管理接口还没有通过 application service 统一封装”,这会持续打破 API 只做 transport concerns 的边界。 + ### 2.5 文档元数据与对象存储组织方式耦合 当前文档列表与下载逻辑高度依赖 MinIO 对象命名方式和对象遍历结果。对象存储目前承担了部分“业务真相”的角色,但对象存储只适合作为文件二进制载体,不适合作为完整文档元数据和状态管理的唯一来源。 @@ -84,7 +106,9 @@ - `knowledge` 通过 `DocumentProcessor.search()` 访问检索 - `agent` 通过 `Retriever` 访问检索 -这会导致同一检索能力未来演进成两条链路,难以统一检索策略、元数据模型和可替换边界。 +这条问题描述需要更新。现在检索主链路已经被 `KnowledgeRetrievalService` 统一收束,`knowledge` 与 `agent` 的共享底座比过去清晰得多。 + +当前仍未完全收口的是 Agent 的 session 相关能力:公开 API 里还存在 `/agent/session/{id}`、`/agent/session/{id}/history`、`/agent/sessions`、`/agent/feedback` 等接口直接读写 `ConversationStore`,这意味着“问答编排统一了,但会话管理还没有统一入口”。 ## 3. Architecture Goals @@ -132,7 +156,8 @@ 系统必须形成稳定的单向依赖关系: - `api -> application -> domain` -- `application -> infrastructure` 通过端口/实现绑定 +- `application -> domain ports` +- `composition root -> application + infrastructure bindings` - `infrastructure -> external systems` 不允许出现基础设施实现反向驱动业务编排,也不允许 domain 依赖 Web 或第三方 SDK。 @@ -238,12 +263,20 @@ backend/app/ - 通用异常 - 通用工具 - 公共基础设施无关组件 +- composition root 与依赖装配 +- 运行时共享的 wiring 辅助入口 非职责: - 不承载业务编排 - 不变成新的 `services` 大杂烩目录 +说明: + +- `shared` 不是业务层,但它是本项目当前明确的装配层。 +- `backend/app/shared/bootstrap.py` 是现阶段的 composition root,负责把端口实现、基础设施适配器和 application service 连接起来。 +- 后续如果新增 wiring 入口,应继续保持在同一类装配边界内,不要把依赖装配拆回各个路由或 service 构造函数中。 + ## 5. Module Responsibilities ### 5.1 `api` @@ -269,6 +302,11 @@ backend/app/ - `KnowledgeRetrievalService` - `AgentConversationService` +说明: + +- 这 4 类是当前已经明确成型的核心用例服务,但不是 application 的全部上限。 +- 现有 Agent session 管理接口所需的会话查询、历史读取、删除和反馈归档能力,后续应补入 application 层的明确用例服务或子服务,而不是继续让 API 直连 `ConversationStore`。 + ### 5.3 `domain` `domain` 层定义系统内部真正稳定的概念,例如: @@ -467,6 +505,21 @@ backend/app/ - 当前 `QAAgent` 的 workflow 编排职责在目标态应迁移到这里,或被其吸收后只保留 façade 角色。 - SSE 与普通问答必须共用这一层,不允许复制业务编排逻辑。 +### 7.5 `AgentSessionService` + +职责: + +- 读取会话详情 +- 读取会话历史 +- 列出会话 +- 删除会话 +- 记录会话反馈 + +说明: + +- 这类能力目前仍散落在 `backend/app/api/routes/agent.py`,并直接依赖 `ConversationStore`。 +- 目标态应把它们收口到 application 层,保证 API 只处理 transport concerns,而不是继续承担 session 级业务访问。 + ## 8. Core Workflows ### 8.1 文档上传入库链路 @@ -519,27 +572,63 @@ backend/app/ - 业务编排链必须完全复用 - 检索能力必须来自同一 `KnowledgeRetrievalService` +### 8.4 Agent Session 管理链路 + +目标流程如下: + +1. `api/agent/session/*` 或 `api/agent/sessions` 接收会话管理请求 +2. `AgentSessionService` 读取、列出、删除或归档反馈 +3. `ConversationStore` 负责持久化与会话生命周期细节 +4. API 输出 session 详情、历史、列表或反馈结果 + +约束: + +- 会话详情、历史、列表和反馈接口不属于问答编排本身,应由独立的 session application service 处理。 +- 这部分能力不应继续直接暴露 `ConversationStore` 给路由层。 + ## 9. Dependency Rules 系统内部依赖方向固定如下: ```text api -> application -> domain -application -> infrastructure (through ports) +application -> domain ports +composition root -> application + infrastructure bindings infrastructure -> external systems ``` 具体规则如下: - `api` 可以依赖 `application` 和 API 自己的 request/response models -- `application` 可以依赖 `domain` 和端口绑定后的 infrastructure 实现 +- `application` 只能依赖 `domain`、端口接口,以及通过 composition root 注入进来的实现实例 - `domain` 不能依赖 `api` 或 `infrastructure` - `infrastructure` 可以依赖 `domain` 定义的端口和数据模型,但不能反向驱动 application 逻辑 +说明: + +- 端口绑定发生在 `shared/bootstrap.py` 这类 composition root,而不是 application 层内部。 +- application 只能接收已装配好的依赖实例,不能反向“依赖某个 infrastructure 实现类”作为其设计前提。 + ## 10. Migration Mapping From Current Code 当前关键代码到目标模块的映射如下。 +### 10.0 旧 facade 存续策略 + +当前仓库里仍保留若干旧 facade / 兼容入口,包括但不限于: + +- `backend/app/services/document_processor.py` +- `backend/app/services/rag/retriever.py` +- `backend/app/services/agent/qa_agent.py` + +这些类的定位是“兼容现有调用方的过渡入口”,不是目标态的主要编排层。它们在迁移完成前应继续保留,直到对应的调用方、测试和 API 入口都迁移到 application service 或新的适配层为止。 + +约束: + +- 不允许因为目标架构已经定义,就在没有替换调用方的情况下直接删除这些 facade。 +- 任何清理旧 `services` 的动作,都必须先确认是否还存在非 HTTP 的内部调用、测试依赖或临时适配依赖。 +- 这些 facade 可以逐步瘦身,但在迁移窗口内应优先保持行为兼容,而不是追求“文件级清零”。 + ### 10.1 文档处理 当前: @@ -551,6 +640,7 @@ infrastructure -> external systems - 其流程编排职责迁移到 `application/documents/DocumentCommandService` - 解析、分块、向量、入库分别通过端口接入 - 检索入口从该类中剥离,不再由 ingest orchestration 承担 search 职责 +- 迁移期间该类作为兼容 facade 保留,直到所有直接调用点收口完成 ### 10.2 检索 @@ -563,6 +653,7 @@ infrastructure -> external systems - `domain/retrieval` 中定义 `Retriever` 端口和统一检索结果模型 - `infrastructure/vectorstore` 中承载具体检索实现 - `application/knowledge/KnowledgeRetrievalService` 作为统一检索用例入口 +- 迁移期间该类作为兼容 facade 保留,避免影响现有检索调用方 ### 10.3 Agent Workflow @@ -576,6 +667,7 @@ infrastructure -> external systems - 具体 LLM 调用走 `AnswerGenerator` - 具体 session 读写走 `ConversationStore` - 检索统一走 `KnowledgeRetrievalService` +- 迁移期间该类作为兼容 facade 保留,避免影响现有问答和测试调用点 ### 10.4 存储 diff --git a/frontend/src/api/compliance.ts b/frontend/src/api/compliance.ts index 510c2b4..8a14abe 100644 --- a/frontend/src/api/compliance.ts +++ b/frontend/src/api/compliance.ts @@ -1,11 +1,10 @@ -import { streamSSE, type ComplianceResult, type SSEMessage } from './index'; +import { API_BASE_URL, streamSSE, type ComplianceResult, type SSEMessage } from './index'; -// Upload and analyze a design document export async function analyzeDocument(file: File): Promise<{ task_id: string; status: string }> { const formData = new FormData(); formData.append('file', file); - const response = await fetch('/api/compliance/analyze', { + const response = await fetch(`${API_BASE_URL}/compliance/analyze`, { method: 'POST', body: formData, }); @@ -14,21 +13,21 @@ export async function analyzeDocument(file: File): Promise<{ task_id: string; st throw new Error(`Upload failed: ${response.status}`); } - return response.json(); + return response.json() as Promise<{ task_id: string; status: string }>; } -// Get analysis result -export async function getComplianceResult(taskId: string): Promise { - const response = await fetch(`/api/compliance/result/${taskId}`); +export async function getComplianceResult( + taskId: string +): Promise { + const response = await fetch(`${API_BASE_URL}/compliance/result/${taskId}`); if (!response.ok) { throw new Error(`Get result failed: ${response.status}`); } - return response.json(); + return response.json() as Promise; } -// Compliance chat with SSE streaming export function complianceChat( segmentId: number, query: string, @@ -36,8 +35,7 @@ export function complianceChat( onError?: (error: Error) => void, onComplete?: () => void ): void { - streamSSE(`/compliance/chat/${segmentId}`, { query }, onMessage, onError, onComplete); + void streamSSE(`/compliance/chat/${segmentId}`, { query }, onMessage, onError, onComplete); } -// Export types -export type { ComplianceResult, SSEMessage }; \ No newline at end of file +export type { ComplianceResult, SSEMessage }; diff --git a/frontend/src/api/docs.ts b/frontend/src/api/docs.ts index 2a38795..a816acc 100644 --- a/frontend/src/api/docs.ts +++ b/frontend/src/api/docs.ts @@ -1,14 +1,12 @@ import type { DocInfo, DocListResponse, DocUploadResponse } from './index'; - -const DOCS_API_BASE = '/api/v1'; +import { API_BASE_URL } from './index'; interface BackendDocumentItem { doc_id: string; - filename: string; - size: number; - object_name: string; - download_url: string; - last_modified?: string | null; + doc_name: string; + status: string; + chunk_count: number; + updated_at?: string; } interface BackendDocumentListResponse { @@ -45,22 +43,14 @@ export interface RegulationSearchResponse { results: RegulationSearchItem[]; } -function formatFileSize(bytes: number): string { - if (!bytes) return '0 B'; - if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)}MB`; - if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)}KB`; - return `${bytes}B`; -} - function mapDoc(item: BackendDocumentItem): DocInfo { return { id: item.doc_id, - name: item.filename, - chunks: 0, - status: 'indexed', - created_at: item.last_modified || undefined, - download_url: `${DOCS_API_BASE}/documents/download/${item.doc_id}`, - size_text: formatFileSize(item.size), + name: item.doc_name, + chunks: item.chunk_count, + status: item.status, + updated_at: item.updated_at, + download_url: `${API_BASE_URL}/documents/download/${item.doc_id}`, }; } @@ -70,7 +60,7 @@ export async function uploadDocument(file: File): Promise { formData.append('doc_name', file.name); formData.append('generate_summary', 'true'); - const response = await fetch(`${DOCS_API_BASE}/documents/upload`, { + const response = await fetch(`${API_BASE_URL}/documents/upload`, { method: 'POST', body: formData, }); @@ -79,35 +69,35 @@ export async function uploadDocument(file: File): Promise { throw new Error(`Upload failed: ${response.status}`); } - const data = await response.json(); + const data = (await response.json()) as DocUploadResponse; return { doc_id: data.doc_id, - filename: data.doc_name || file.name, - size: file.size, + doc_name: data.doc_name || file.name, status: data.status, + message: data.message, num_chunks: data.num_chunks, summary: data.summary, }; } export async function getDocumentList(): Promise { - const response = await fetch(`${DOCS_API_BASE}/documents/management-list`); + const response = await fetch(`${API_BASE_URL}/documents/management-list`); if (!response.ok) { throw new Error(`List failed: ${response.status}`); } - const data = await response.json() as BackendDocumentListResponse; + const data = (await response.json()) as BackendDocumentListResponse; return { docs: data.documents.map(mapDoc), }; } export async function searchRegulations(query: string, topK: number = 8): Promise { - const response = await fetch(`${DOCS_API_BASE}/knowledge/retrieval`, { + const response = await fetch(`${API_BASE_URL}/knowledge/retrieval`, { method: 'POST', headers: { - 'Content-Type': 'application/json', Accept: 'application/json', + 'Content-Type': 'application/json', }, body: JSON.stringify({ query, top_k: topK }), }); @@ -116,7 +106,7 @@ export async function searchRegulations(query: string, topK: number = 8): Promis throw new Error(`Search failed: ${response.status}`); } - const data = await response.json() as BackendKnowledgeResponse; + const data = (await response.json()) as BackendKnowledgeResponse; return { query: data.query, total: data.total, @@ -125,12 +115,13 @@ export async function searchRegulations(query: string, topK: number = 8): Promis return { id: item.id, file: String(metadata.doc_name || metadata.filename || metadata.source || '法规知识库'), - clause: String(metadata.chunk_type || metadata.section || metadata.clause || '法规片段'), + clause: String(metadata.section_title || metadata.clause_number || metadata.clause || '法规片段'), score: item.score, content: item.content, tags: [ metadata.regulation_type ? String(metadata.regulation_type) : '', metadata.version ? `v${String(metadata.version)}` : '', + metadata.page_number ? `p.${String(metadata.page_number)}` : '', ].filter(Boolean), }; }), @@ -138,7 +129,7 @@ export async function searchRegulations(query: string, topK: number = 8): Promis } export function getDocumentDownloadUrl(docId: string): string { - return `${DOCS_API_BASE}/documents/download/${docId}`; + return `${API_BASE_URL}/documents/download/${docId}`; } export type { DocInfo, DocListResponse, DocUploadResponse }; diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index df95082..793bf39 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -1,63 +1,75 @@ -// API configuration - 使用相对路径,通过 Vite proxy 转发 -const API_BASE_URL = '/api'; +const API_BASE_URL = '/api/v1'; -// Helper function for fetch requests -async function fetchAPI(endpoint: string, options?: RequestInit): Promise { - const response = await fetch(`${API_BASE_URL}${endpoint}`, { +interface ApiErrorPayload { + detail?: string; + message?: string; + error?: string; +} + +function buildUrl(endpoint: string): string { + return `${API_BASE_URL}${endpoint.startsWith('/') ? endpoint : `/${endpoint}`}`; +} + +async function readErrorMessage(response: Response): Promise { + try { + const payload = (await response.json()) as ApiErrorPayload; + return payload.detail || payload.message || payload.error || `${response.status} ${response.statusText}`; + } catch { + return `${response.status} ${response.statusText}`; + } +} + +export async function fetchAPI(endpoint: string, options?: RequestInit): Promise { + const headers = new Headers(options?.headers); + if (!headers.has('Accept')) { + headers.set('Accept', 'application/json'); + } + if (!headers.has('Content-Type') && !(options?.body instanceof FormData)) { + headers.set('Content-Type', 'application/json'); + } + + const response = await fetch(buildUrl(endpoint), { ...options, - headers: { - ...options?.headers, - 'Content-Type': 'application/json', - }, + headers, }); if (!response.ok) { - throw new Error(`API Error: ${response.status} ${response.statusText}`); + throw new Error(`API Error: ${await readErrorMessage(response)}`); } - return response.json(); + return response.json() as Promise; } -// SSE helper for streaming responses -function createSSEConnection(endpoint: string, body: unknown): EventSource { - // For POST requests with SSE, we need to use fetch with ReadableStream - // since EventSource only supports GET requests - const url = `${API_BASE_URL}${endpoint}`; - - return new EventSource(url); // This won't work for POST, we'll handle it differently +export interface SSEMessage { + type: string; + text?: string; + docs?: RetrievedDoc[]; } -// SSE streaming helper for POST requests -async function streamSSE( +export async function streamSSE( endpoint: string, body: unknown, - onMessage: (data: unknown) => void, + onMessage: (data: TMessage) => void, onError?: (error: Error) => void, onComplete?: () => void ): Promise { - const url = `${API_BASE_URL}${endpoint}`; - - const response = await fetch(url, { + const response = await fetch(buildUrl(endpoint), { method: 'POST', headers: { + Accept: 'text/event-stream', 'Content-Type': 'application/json', - 'Accept': 'text/event-stream', }, body: JSON.stringify(body), }); if (!response.ok) { - if (onError) { - onError(new Error(`HTTP error! status: ${response.status}`)); - } + onError?.(new Error(`HTTP error! status: ${await readErrorMessage(response)}`)); return; } const reader = response.body?.getReader(); if (!reader) { - if (onError) { - onError(new Error('No response body')); - } + onError?.(new Error('No response body')); return; } @@ -67,47 +79,61 @@ async function streamSSE( try { while (true) { const { done, value } = await reader.read(); - if (done) break; + if (done) { + break; + } buffer += decoder.decode(value, { stream: true }); + const events = buffer.split('\n\n'); + buffer = events.pop() || ''; - // Process SSE events - const lines = buffer.split('\n'); - buffer = ''; + for (const eventBlock of events) { + const dataLines = eventBlock + .split('\n') + .filter((line) => line.startsWith('data:')) + .map((line) => line.slice(5).trim()); - for (const line of lines) { - if (line.startsWith('data:')) { - const data = line.slice(5).trim(); - if (data) { - try { - const parsed = JSON.parse(data); - onMessage(parsed); - } catch { - // Handle non-JSON data - onMessage({ type: 'raw', text: data }); - } - } + if (dataLines.length === 0) { + continue; + } + + const rawData = dataLines.join('\n'); + try { + onMessage(JSON.parse(rawData) as TMessage); + } catch { + onMessage({ type: 'raw', text: rawData } as TMessage); } } } - if (onComplete) { - onComplete(); + if (buffer.trim()) { + const dataLines = buffer + .split('\n') + .filter((line) => line.startsWith('data:')) + .map((line) => line.slice(5).trim()); + + if (dataLines.length > 0) { + const rawData = dataLines.join('\n'); + try { + onMessage(JSON.parse(rawData) as TMessage); + } catch { + onMessage({ type: 'raw', text: rawData } as TMessage); + } + } } + + onComplete?.(); } catch (error) { - if (onError) { - onError(error instanceof Error ? error : new Error(String(error))); - } + onError?.(error instanceof Error ? error : new Error(String(error))); } } -// Export types export interface DocInfo { id: string; name: string; chunks: number; status: string; - created_at?: string; + updated_at?: string; download_url?: string; size_text?: string; } @@ -118,9 +144,9 @@ export interface DocListResponse { export interface DocUploadResponse { doc_id: string; - filename: string; - size: number; + doc_name: string; status: string; + message?: string; num_chunks?: number; summary?: string; } @@ -145,16 +171,10 @@ export interface RetrievedDoc { download_url?: string; } -export interface SSEMessage { - type: string; - text?: string; - docs?: RetrievedDoc[]; -} - export interface Regulation { id: number; name: string; - clause: string; + clause: string | null; score: number; match_keyword: string; category: string; @@ -197,17 +217,20 @@ export interface ComplianceResult { } export interface SystemStats { - docs: number; - chunks: number; - vectors: number; - segments: number; + documents_total: number; + documents_indexed: number; + documents_failed: number; + chunks_total: number; } export interface SystemConfig { - llm: { model: string }; - embedding: { model: string; dimension: number }; - milvus: { host: string; port: number }; - retrieval: { vector_top_k: number; final_top_k: number }; + embedding_model: string; + embedding_dim: number; + embedding_base_url: string; + milvus_collection: string; + llm_provider: string; + llm_model: string; + document_metadata_path: string; } -export { fetchAPI, streamSSE, API_BASE_URL }; +export { API_BASE_URL }; diff --git a/frontend/src/api/status.ts b/frontend/src/api/status.ts index 81da0dc..87fbcd4 100644 --- a/frontend/src/api/status.ts +++ b/frontend/src/api/status.ts @@ -1,19 +1,15 @@ -import { fetchAPI, type SystemStats, type SystemConfig } from './index'; +import { fetchAPI, type SystemConfig, type SystemStats } from './index'; -// Get system statistics export async function getSystemStats(): Promise { return fetchAPI('/status/stats'); } -// Get system configuration export async function getSystemConfig(): Promise { return fetchAPI('/status/config'); } -// Get Milvus health status export async function getMilvusHealth(): Promise<{ connected: boolean; collections: string[] }> { return fetchAPI('/status/milvus/health'); } -// Export types -export type { SystemStats, SystemConfig }; \ No newline at end of file +export type { SystemConfig, SystemStats }; diff --git a/frontend/src/components/common/TPattern.tsx b/frontend/src/components/common/TPattern.tsx index 222aeea..1981c6f 100644 --- a/frontend/src/components/common/TPattern.tsx +++ b/frontend/src/components/common/TPattern.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { useTheme } from '../../contexts/ThemeContext'; +import { useTheme } from '../../contexts'; export const TPattern: React.FC = () => { const { theme, isDark } = useTheme(); @@ -27,4 +27,4 @@ export const TPattern: React.FC = () => { ); -}; \ No newline at end of file +}; diff --git a/frontend/src/components/common/ThemeToggle.tsx b/frontend/src/components/common/ThemeToggle.tsx index 3198a32..760355c 100644 --- a/frontend/src/components/common/ThemeToggle.tsx +++ b/frontend/src/components/common/ThemeToggle.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { useTheme } from '../../contexts/ThemeContext'; +import { useTheme } from '../../contexts'; export const ThemeToggle: React.FC = () => { const { isDark, toggleTheme, theme } = useTheme(); @@ -32,4 +32,4 @@ export const ThemeToggle: React.FC = () => { )} ); -}; \ No newline at end of file +}; diff --git a/frontend/src/components/layout/Content.tsx b/frontend/src/components/layout/Content.tsx index cd007b0..833d09c 100644 --- a/frontend/src/components/layout/Content.tsx +++ b/frontend/src/components/layout/Content.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { useTheme } from '../../contexts/ThemeContext'; +import { useTheme } from '../../contexts'; interface ContentProps { children: React.ReactNode; @@ -24,4 +24,4 @@ export const Content: React.FC = ({ children, wide = false }) => { {children} ); -}; \ No newline at end of file +}; diff --git a/frontend/src/components/layout/Header.tsx b/frontend/src/components/layout/Header.tsx index 7d5c1f0..823eb20 100644 --- a/frontend/src/components/layout/Header.tsx +++ b/frontend/src/components/layout/Header.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { useTheme } from '../../contexts/ThemeContext'; +import { useTheme } from '../../contexts'; import { TLogo } from '../common/TLogo'; import { ThemeToggle } from '../common/ThemeToggle'; @@ -44,4 +44,4 @@ export const Header: React.FC = () => { ); -}; \ No newline at end of file +}; diff --git a/frontend/src/components/layout/Tabs.tsx b/frontend/src/components/layout/Tabs.tsx index c1e9356..a62fc32 100644 --- a/frontend/src/components/layout/Tabs.tsx +++ b/frontend/src/components/layout/Tabs.tsx @@ -1,7 +1,8 @@ import React from 'react'; import { useTheme, useApp } from '../../contexts'; +import type { TabId } from '../../contexts'; -const tabs = [ +const tabs: Array<{ id: TabId; label: string }> = [ { id: 'docs', label: '文档管理' }, { id: 'compliance', label: '合规分析' }, { id: 'status', label: '系统状态' }, @@ -24,7 +25,7 @@ export const Tabs: React.FC = () => { {tabs.map((tab) => ( ); -}; \ No newline at end of file +}; diff --git a/frontend/src/components/ui/Card.tsx b/frontend/src/components/ui/Card.tsx index e8bc925..97f49ab 100644 --- a/frontend/src/components/ui/Card.tsx +++ b/frontend/src/components/ui/Card.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { useTheme } from '../../contexts/ThemeContext'; +import { useTheme } from '../../contexts'; interface CardProps { accent?: boolean; @@ -51,4 +51,4 @@ export const Card: React.FC = ({ {children} ); -}; \ No newline at end of file +}; diff --git a/frontend/src/components/ui/Input.tsx b/frontend/src/components/ui/Input.tsx index 59fc62b..fe6e4fd 100644 --- a/frontend/src/components/ui/Input.tsx +++ b/frontend/src/components/ui/Input.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { useTheme } from '../../contexts/ThemeContext'; +import { useTheme } from '../../contexts'; interface InputProps { value: string; @@ -42,4 +42,4 @@ export const Input: React.FC = ({ }} /> ); -}; \ No newline at end of file +}; diff --git a/frontend/src/components/ui/ProgressBar.tsx b/frontend/src/components/ui/ProgressBar.tsx index b16fc9c..52e216b 100644 --- a/frontend/src/components/ui/ProgressBar.tsx +++ b/frontend/src/components/ui/ProgressBar.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { useTheme } from '../../contexts/ThemeContext'; +import { useTheme } from '../../contexts'; interface ProgressBarProps { percent: number; @@ -40,4 +40,4 @@ export const ProgressBar: React.FC = ({ )} ); -}; \ No newline at end of file +}; diff --git a/frontend/src/components/ui/ScoreBar.tsx b/frontend/src/components/ui/ScoreBar.tsx index 053331a..6e7ee55 100644 --- a/frontend/src/components/ui/ScoreBar.tsx +++ b/frontend/src/components/ui/ScoreBar.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { useTheme } from '../../contexts/ThemeContext'; +import { useTheme } from '../../contexts'; interface ScoreBarProps { score: number; // 0-100 @@ -49,4 +49,4 @@ export const ScoreBar: React.FC = ({ ); -}; \ No newline at end of file +}; diff --git a/frontend/src/contexts/AppContext.tsx b/frontend/src/contexts/AppContext.tsx index 4df3156..a2040c1 100644 --- a/frontend/src/contexts/AppContext.tsx +++ b/frontend/src/contexts/AppContext.tsx @@ -1,21 +1,6 @@ -import { createContext, useContext, useState, type ReactNode } from 'react'; +import { useState, type ReactNode } from 'react'; -type TabId = 'docs' | 'compliance' | 'status' | 'rag'; - -interface AppContextValue { - activeTab: TabId; - setActiveTab: (tab: TabId) => void; -} - -const AppContext = createContext(undefined); - -export const useApp = (): AppContextValue => { - const context = useContext(AppContext); - if (!context) { - throw new Error('useApp must be used within an AppProvider'); - } - return context; -}; +import { AppContext, type TabId } from './app-context'; interface AppProviderProps { children: ReactNode; @@ -29,4 +14,4 @@ export const AppProvider: React.FC = ({ children }) => { {children} ); -}; \ No newline at end of file +}; diff --git a/frontend/src/contexts/ThemeContext.tsx b/frontend/src/contexts/ThemeContext.tsx index d145b44..0960b9d 100644 --- a/frontend/src/contexts/ThemeContext.tsx +++ b/frontend/src/contexts/ThemeContext.tsx @@ -1,22 +1,7 @@ -import React, { createContext, useContext, useState, useEffect, type ReactNode } from 'react'; +import React, { useEffect, useState, type ReactNode } from 'react'; + import { darkTheme, lightTheme } from '../types/theme'; -import type { ThemeColors } from '../types/theme'; - -interface ThemeContextValue { - isDark: boolean; - theme: ThemeColors; - toggleTheme: () => void; -} - -const ThemeContext = createContext(undefined); - -export const useTheme = (): ThemeContextValue => { - const context = useContext(ThemeContext); - if (!context) { - throw new Error('useTheme must be used within a ThemeProvider'); - } - return context; -}; +import { ThemeContext } from './theme-context'; interface ThemeProviderProps { children: ReactNode; @@ -30,15 +15,15 @@ export const ThemeProvider: React.FC = ({ children }) => { setIsDark((prev) => !prev); }; - // Apply class to document for Tailwind dark mode + body background useEffect(() => { if (isDark) { document.documentElement.classList.add('dark'); document.body.style.background = '#0a0a12'; - } else { - document.documentElement.classList.remove('dark'); - document.body.style.background = '#ffffff'; + return; } + + document.documentElement.classList.remove('dark'); + document.body.style.background = '#ffffff'; }, [isDark]); return ( @@ -46,4 +31,4 @@ export const ThemeProvider: React.FC = ({ children }) => { {children} ); -}; \ No newline at end of file +}; diff --git a/frontend/src/contexts/app-context.ts b/frontend/src/contexts/app-context.ts new file mode 100644 index 0000000..68d31da --- /dev/null +++ b/frontend/src/contexts/app-context.ts @@ -0,0 +1,10 @@ +import { createContext } from 'react'; + +export type TabId = 'docs' | 'compliance' | 'status' | 'rag'; + +export interface AppContextValue { + activeTab: TabId; + setActiveTab: (tab: TabId) => void; +} + +export const AppContext = createContext(undefined); diff --git a/frontend/src/contexts/index.ts b/frontend/src/contexts/index.ts index d44f249..550a1c7 100644 --- a/frontend/src/contexts/index.ts +++ b/frontend/src/contexts/index.ts @@ -1,2 +1,5 @@ -export { ThemeProvider, useTheme } from './ThemeContext'; -export { AppProvider, useApp } from './AppContext'; \ No newline at end of file +export { ThemeProvider } from './ThemeContext'; +export { useTheme } from './useTheme'; +export { AppProvider } from './AppContext'; +export type { AppContextValue, TabId } from './app-context'; +export { useApp } from './useApp'; diff --git a/frontend/src/contexts/theme-context.ts b/frontend/src/contexts/theme-context.ts new file mode 100644 index 0000000..9f6d796 --- /dev/null +++ b/frontend/src/contexts/theme-context.ts @@ -0,0 +1,11 @@ +import { createContext } from 'react'; + +import type { ThemeColors } from '../types/theme'; + +export interface ThemeContextValue { + isDark: boolean; + theme: ThemeColors; + toggleTheme: () => void; +} + +export const ThemeContext = createContext(undefined); diff --git a/frontend/src/contexts/useApp.ts b/frontend/src/contexts/useApp.ts new file mode 100644 index 0000000..8bda7b5 --- /dev/null +++ b/frontend/src/contexts/useApp.ts @@ -0,0 +1,11 @@ +import { useContext } from 'react'; + +import { AppContext, type AppContextValue } from './app-context'; + +export function useApp(): AppContextValue { + const context = useContext(AppContext); + if (!context) { + throw new Error('useApp must be used within an AppProvider'); + } + return context; +} diff --git a/frontend/src/contexts/useTheme.ts b/frontend/src/contexts/useTheme.ts new file mode 100644 index 0000000..8ac7f83 --- /dev/null +++ b/frontend/src/contexts/useTheme.ts @@ -0,0 +1,11 @@ +import { useContext } from 'react'; + +import { ThemeContext, type ThemeContextValue } from './theme-context'; + +export function useTheme(): ThemeContextValue { + const context = useContext(ThemeContext); + if (!context) { + throw new Error('useTheme must be used within a ThemeProvider'); + } + return context; +} diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts deleted file mode 100644 index 33254f9..0000000 --- a/frontend/src/lib/api.ts +++ /dev/null @@ -1,85 +0,0 @@ -import type { - AgentChatResponse, - DocumentListResponse, - KnowledgeSearchResponse, - UploadDocumentResponse, -} from '../types'; - -const API_BASE = ( - import.meta.env.VITE_API_BASE_URL || - `${window.location.protocol}//${window.location.hostname}:8000/api/v1` -).replace(/\/$/, ''); - -async function request(path: string, init?: RequestInit): Promise { - const response = await fetch(`${API_BASE}${path}`, { - ...init, - headers: { - Accept: 'application/json', - ...(init?.headers || {}), - }, - }); - - if (!response.ok) { - let message = `Request failed: ${response.status}`; - try { - const data = await response.json(); - message = data.detail || data.message || message; - } catch { - // Fall back to HTTP status when the response body is not JSON. - } - throw new Error(message); - } - - return response.json() as Promise; -} - -export const api = { - listDocuments: () => request('/documents/list'), - - listDocumentManagementItems: () => request('/documents/management-list'), - - uploadDocument: async (payload: { - file: File; - docName?: string; - regulationType?: string; - version?: string; - generateSummary?: boolean; - }) => { - const formData = new FormData(); - formData.append('file', payload.file); - if (payload.docName) formData.append('doc_name', payload.docName); - if (payload.regulationType) formData.append('regulation_type', payload.regulationType); - if (payload.version) formData.append('version', payload.version); - formData.append('generate_summary', String(Boolean(payload.generateSummary))); - - return request('/documents/upload', { - method: 'POST', - body: formData, - }); - }, - - searchKnowledge: (query: string, topK = 8) => - request('/knowledge/retrieval', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ query, top_k: topK }), - }), - - chat: (payload: { query: string; sessionId?: string }) => - request('/agent/chat', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - query: payload.query, - session_id: payload.sessionId, - }), - }), - - get downloadBase() { - return API_BASE.replace(/\/api\/v1$/, ''); - }, -}; diff --git a/frontend/src/pages/Compliance/ChatPanel.tsx b/frontend/src/pages/Compliance/ChatPanel.tsx index 61162eb..2231851 100644 --- a/frontend/src/pages/Compliance/ChatPanel.tsx +++ b/frontend/src/pages/Compliance/ChatPanel.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { useTheme } from '../../contexts/ThemeContext'; +import { useTheme } from '../../contexts'; import type { ComplianceChunk } from '../../types'; interface ChatPanelProps { @@ -243,4 +243,4 @@ export const ChatPanel: React.FC = ({ ); -}; \ No newline at end of file +}; diff --git a/frontend/src/pages/Compliance/CompliancePage.tsx b/frontend/src/pages/Compliance/CompliancePage.tsx index e254321..35ef3b9 100644 --- a/frontend/src/pages/Compliance/CompliancePage.tsx +++ b/frontend/src/pages/Compliance/CompliancePage.tsx @@ -1,5 +1,5 @@ -import React, { useState } from 'react'; -import { useTheme } from '../../contexts/ThemeContext'; +import React, { useRef, useState } from 'react'; +import { useTheme } from '../../contexts'; import type { UploadedDoc, ComplianceChunk, Regulation, SegmentRisk, RiskDashboardData } from '../../types'; import { mockComplianceChunks, @@ -82,9 +82,11 @@ const getRegsByCategory = (regulations: Regulation[]) => { export const CompliancePage: React.FC = () => { const { theme, isDark } = useTheme(); + const nextMessageIdRef = useRef(1); // Upload & Analysis States const [uploadedDoc, setUploadedDoc] = useState(null); + const [uploadedFile, setUploadedFile] = useState(null); const [isAnalyzing, setIsAnalyzing] = useState(false); const [analyzeStep, setAnalyzeStep] = useState(0); const [analyzePercent, setAnalyzePercent] = useState(0); @@ -100,10 +102,17 @@ export const CompliancePage: React.FC = () => { const [chatLoading, setChatLoading] = useState(false); const [dashboardExpanded, setDashboardExpanded] = useState(false); + const nextMessageId = () => { + const currentId = nextMessageIdRef.current; + nextMessageIdRef.current += 1; + return currentId; + }; + // Handlers const handleUpload = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file) { + setUploadedFile(file); setUploadedDoc({ name: file.name, size: `${(file.size / 1024 / 1024).toFixed(2)}MB`, @@ -113,27 +122,14 @@ export const CompliancePage: React.FC = () => { }; const startAnalysis = async () => { - if (!uploadedDoc) return; - + if (!uploadedDoc || !uploadedFile) return; setIsAnalyzing(true); setAnalyzeStep(1); setAnalyzePercent(0); try { - // Get file from upload input - const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement; - const file = fileInput?.files?.[0]; - console.log("123") - - // if (!file) { - // // setIsAnalyzing(false); - // // return; - // } - - console.log("456") - // Upload and get task ID - const uploadRes = await analyzeDocument(file); + const uploadRes = await analyzeDocument(uploadedFile); // Simulate progress setTimeout(() => { @@ -174,7 +170,7 @@ export const CompliancePage: React.FC = () => { regulations: s.regulations.map(r => ({ id: r.id, name: r.name, - clause: r.clause, + clause: r.clause || '', score: r.score, matchKeyword: r.match_keyword, category: r.category as 'high' | 'medium' | 'low', @@ -204,7 +200,6 @@ export const CompliancePage: React.FC = () => { } } catch (error) { console.error('Failed to get compliance result:', error); - // Fallback to mock data setChunks(mockComplianceChunks); } @@ -215,7 +210,6 @@ export const CompliancePage: React.FC = () => { } catch (error) { console.error('Failed to analyze document:', error); setIsAnalyzing(false); - // Fallback to mock data after delay setTimeout(() => { setChunks(mockComplianceChunks); }, 4500); @@ -230,7 +224,7 @@ export const CompliancePage: React.FC = () => { setChatMessages(prev => ({ ...prev, [chunkId]: [{ - id: Date.now(), + id: nextMessageId(), role: 'assistant', content: `您好!我是法规合规分析助手。当前段落涉及 ${chunk?.regulations.length} 条相关法规,您可以询问合规性评估、法规解读或修改建议。`, }] @@ -248,7 +242,7 @@ export const CompliancePage: React.FC = () => { const chunk = chunks.find(c => c.id === activeChunkId); if (!chunk) return; - const userMsg = { id: Date.now(), role: 'user' as const, content: chatInput }; + const userMsg = { id: nextMessageId(), role: 'user' as const, content: chatInput }; setChatMessages(prev => ({ ...prev, [activeChunkId]: [...(prev[activeChunkId] || []), userMsg], @@ -267,7 +261,7 @@ export const CompliancePage: React.FC = () => { currentResponse += sseData.text; setChatMessages(prev => ({ ...prev, - [activeChunkId]: [...(prev[activeChunkId] || []).slice(0, -1), { id: Date.now() + 1, role: 'assistant', content: currentResponse }], + [activeChunkId]: [...(prev[activeChunkId] || []).slice(0, -1), { id: nextMessageId(), role: 'assistant', content: currentResponse }], })); } else if (sseData.type === 'done') { setChatLoading(false); @@ -291,7 +285,7 @@ export const CompliancePage: React.FC = () => { } setChatMessages(prev => ({ ...prev, - [activeChunkId]: [...(prev[activeChunkId] || []), { id: Date.now() + 1, role: 'assistant', content: response }], + [activeChunkId]: [...(prev[activeChunkId] || []), { id: nextMessageId(), role: 'assistant', content: response }], })); }, () => { @@ -1913,4 +1907,4 @@ export const CompliancePage: React.FC = () => { )} ); -}; \ No newline at end of file +}; diff --git a/frontend/src/pages/Docs/DocsPage.tsx b/frontend/src/pages/Docs/DocsPage.tsx index c0dd32c..32da143 100644 --- a/frontend/src/pages/Docs/DocsPage.tsx +++ b/frontend/src/pages/Docs/DocsPage.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useRef, useState } from 'react'; -import { useTheme } from '../../contexts/ThemeContext'; +import { useTheme } from '../../contexts'; import { Content } from '../../components/layout/Content'; import { TPattern } from '../../components/common/TPattern'; import { getDocumentList, searchRegulations, uploadDocument, type RegulationSearchItem } from '../../api/docs'; @@ -16,6 +16,7 @@ const PIPELINE_STEPS = [ ]; const STEP_DURATION_MS = 700; +const INITIAL_SEARCH_QUERY = '新能源汽车电池安全要求'; function wait(ms: number) { return new Promise((resolve) => { @@ -35,33 +36,12 @@ export const DocsPage: React.FC = () => { const [uploading, setUploading] = useState(false); const [uploadFileName, setUploadFileName] = useState(''); const [loading, setLoading] = useState(true); - const [searchQuery, setSearchQuery] = useState('新能源汽车电池安全要求'); + const [searchQuery, setSearchQuery] = useState(INITIAL_SEARCH_QUERY); const [searchResults, setSearchResults] = useState([]); const [searchLoading, setSearchLoading] = useState(false); const [searchError, setSearchError] = useState(''); - useEffect(() => { - void loadDocuments(); - }, []); - - useEffect(() => { - void runSearch(searchQuery); - }, []); - - useEffect(() => { - return () => { - pipelineRunIdRef.current += 1; - }; - }, []); - - const resetPipeline = (status: PipelineStatus = 'idle') => { - pipelineRunIdRef.current += 1; - setActiveStep(-1); - setCompletedSteps([]); - setPipelineStatus(status); - }; - - const loadDocuments = async () => { + async function loadDocuments() { setLoading(true); try { const response = await getDocumentList(); @@ -69,10 +49,11 @@ export const DocsPage: React.FC = () => { id: parseInt(String(doc.id).replace('doc-', ''), 10) || Math.floor(Math.random() * 10000), name: doc.name, chunks: doc.chunks, - size: doc.size_text || `${((doc.chunks * 8) / 1024).toFixed(1)}MB`, - status: doc.status === 'indexed' ? 'indexed' : 'parsing', + size: doc.updated_at ? new Date(doc.updated_at).toLocaleString() : 'Indexed document', + status: doc.status === 'indexed' ? 'indexed' : doc.status === 'failed' ? 'failed' : 'parsing', docId: doc.id, downloadUrl: doc.download_url, + updatedAt: doc.updated_at, })); setDocs(apiDocs); } catch (error) { @@ -81,7 +62,43 @@ export const DocsPage: React.FC = () => { } finally { setLoading(false); } - }; + } + + async function runSearch(query: string) { + if (!query.trim()) return; + setSearchLoading(true); + setSearchError(''); + try { + const response = await searchRegulations(query.trim(), 8); + setSearchResults(response.results); + } catch (error) { + console.error('Failed to search regulations:', error); + setSearchError(error instanceof Error ? error.message : '检索失败'); + setSearchResults([]); + } finally { + setSearchLoading(false); + } + } + + useEffect(() => { + const timerId = window.setTimeout(() => { + void loadDocuments(); + }, 0); + return () => window.clearTimeout(timerId); + }, []); + + useEffect(() => { + const timerId = window.setTimeout(() => { + void runSearch(INITIAL_SEARCH_QUERY); + }, 0); + return () => window.clearTimeout(timerId); + }, []); + + useEffect(() => { + return () => { + pipelineRunIdRef.current += 1; + }; + }, []); const runPipelineFlow = async (runId: number, uploadPromise: Promise>>) => { const guardedSetActiveStep = (step: number) => { @@ -209,22 +226,6 @@ export const DocsPage: React.FC = () => { } as React.ChangeEvent); }; - const runSearch = async (query: string) => { - if (!query.trim()) return; - setSearchLoading(true); - setSearchError(''); - try { - const response = await searchRegulations(query.trim(), 8); - setSearchResults(response.results); - } catch (error) { - console.error('Failed to search regulations:', error); - setSearchError(error instanceof Error ? error.message : '检索失败'); - setSearchResults([]); - } finally { - setSearchLoading(false); - } - }; - const getStepStyle = (index: number) => { const isActive = activeStep === index; const isCompleted = completedSteps.includes(index); @@ -525,7 +526,7 @@ export const DocsPage: React.FC = () => {
{doc.name}
- {doc.size} + {doc.updatedAt ? new Date(doc.updatedAt).toLocaleString() : doc.size} {doc.docId ? ` · ${doc.docId}` : ''}
@@ -549,10 +550,14 @@ export const DocsPage: React.FC = () => { padding: '6px 12px', background: theme.bgHover, borderRadius: 6, - color: theme.text2, + color: doc.status === 'failed' ? '#d64545' : theme.text2, }} > - {doc.status === 'parsing' ? '处理中...' : `${doc.chunks} chunks`} + {doc.status === 'parsing' + ? '处理中...' + : doc.status === 'failed' + ? '处理失败' + : `${doc.chunks} chunks`} diff --git a/frontend/src/pages/RagChat/RagChatPage.tsx b/frontend/src/pages/RagChat/RagChatPage.tsx index 7aedf9d..33a804f 100644 --- a/frontend/src/pages/RagChat/RagChatPage.tsx +++ b/frontend/src/pages/RagChat/RagChatPage.tsx @@ -1,5 +1,5 @@ -import React, { useState, useEffect } from 'react'; -import { useTheme } from '../../contexts/ThemeContext'; +import React, { useEffect, useRef, useState } from 'react'; +import { useTheme } from '../../contexts'; import type { ChatMessage, RetrievalData } from '../../types'; import { getQuickQuestions, ragChat } from '../../api/rag'; @@ -16,6 +16,7 @@ const ragQuickQuestionsDefault = [ export const RagChatPage: React.FC = () => { const { theme } = useTheme(); + const nextMessageIdRef = useRef(1); const [messages, setMessages] = useState([]); const [retrievals, setRetrievals] = useState([]); const [input, setInput] = useState(''); @@ -24,23 +25,32 @@ export const RagChatPage: React.FC = () => { const [selectedRetrieval, setSelectedRetrieval] = useState(null); const [quickQuestions, setQuickQuestions] = useState(ragQuickQuestionsDefault); - useEffect(() => { - void loadQuickQuestions(); - }, []); + function nextMessageId() { + const currentId = nextMessageIdRef.current; + nextMessageIdRef.current += 1; + return currentId; + } - const loadQuickQuestions = async () => { + async function loadQuickQuestions() { try { const response = await getQuickQuestions(); setQuickQuestions(response.questions.map(q => q.question)); } catch (error) { console.error('Failed to load quick questions:', error); } - }; + } + + useEffect(() => { + const timerId = window.setTimeout(() => { + void loadQuickQuestions(); + }, 0); + return () => window.clearTimeout(timerId); + }, []); const sendMessage = (text: string) => { if (!text.trim()) return; - const userMsg = { id: Date.now(), role: 'user' as const, content: text }; + const userMsg = { id: nextMessageId(), role: 'user' as const, content: text }; setMessages((prev) => [...prev, userMsg]); setInput(''); setLoading(true); @@ -84,7 +94,7 @@ export const RagChatPage: React.FC = () => { if (lastMsg?.role === 'assistant') { return [...prev.slice(0, -1), { ...lastMsg, content: currentResponse }]; } - return [...prev, { id: Date.now() + 1, role: 'assistant' as const, content: currentResponse }]; + return [...prev, { id: nextMessageId(), role: 'assistant' as const, content: currentResponse }]; }); } else if (sseData.type === 'done') { setLoading(false); @@ -97,7 +107,7 @@ export const RagChatPage: React.FC = () => { setLoading(false); setMessages((prev) => [ ...prev, - { id: Date.now() + 1, role: 'assistant' as const, content: '抱歉,连接服务器时出错,请稍后再试。' } + { id: nextMessageId(), role: 'assistant' as const, content: '抱歉,连接服务器时出错,请稍后再试。' } ]); }, () => { @@ -159,7 +169,7 @@ export const RagChatPage: React.FC = () => { if (lastMsg?.role === 'assistant') { return [...prev.slice(0, -1), { ...lastMsg, content: currentResponse }]; } - return [...prev, { id: Date.now() + 1, role: 'assistant' as const, content: currentResponse }]; + return [...prev, { id: nextMessageId(), role: 'assistant' as const, content: currentResponse }]; }); } else if (sseData.type === 'done') { setLoading(false); @@ -170,7 +180,7 @@ export const RagChatPage: React.FC = () => { setLoading(false); setMessages((prev) => [ ...prev, - { id: Date.now() + 1, role: 'assistant' as const, content: '抱歉,连接服务器时出错,请稍后再试。' } + { id: nextMessageId(), role: 'assistant' as const, content: '抱歉,连接服务器时出错,请稍后再试。' } ]); }, () => { diff --git a/frontend/src/pages/Status/StatusPage.tsx b/frontend/src/pages/Status/StatusPage.tsx index 258d0db..b4b5ef4 100644 --- a/frontend/src/pages/Status/StatusPage.tsx +++ b/frontend/src/pages/Status/StatusPage.tsx @@ -1,5 +1,5 @@ -import React, { useState, useEffect } from 'react'; -import { useTheme } from '../../contexts/ThemeContext'; +import React, { useEffect, useState } from 'react'; +import { useTheme } from '../../contexts'; import { Content } from '../../components/layout/Content'; import { TPattern } from '../../components/common/TPattern'; import { getSystemStats, getSystemConfig, type SystemStats, type SystemConfig } from '../../api/status'; @@ -38,17 +38,16 @@ const StatsCard = ({ label, value, accent = false }: { export const StatusPage: React.FC = () => { const { theme, isDark } = useTheme(); - const [stats, setStats] = useState({ docs: 0, chunks: 0, vectors: 0, segments: 0 }); + const [stats, setStats] = useState({ + documents_total: 0, + documents_indexed: 0, + documents_failed: 0, + chunks_total: 0, + }); const [config, setConfig] = useState(null); const [docs, setDocs] = useState([]); - const [loading, setLoading] = useState(true); - useEffect(() => { - loadData(); - }, []); - - const loadData = async () => { - setLoading(true); + async function loadData() { try { const [statsRes, configRes, docsRes] = await Promise.all([ getSystemStats(), @@ -61,30 +60,31 @@ export const StatusPage: React.FC = () => { } catch (error) { console.error('Failed to load status data:', error); } - setLoading(false); - }; + } - // 计算总chunks - const totalChunks = docs.reduce((sum, d) => sum + d.chunks, 0); + useEffect(() => { + const timerId = window.setTimeout(() => { + void loadData(); + }, 0); + return () => window.clearTimeout(timerId); + }, []); return ( - {/* System Stats */}
- - - - + + + +
- {/* Configuration */}

{ letterSpacing: '1px', }}>SYSTEM CONFIGURATION

- {/* ChromaDB Config */}
-
VECTOR DATABASE
-
- {[ - ['Vector DB', 'Milvus'], - ['Host', config?.milvus.host || 'localhost'], - ['Port', String(config?.milvus.port || 19530)], - ].map(([k, v]) => ( -
- {k} - {v} -
- ))} -
-
- - {/* LLM Config */} -
-
LLM CONFIGURATION
+
MODELS
{[ - ['LLM Model', config?.llm.model || 'qwen-max'], - ['Embedding Model', config?.embedding.model || 'text-embedding-v3'], - ['Embedding Dim', String(config?.embedding.dimension || 1536)], - ['Temperature', '0.1'], + ['LLM Provider', config?.llm_provider || '-'], + ['LLM Model', config?.llm_model || '-'], + ['Embedding Model', config?.embedding_model || '-'], + ['Embedding Dim', String(config?.embedding_dim || 0)], ].map(([k, v]) => (
{
- {/* Retrieval Config */}
-
RETRIEVAL CONFIGURATION (HYBRID)
-
- {[ - ['Vector Top-K', String(config?.retrieval.vector_top_k || 10)], - ['BM25 Top-K', '10'], - ['Final Top-K', String(config?.retrieval.final_top_k || 5)], - ].map(([k, v]) => ( -
- {k} - {v} -
- ))} -
-
- - {/* Chunk Config */} -
-
CHUNK CONFIGURATION
+
STORAGE AND PATHS
{[ - ['Chunk Size', '800'], - ['Chunk Overlap', '100'], + ['Milvus Collection', config?.milvus_collection || '-'], + ['Metadata Path', config?.document_metadata_path || '-'], + ['Embedding Base URL', config?.embedding_base_url || '-'], ].map(([k, v]) => (
{
- {/* Indexed Docs Overview */}

{ }}>
{d.name} - {(d.chunks * 8 / 1024).toFixed(1)}MB + + {d.updated_at ? new Date(d.updated_at).toLocaleString() : d.status} +
{d.chunks} chunks
- INDEXED + + {d.status.toUpperCase()} +
@@ -254,4 +196,4 @@ export const StatusPage: React.FC = () => {

); -}; \ No newline at end of file +}; diff --git a/frontend/src/types/doc.ts b/frontend/src/types/doc.ts index 9725053..d08e76e 100644 --- a/frontend/src/types/doc.ts +++ b/frontend/src/types/doc.ts @@ -3,10 +3,11 @@ export interface Doc { name: string; chunks: number; size: string; - status: 'indexed' | 'parsing' | 'pending'; + status: 'indexed' | 'parsing' | 'pending' | 'failed'; docId?: string; downloadUrl?: string; summary?: string; + updatedAt?: string; } export interface SearchResult { diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 188ea13..2b5ede0 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,17 +1,24 @@ -import { defineConfig } from 'vite' +import { defineConfig, loadEnv } from 'vite' import react from '@vitejs/plugin-react' // https://vite.dev/config/ -export default defineConfig({ - plugins: [react()], - server: { - host: '0.0.0.0', - port: 5173, - proxy: { - '^/api/.*': { - target: 'http://6.86.80.8:8000', - changeOrigin: true, +export default defineConfig(({ mode }) => { + const env = loadEnv(mode, process.cwd(), '') + const apiHost = env.API_HOST || 'localhost' + const apiPort = env.API_PORT || '8000' + const proxyTarget = env.VITE_API_PROXY_TARGET || `http://${apiHost}:${apiPort}` + + return { + plugins: [react()], + server: { + host: '0.0.0.0', + port: Number(env.FRONTEND_PORT || 5173), + proxy: { + '/api': { + target: proxyTarget, + changeOrigin: true, + }, }, }, - }, + } }) diff --git a/pyproject.toml b/pyproject.toml index 72bc15b..375c01e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,23 +18,21 @@ dependencies = [ "langchain-milvus>=0.1.0", "pymupdf>=1.24.0", "python-docx>=0.8.11", - "FlagEmbedding>=1.2.0", - "sentence-transformers>=2.2.0", "pydantic>=2.0.0", "pydantic-settings>=2.0.0", "python-dotenv>=1.0.0", "loguru>=0.7.0", "tenacity>=8.2.0", "httpx>=0.24.0", + "alibabacloud-docmind-api20220711>=1.0.6", + "alibabacloud-tea-openapi>=0.3.11", + "alibabacloud-tea-util>=0.3.13", "celery>=5.3.0", "redis>=4.5.0", "minio>=7.1.0", "psycopg2-binary>=2.9.0" ] -[project.optional-dependencies] -mineru = ["magic-pdf[full]>=0.6.0"] - [dependency-groups] dev = ["pytest>=7.0.0", "pytest-asyncio>=0.21.0", "isort>=8.0.1"] diff --git a/requirements.txt b/requirements.txt index 0f1b160..4825645 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,17 +14,15 @@ langchain>=0.1.0 langchain-milvus>=0.1.0 # PDF解析 -pymupdf>=1.24.0 # PyMuPDF +pymupdf>=1.24.0 # Word文档解析 python-docx>=0.8.11 -# MinerU多模态PDF解析(可选,需要额外配置) -# magic-pdf[full]>=0.6.0 - -# 嵌入模型 -FlagEmbedding>=1.2.0 -sentence-transformers>=2.2.0 +# 阿里云文档解析 +alibabacloud-docmind-api20220711>=1.0.6 +alibabacloud-tea-openapi>=0.3.11 +alibabacloud-tea-util>=0.3.13 # 任务队列 celery>=5.3.0 @@ -43,4 +41,4 @@ pydantic-settings>=2.0.0 python-dotenv>=1.0.0 loguru>=0.7.0 tenacity>=8.2.0 -httpx>=0.24.0 \ No newline at end of file +httpx>=0.24.0 diff --git a/tests/test_embedding.py b/tests/test_embedding.py index 001ab60..76106b9 100644 --- a/tests/test_embedding.py +++ b/tests/test_embedding.py @@ -1,185 +1,197 @@ -# tests/test_embedding.py -"""嵌入和分块测试""" +"""新架构下的文档编排与 embedding 边界测试。""" -import pytest -from loguru import logger -import sys -import os +from __future__ import annotations -PROJECT_ROOT = os.path.dirname(os.path.dirname(__file__)) -sys.path.insert(0, os.path.join(PROJECT_ROOT, "backend")) +from dataclasses import dataclass -from app.services.embedding.text_chunker import RegulationChunker, TextChunk, ChunkMetadata -from app.services.embedding.bge_m3_embedder import BGEM3Embedder, EmbeddingResult +from app.application.documents.services import DocumentCommandService +from app.domain.documents import Chunk, Document, DocumentStatus, ParsedDocument +from app.shared import bootstrap -class TestRegulationChunker: - """法规分块器测试""" +class FakeRepository: + def __init__(self) -> None: + self.documents: dict[str, Document] = {} - @pytest.fixture - def chunker(self): - """创建分块器实例""" - return RegulationChunker(chunk_size=512) + def create(self, document: Document) -> Document: + self.documents[document.doc_id] = document + return document - @pytest.fixture - def sample_regulation(self): - """示例法规文档""" - return """ -# GB 7258-2017 机动车运行安全技术条件 + def update(self, document: Document) -> Document: + self.documents[document.doc_id] = document + return document -第一章 范围 + def get(self, doc_id: str) -> Document | None: + return self.documents.get(doc_id) -第一条 本标准规定了机动车运行安全技术条件。 + def list(self, limit: int | None = None) -> list[Document]: + values = list(self.documents.values()) + return values[:limit] if limit is not None else values -第二条 本标准适用于在我国道路上行驶的所有机动车。 + def update_status( + self, + doc_id: str, + status: DocumentStatus, + *, + error_message: str = "", + chunk_count: int | None = None, + summary: str | None = None, + summary_latency_ms: int | None = None, + parser_name: str | None = None, + index_name: str | None = None, + metadata: dict | None = None, + ) -> Document | None: + document = self.documents.get(doc_id) + if not document: + return None + document.status = status + document.error_message = error_message + if chunk_count is not None: + document.chunk_count = chunk_count + if summary is not None: + document.summary = summary + if summary_latency_ms is not None: + document.summary_latency_ms = summary_latency_ms + if parser_name is not None: + document.parser_name = parser_name + if index_name is not None: + document.index_name = index_name + if metadata: + document.metadata.update(metadata) + return document -第二章 术语和定义 -第三条 下列术语和定义适用于本标准。 +class FakeBinaryStore: + def __init__(self) -> None: + self.saved: dict[str, bytes] = {} -(一)机动车:以动力装置驱动或者牵引,上道路行驶的供人员乘用或者用于运送物品以及进行工程专项作业的轮式车辆。 + def save(self, *, object_name: str, data: bytes, content_type: str, metadata: dict[str, str] | None = None) -> None: + self.saved[object_name] = data -(二)整车:完整的机动车,包括所有必要的部件和系统。 + def read(self, object_name: str) -> bytes: + return self.saved[object_name] -第三章 技术要求 + def delete(self, object_name: str) -> None: + self.saved.pop(object_name, None) -第四条 机动车应满足以下基本要求: -1. 车辆应具有唯一的产品标识; -2. 车辆结构应安全可靠; -3. 车辆应配备必要的安全装置。 -""" - - def test_chunk_document(self, chunker, sample_regulation): - """测试文档分块""" - chunks = chunker.chunk_document( - sample_regulation, - doc_id="gb7258", - doc_name="GB 7258-2017", - regulation_type="车辆安全" +class FakeParser: + def parse(self, *, file_path: str, doc_id: str, doc_name: str) -> ParsedDocument: + return ParsedDocument( + doc_id=doc_id, + doc_name=doc_name, + structure_nodes=[{"title": "第一章"}], + semantic_blocks=[{"semantic_id": "semantic-1", "text": "法规正文", "section_title": "第一章"}], + vector_chunks=[ + { + "chunk_id": f"{doc_id}-chunk-1", + "semantic_id": "semantic-1", + "chunk_type": "section_text", + "section_title": "第一章", + "section_path": ["第一章"], + "page_start": 1, + "text": "法规正文", + "embedding_text": "标准:测试\n章节:第一章\n\n法规正文", + } + ], + parser_name="fake_parser", ) - # 应该有多个分块 - assert len(chunks) > 3 - # 每个分块应该有内容 - for chunk in chunks: - assert len(chunk.content) > 0 - assert chunk.metadata.doc_id == "gb7258" - - def test_section_detection(self, chunker, sample_regulation): - """测试章节检测""" - chunks = chunker.chunk_document( - sample_regulation, - doc_id="test", - doc_name="测试" - ) - - # 应该检测到章节 - section_numbers = [c.metadata.section_number for c in chunks] - assert any(s for s in section_numbers) # 至少有一个章节编号 - - def test_clause_detection(self, chunker, sample_regulation): - """测试条款检测""" - chunks = chunker.chunk_document( - sample_regulation, - doc_id="test", - doc_name="测试" - ) - - # 应该检测到条款 - clause_numbers = [c.metadata.clause_number for c in chunks] - assert any(c for c in clause_numbers) # 至少有一个条款编号 - - def test_long_clause_split(self, chunker): - """测试长条款分割""" - long_clause = """ -第一条 本条款内容很长,需要进行分割处理。 - -本条款包含以下多项内容: -1. 第一项内容,这是一个非常长的子项,包含了大量的文字描述,需要进行适当的处理。 -2. 第二项内容,这也是一个较长的子项,包含了相关的技术要求和规范说明。 -3. 第三项内容,继续描述相关要求和注意事项,确保文档的完整性和规范性。 -4. 第四项内容,补充说明其他相关事项,保证内容的全面性。 -""" - - chunks = chunker.chunk_document( - long_clause, - doc_id="test", - doc_name="测试" - ) - - # 长条款应该被分割成多个chunk - assert len(chunks) >= 1 - - -class TestBGEM3Embedder: - """BGE-M3嵌入模型测试""" - - @pytest.fixture - def embedder(self): - """创建嵌入模型实例""" - try: - return BGEM3Embedder() - except Exception as e: - pytest.skip(f"嵌入模型加载失败: {e}") - - def test_embed_single(self, embedder): - """测试单文本嵌入""" - text = "这是一条测试文本" - result = embedder.embed_single(text) - - # 应该包含dense和sparse向量 - assert 'dense' in result - assert 'sparse' in result - - # dense向量维度应该是1024 - assert len(result['dense']) == 1024 - - def test_embed_batch(self, embedder): - """测试批量嵌入""" - texts = [ - "第一条 本标准规定了机动车安全要求", - "第二条 机动车应符合技术条件", - "第三条 生产企业应建立管理体系" +class FakeChunkBuilder: + def build(self, *, parsed_document: ParsedDocument, regulation_type: str, version: str) -> list[Chunk]: + return [ + Chunk( + chunk_id=f"{parsed_document.doc_id}-chunk-1", + doc_id=parsed_document.doc_id, + doc_name=parsed_document.doc_name, + content="法规正文", + embedding_text="标准:测试\n章节:第一章\n\n法规正文", + section_title="第一章", + section_path=["第一章"], + page_number=1, + regulation_type=regulation_type, + version=version, + semantic_id="semantic-1", + block_type="section_text", + metadata={"source": "aliyun_vector_chunk"}, + ) ] - result = embedder.embed(texts) - # 应该返回正确数量的向量 - assert len(result.dense_embeddings) == 3 +class FakeEmbeddingProvider: + def __init__(self) -> None: + self.calls: list[list[str]] = [] - # 维度应该是1024 - assert result.dense_embeddings.shape[1] == 1024 + def embed_texts(self, texts: list[str]) -> list[list[float]]: + self.calls.append(texts) + return [[0.1] * 1536 for _ in texts] - def test_embed_empty_list(self, embedder): - """测试空列表嵌入""" - result = embedder.embed([]) - - # 应该返回空结果 - assert len(result.dense_embeddings) == 0 - - def test_similarity(self, embedder): - """测试相似度计算""" - import numpy as np - - texts = [ - "机动车安全标准要求", - "汽车安全技术规范", - "食品安全管理规定" # 不相关文本 - ] - - result = embedder.embed(texts) - - # 计算第一个文本与其他文本的相似度 - query = result.dense_embeddings[0] - docs = result.dense_embeddings[1:] - - similarities = embedder.compute_similarity(query, docs) - - # 相关文档的相似度应该更高 - assert similarities[0] > similarities[1] # 车辆安全 > 食品安全 + def embed_query(self, text: str) -> list[float]: + return [0.2] * 1536 -if __name__ == "__main__": - pytest.main([__file__, "-v"]) +class FakeVectorIndex: + def __init__(self) -> None: + self.upserts: list[tuple[list[Chunk], list[list[float]]]] = [] + + def upsert(self, chunks: list[Chunk], vectors: list[list[float]]) -> int: + self.upserts.append((chunks, vectors)) + return len(chunks) + + def delete_by_document(self, doc_id: str) -> int: + return 0 + + def search(self, query_vector: list[float], top_k: int, filters: str | None = None): + return [] + + def health(self) -> dict: + return {"collection_name": "regulations_dense_1536"} + + +def test_document_command_service_uses_1536_dense_embedding_and_updates_status(): + repository = FakeRepository() + binary_store = FakeBinaryStore() + embedding_provider = FakeEmbeddingProvider() + vector_index = FakeVectorIndex() + service = DocumentCommandService( + document_repository=repository, + binary_store=binary_store, + parser=FakeParser(), + chunk_builder=FakeChunkBuilder(), + embedding_provider=embedding_provider, + vector_index=vector_index, + ) + + result = service.upload_and_process( + doc_id="doc12345", + file_name="test.pdf", + content=b"dummy pdf bytes", + content_type="application/pdf", + doc_name="测试法规", + regulation_type="车辆安全", + version="2026", + generate_summary=False, + ) + + assert result.status == "indexed" + assert result.num_chunks == 1 + assert embedding_provider.calls == [["标准:测试\n章节:第一章\n\n法规正文"]] + assert len(vector_index.upserts) == 1 + stored = repository.get("doc12345") + assert stored is not None + assert stored.status == DocumentStatus.INDEXED + assert stored.chunk_count == 1 + assert stored.parser_name == "fake_parser" + assert stored.index_name == "regulations_dense_1536" + + +def test_bootstrap_defaults_to_local_parser_and_chunk_builder(): + bootstrap.get_parser.cache_clear() + bootstrap.get_chunk_builder.cache_clear() + + parser = bootstrap.get_parser() + chunk_builder = bootstrap.get_chunk_builder() + + assert parser.__class__.__name__ == "LocalDocumentParser" + assert chunk_builder.__class__.__name__ == "LocalRegulationChunkBuilder" diff --git a/tests/test_milvus.py b/tests/test_milvus.py index 6f5b3c4..c0a5f0f 100644 --- a/tests/test_milvus.py +++ b/tests/test_milvus.py @@ -1,137 +1,127 @@ -# tests/test_milvus.py -"""Milvus集成测试""" +"""新架构下的检索与 Milvus dense-only 约定测试。""" -import pytest -from loguru import logger -import sys -import os +from __future__ import annotations -PROJECT_ROOT = os.path.dirname(os.path.dirname(__file__)) -sys.path.insert(0, os.path.join(PROJECT_ROOT, "backend")) - -from app.services.storage.milvus_client import MilvusClient, SearchResult -from app.services.embedding.bge_m3_embedder import BGEM3Embedder -from app.config.settings import settings +from app.application.agent.services import AgentConversationService +from app.application.knowledge.services import KnowledgeRetrievalService +from app.domain.conversation.models import AnswerResult, AnswerSource, ConversationSession +from app.domain.retrieval import RetrievalQuery, RetrievedChunk -class TestMilvusConnection: - """Milvus连接测试""" +class FakeRetriever: + def __init__(self) -> None: + self.queries: list[RetrievalQuery] = [] - def test_connection(self): - """测试Milvus连接""" - client = MilvusClient() - - result = client.connect() - assert result == True - - client.disconnect() - - def test_create_collection(self): - """测试创建Collection""" - client = MilvusClient() - client.connect() - - result = client.create_collection(recreate=True) - assert result == True - - # 检查Collection是否存在 - stats = client.get_collection_stats() - assert stats["name"] == settings.milvus_collection - - client.disconnect() - - -class TestMilvusOperations: - """Milvus操作测试""" - - @pytest.fixture - def client(self): - """创建测试客户端""" - client = MilvusClient() - client.connect() - client.create_collection(recreate=True) - client.load_collection() - yield client - client.disconnect() - - def test_insert_and_search(self, client): - """测试插入和检索""" - from app.services.embedding.text_chunker import TextChunk, ChunkMetadata - - # 创建测试数据 - chunks = [ - TextChunk( - content="第一条 为保障机动车安全技术性能,预防和减少机动车交通事故,保护人身安全,制定本标准。", - metadata=ChunkMetadata( - doc_id="test_doc", - doc_name="测试文档", - chunk_id="test_chunk_1", - clause_number="第一条", - regulation_type="车辆安全" - ) - ), - TextChunk( - content="第二条 本标准适用于在我国道路上行驶的所有机动车。", - metadata=ChunkMetadata( - doc_id="test_doc", - doc_name="测试文档", - chunk_id="test_chunk_2", - clause_number="第二条", - regulation_type="车辆安全" - ) + def retrieve(self, query: RetrievalQuery) -> list[RetrievedChunk]: + self.queries.append(query) + return [ + RetrievedChunk( + chunk_id="chunk-1", + doc_id="doc-1", + doc_name="测试法规", + content="法规正文", + score=0.91, + section_title="第一章", + page_number=1, + metadata={"section_title": "第一章"}, ) ] - # 生成嵌入 - embedder = BGEM3Embedder() - embeddings = embedder.embed([c.content for c in chunks]) + def search(self, query: str, top_k: int, filters: str | None = None) -> list[RetrievedChunk]: + return self.retrieve(RetrievalQuery(query=query, top_k=top_k, filters=filters)) - # 插入数据 - inserted_ids = client.insert_chunks(chunks, embeddings) - assert len(inserted_ids) == 2 - # 执行检索 - query = "机动车安全标准" - query_embedding = embedder.embed_single(query) - - results = client.hybrid_search( - query_dense=query_embedding['dense'].tolist(), - query_sparse=query_embedding['sparse'], - top_k=2 +class FakeAnswerGenerator: + def generate( + self, + *, + query: str, + retrieved_chunks: list[RetrievedChunk], + history: list[dict[str, str]] | None = None, + provider: str | None = None, + model: str | None = None, + prompt_template: str | None = None, + ) -> AnswerResult: + return AnswerResult( + answer=f"回答: {query}", + sources=[ + AnswerSource( + doc_id=item.doc_id, + doc_name=item.doc_name, + chunk_id=item.chunk_id, + section_title=item.section_title, + page_number=item.page_number, + score=item.score, + content=item.content, + metadata=item.metadata, + ) + for item in retrieved_chunks + ], + model=model or "deepseek-v4-flash", + latency_ms=12, + retrieved_count=len(retrieved_chunks), + context_tokens=128, ) - assert len(results) > 0 - assert "机动车" in results[0].content or "安全" in results[0].content + def stream_generate(self, **kwargs): + sources = [source.__dict__ for source in self.generate(**kwargs).sources] + yield {"event": "sources", "data": sources} + yield {"event": "content", "data": "流式回答"} + yield {"event": "done", "data": {"retrieved_count": 1}} -class TestEmbedding: - """嵌入模型测试""" +class FakeConversationStore: + def __init__(self) -> None: + self.sessions: dict[str, ConversationSession] = {} - def test_embed_single_text(self): - """测试单文本嵌入""" - embedder = BGEM3Embedder() + def create_session(self, metadata: dict | None = None) -> ConversationSession: + session = ConversationSession(session_id="sess-1", created_at=1, updated_at=1, metadata=metadata or {}) + self.sessions[session.session_id] = session + return session - result = embedder.embed_single("这是一条测试文本") + def get_session(self, session_id: str) -> ConversationSession | None: + return self.sessions.get(session_id) - assert 'dense' in result - assert 'sparse' in result - assert len(result['dense']) == 1024 # BGE-M3默认维度 + def save_message(self, session_id: str, *, role: str, content: str, sources: list[dict] | None = None): + session = self.sessions.get(session_id) + if session is None: + return None + session.messages.append(type("Msg", (), {"role": role, "content": content})()) + return session - def test_embed_batch(self): - """测试批量嵌入""" - embedder = BGEM3Embedder() + def delete_session(self, session_id: str) -> bool: + return self.sessions.pop(session_id, None) is not None - texts = [ - "第一条 本标准规定了机动车安全要求", - "第二条 机动车应符合以下技术条件", - "第三条 生产企业应建立质量管理体系" - ] - - result = embedder.embed(texts) - - assert len(result.dense_embeddings) == 3 - assert result.dense_embeddings.shape[1] == 1024 + def list_sessions(self) -> list[dict]: + return [{"session_id": key, "message_count": len(value.messages), "created_at": value.created_at, "updated_at": value.updated_at} for key, value in self.sessions.items()] -if __name__ == "__main__": - pytest.main([__file__, "-v"]) +def test_knowledge_retrieval_service_builds_retrieval_query(): + retriever = FakeRetriever() + service = KnowledgeRetrievalService(retriever=retriever) + + results = service.retrieve(query="机动车安全", top_k=3, filters='doc_name == "测试法规"') + + assert len(results) == 1 + assert retriever.queries[0].query == "机动车安全" + assert retriever.queries[0].top_k == 3 + assert retriever.queries[0].filters == 'doc_name == "测试法规"' + + +def test_agent_conversation_service_reuses_shared_retrieval_service(): + retriever = FakeRetriever() + retrieval_service = KnowledgeRetrievalService(retriever=retriever) + conversation_store = FakeConversationStore() + service = AgentConversationService( + retrieval_service=retrieval_service, + answer_generator=FakeAnswerGenerator(), + conversation_store=conversation_store, + ) + + session_id, result = service.chat(query="问一个问题", top_k=2, model="qwen3.5-flash") + + assert session_id == "sess-1" + assert result.answer == "回答: 问一个问题" + assert result.retrieved_count == 1 + assert retriever.queries[0].top_k == 2 + assert len(conversation_store.sessions["sess-1"].messages) == 2 diff --git a/tests/test_parser.py b/tests/test_parser.py index 549fdfb..7535abd 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -1,119 +1,236 @@ -# tests/test_parser.py -"""文档解析测试""" +"""API contract checks for the migrated backend architecture.""" -import pytest -from loguru import logger -import sys -import os +from __future__ import annotations -PROJECT_ROOT = os.path.dirname(os.path.dirname(__file__)) -sys.path.insert(0, os.path.join(PROJECT_ROOT, "backend")) +from dataclasses import dataclass -from app.services.parser.pdf_parser import PDFParser, parse_pdf_to_markdown -from app.services.parser.docx_parser import DocxParser, parse_docx_to_markdown -from app.services.parser.mineru_parser import MinerUParser, ParserOrchestrator +from fastapi.testclient import TestClient + +from app.api.main import app +from app.application.documents import DocumentProcessResult +from app.domain.conversation.models import AnswerResult, AnswerSource, ConversationSession +from app.domain.documents import Document, DocumentStatus +from app.domain.retrieval import RetrievedChunk -class TestPDFParser: - """PDF解析测试""" - - def test_parser_initialization(self): - """测试PDF解析器初始化""" - parser = PDFParser() - assert parser is not None - - def test_parse_sample_pdf(self): - """测试解析示例PDF(如果有)""" - # 如果有示例PDF文件,可以在此测试 - sample_pdf = os.path.join(os.path.dirname(__file__), "sample.pdf") - - if os.path.exists(sample_pdf): - parser = PDFParser() - result = parser.parse(sample_pdf) - - assert result.total_pages > 0 - assert len(result.pages) > 0 - assert len(result.markdown_text) > 0 +@dataclass +class FakeMessage: + role: str + content: str -class TestDocxParser: - """Word文档解析测试""" - - def test_parser_initialization(self): - """测试Word解析器初始化""" - parser = DocxParser() - assert parser is not None - - def test_parse_sample_docx(self): - """测试解析示例DOCX""" - sample_docx = os.path.join(os.path.dirname(__file__), "sample.docx") - - if os.path.exists(sample_docx): - parser = DocxParser() - result = parser.parse(sample_docx) - - assert len(result.paragraphs) > 0 - assert len(result.markdown_text) > 0 - - -class TestChunker: - """分块器测试""" - - def test_chunker_initialization(self): - """测试分块器初始化""" - from app.services.embedding.text_chunker import RegulationChunker - - chunker = RegulationChunker(chunk_size=512) - assert chunker is not None - - def test_chunk_sample_text(self): - """测试分块示例文本""" - from app.services.embedding.text_chunker import RegulationChunker - - sample_text = """ -# 测试法规文档 - -第一章 总则 - -第一条 为规范某项行为,制定本规定。 - -第二条 本规定适用于相关主体。 - -第二章 具体要求 - -第三条 相关主体应当遵守以下要求: - -(一)建立管理制度; -(二)配备专业人员; -(三)定期进行检查。 -""" - - chunker = RegulationChunker(chunk_size=256) - chunks = chunker.chunk_document( - sample_text, - doc_id="test", - doc_name="测试法规" +class FakeDocumentCommandService: + def upload_and_process(self, **kwargs) -> DocumentProcessResult: + return DocumentProcessResult( + doc_id="doc-api-1", + doc_name=kwargs.get("doc_name") or "test.pdf", + status="indexed", + message="处理成功", + num_chunks=2, + summary="", + summary_latency_ms=0, ) - assert len(chunks) > 0 - # 验证分块包含章节信息 - has_section = any(c.metadata.section_number for c in chunks) - assert has_section +class FakeDocumentQueryService: + def get(self, doc_id: str) -> Document | None: + if doc_id != "doc-api-1": + return None + return Document( + doc_id=doc_id, + doc_name="测试法规", + file_name="test.pdf", + object_name="doc-api-1/test.pdf", + content_type="application/pdf", + size_bytes=12, + status=DocumentStatus.INDEXED, + chunk_count=2, + ) + + def list_documents(self, limit: int | None = None) -> list[Document]: + documents = [ + Document( + doc_id="doc-api-1", + doc_name="测试法规", + file_name="test.pdf", + object_name="doc-api-1/test.pdf", + content_type="application/pdf", + size_bytes=12, + status=DocumentStatus.INDEXED, + chunk_count=2, + ) + ] + return documents[:limit] if limit is not None else documents + + def download(self, doc_id: str) -> tuple[Document, bytes]: + document = self.get(doc_id) + if document is None: + raise FileNotFoundError(doc_id) + return document, b"pdf-bytes" -class TestFullPipeline: - """完整流程测试""" - - def test_pipeline_without_files(self): - """测试流程初始化(无文件)""" - from app.services.document_processor import DocumentProcessor - - processor = DocumentProcessor() - assert processor is not None - - processor.close() +class FakeRetrievalService: + def retrieve(self, *, query: str, top_k: int, filters: str | None = None) -> list[RetrievedChunk]: + return [ + RetrievedChunk( + chunk_id="chunk-1", + doc_id="doc-api-1", + doc_name="测试法规", + content=f"关于 {query} 的法规内容", + score=0.92, + section_title="第一章", + page_number=1, + metadata={"filters": filters or ""}, + ) + ] -if __name__ == "__main__": - pytest.main([__file__, "-v"]) +class FakeConversationStore: + def __init__(self) -> None: + self.session = ConversationSession( + session_id="sess-1", + created_at=1, + updated_at=1, + messages=[FakeMessage(role="user", content="历史问题"), FakeMessage(role="assistant", content="历史回答")], + ) + + def get_session(self, session_id: str) -> ConversationSession | None: + if session_id == "sess-1": + return self.session + return None + + def delete_session(self, session_id: str) -> bool: + return session_id == "sess-1" + + def list_sessions(self) -> list[dict]: + return [{"session_id": "sess-1", "message_count": len(self.session.messages), "created_at": 1, "updated_at": 1}] + + +class FakeAgentConversationService: + def ask(self, **kwargs): + result = AnswerResult( + answer="这是基于法规上下文的回答", + sources=[ + AnswerSource( + doc_id="doc-api-1", + doc_name="测试法规", + chunk_id="chunk-1", + section_title="第一章", + page_number=1, + score=0.92, + content="法规原文", + metadata={"section_title": "第一章"}, + ) + ], + model=kwargs.get("model") or "qwen3.5-flash", + latency_ms=11, + retrieved_count=1, + context_tokens=128, + truncated=False, + error=None, + ) + return None, result + + def chat(self, **kwargs): + result = AnswerResult( + answer="会话回答", + sources=[], + model=kwargs.get("model") or "qwen3.5-flash", + latency_ms=12, + retrieved_count=1, + context_tokens=64, + truncated=False, + error=None, + ) + return "sess-1", result + + def stream_chat(self, **kwargs): + return "sess-1", iter( + [ + {"event": "status", "data": "正在处理"}, + {"event": "content", "data": "流式回答"}, + {"event": "done", "data": {"retrieved_count": 1}}, + ] + ) + + +def test_documents_upload_contract_preserved(monkeypatch): + from app.api.routes import documents + + monkeypatch.setattr(documents, "get_document_command_service", lambda: FakeDocumentCommandService()) + + client = TestClient(app) + response = client.post( + "/api/v1/documents/upload", + files={"file": ("test.pdf", b"dummy-pdf", "application/pdf")}, + data={"doc_name": "测试法规", "regulation_type": "车辆安全", "version": "2026"}, + ) + + assert response.status_code == 200 + payload = response.json() + assert payload["doc_id"] == "doc-api-1" + assert payload["doc_name"] == "测试法规" + assert payload["status"] == "indexed" + assert payload["num_chunks"] == 2 + + +def test_documents_query_contract_preserved(monkeypatch): + from app.api.routes import documents + + monkeypatch.setattr(documents, "get_document_query_service", lambda: FakeDocumentQueryService()) + + client = TestClient(app) + + status_response = client.get("/api/v1/documents/status/doc-api-1") + assert status_response.status_code == 200 + assert status_response.json()["status"] == "indexed" + + list_response = client.get("/api/v1/documents/list") + assert list_response.status_code == 200 + assert list_response.json()["total"] == 1 + + download_response = client.get("/api/v1/documents/download/doc-api-1") + assert download_response.status_code == 200 + assert download_response.content == b"pdf-bytes" + + +def test_knowledge_retrieval_contract_preserved(monkeypatch): + from app.api.routes import knowledge + + monkeypatch.setattr(knowledge, "get_retrieval_service", lambda: FakeRetrievalService()) + + client = TestClient(app) + response = client.post( + "/api/v1/knowledge/retrieval", + json={"query": "机动车安全", "top_k": 3, "filters": 'doc_id == "doc-api-1"'}, + ) + + assert response.status_code == 200 + payload = response.json() + assert payload["query"] == "机动车安全" + assert payload["total"] == 1 + assert payload["results"][0]["metadata"]["doc_id"] == "doc-api-1" + assert payload["results"][0]["metadata"]["section_title"] == "第一章" + + +def test_agent_ask_and_stream_contract_preserved(monkeypatch): + from app.api.routes import agent + + store = FakeConversationStore() + monkeypatch.setattr(agent, "get_agent_conversation_service", lambda: FakeAgentConversationService()) + monkeypatch.setattr(agent, "get_conversation_store", lambda: store) + + client = TestClient(app) + + ask_response = client.post("/api/v1/agent/ask", json={"query": "这个法规要求什么?"}) + assert ask_response.status_code == 200 + ask_payload = ask_response.json() + assert ask_payload["answer"] == "这是基于法规上下文的回答" + assert ask_payload["retrieved_count"] == 1 + assert ask_payload["sources"][0]["doc_id"] == "doc-api-1" + + stream_response = client.get("/api/v1/agent/chat/stream", params={"query": "继续说明"}) + assert stream_response.status_code == 200 + assert stream_response.headers["content-type"].startswith("text/event-stream") + assert "event: session" in stream_response.text + assert "event: content" in stream_response.text diff --git a/tests/verify_mvp.py b/tests/verify_mvp.py index bd7bf31..b4adfae 100644 --- a/tests/verify_mvp.py +++ b/tests/verify_mvp.py @@ -1,223 +1,211 @@ """ -MVP功能验证脚本 +Post-migration backend smoke checks. -用于验证完整的文档处理流程: -1. PDF/DOCX解析 -2. 智能分块 -3. 向量嵌入 -4. Milvus入库 -5. 混合检索 - -使用方法: -1. 首先启动Milvus: docker-compose up -d -2. 运行此脚本: python verify_mvp.py +Purpose: +1. Verify the new architecture modules can be imported +2. Verify migration-critical config matches the RFC +3. Verify external dependencies when they are available +4. Optionally verify the real ingest path with a sample document """ -import os +from __future__ import annotations + +import argparse import sys -import time +from pathlib import Path -PROJECT_ROOT = os.path.dirname(os.path.dirname(__file__)) -sys.path.insert(0, os.path.join(PROJECT_ROOT, "backend")) +PROJECT_ROOT = Path(__file__).resolve().parent.parent +sys.path.insert(0, str(PROJECT_ROOT / "backend")) from loguru import logger -from app.config.logging import setup_logging -from app.services.document_processor import DocumentProcessor, ProcessingResult -from app.services.storage.milvus_client import MilvusClient -from app.config.settings import settings -# 设置日志 +from app.config.logging import setup_logging +from app.config.settings import settings +from app.shared.bootstrap import ( + get_document_command_service, + get_retrieval_service, + get_vector_index, +) + setup_logging(level="INFO") -def verify_milvus_connection(): - """验证Milvus连接""" - logger.info("=" * 50) - logger.info("Step 1: 验证Milvus连接") - logger.info("=" * 50) - - client = MilvusClient() - - try: - result = client.connect() - if result: - logger.success("Milvus连接成功") - - # 创建Collection - client.create_collection(recreate=True) - stats = client.get_collection_stats() - logger.info(f"Collection信息: {stats}") - - client.disconnect() - return True - else: - logger.error("Milvus连接失败,请检查docker-compose是否启动") - return False - - except Exception as e: - logger.error(f"Milvus连接异常: {e}") - logger.info("请先启动Milvus: cd docker && docker-compose up -d") - return False - - -def verify_embedding_model(): - """验证嵌入模型""" - logger.info("=" * 50) - logger.info("Step 2: 验证BGE-M3嵌入模型") - logger.info("=" * 50) - - try: - from app.services.embedding.bge_m3_embedder import BGEM3Embedder - - embedder = BGEM3Embedder() - logger.success("嵌入模型加载成功") - - # 测试嵌入 - test_text = "这是一条测试文本,用于验证嵌入模型功能" - result = embedder.embed_single(test_text) - - logger.info(f"Dense向量维度: {len(result['dense'])}") - logger.info(f"Sparse向量词数: {len(result['sparse'])}") - - return True - - except Exception as e: - logger.error(f"嵌入模型验证失败: {e}") - logger.info("请确保已安装FlagEmbedding: pip install FlagEmbedding") - return False - - -def verify_sample_document(): - """验证示例文档处理""" - logger.info("=" * 50) - logger.info("Step 3: 验证文档处理流程") - logger.info("=" * 50) - - # 使用内置的示例文本(无需外部文件) - sample_text = """ -# GB 7258-2017 机动车运行安全技术条件 - -第一章 范围 - -第一条 本标准规定了机动车运行安全技术条件,适用于在我国道路上行驶的所有机动车。 - -第二条 本标准包括整车、发动机、传动系、行驶系、制动系、照明与信号装置等技术要求。 - -第二章 术语和定义 - -第三条 下列术语和定义适用于本标准: - -(一)机动车:以动力装置驱动或者牵引,上道路行驶的供人员乘用或者用于运送物品的轮式车辆。 - -(二)整车产品:完整的机动车产品,包括所有必要的部件和系统。 - -第三章 整车技术要求 - -第四条 机动车整车应满足以下基本技术要求: - -1. 车辆外廓尺寸应符合规定限值; -2. 车辆应具有唯一的产品标识; -3. 车辆结构应安全可靠,各部件连接牢固。 - -第五条 车辆应配备必要的安全装置,包括: -- 制动系统 -- 照明与信号装置 -- 安全带 -- 灭火器 -""" - - try: - from app.services.embedding.text_chunker import RegulationChunker - from app.services.embedding.bge_m3_embedder import BGEM3Embedder - from app.services.storage.milvus_client import MilvusClient - - # 1. 分块 - logger.info("测试分块...") - chunker = RegulationChunker(chunk_size=256) - chunks = chunker.chunk_document( - sample_text, - doc_id="gb7258_test", - doc_name="GB 7258-2017 测试", - regulation_type="车辆安全" - ) - logger.success(f"分块完成,共{len(chunks)}个chunk") - - # 2. 嵌入 - logger.info("测试嵌入...") - embedder = BGEM3Embedder() - embeddings = embedder.embed([c.content for c in chunks]) - logger.success(f"嵌入完成,向量数: {len(embeddings.dense_embeddings)}") - - # 3. 入库 - logger.info("测试入库...") - client = MilvusClient() - client.connect() - client.create_collection(recreate=False) - client.load_collection() - - inserted_ids = client.insert_chunks(chunks, embeddings) - logger.success(f"入库完成,共{len(inserted_ids)}条记录") - - # 4. 检索 - logger.info("测试检索...") - query = "机动车安全技术要求" - query_emb = embedder.embed_single(query) - - results = client.hybrid_search( - query_dense=query_emb['dense'].tolist(), - query_sparse=query_emb['sparse'], - top_k=3 - ) - logger.success(f"检索完成,返回{len(results)}条结果") - - for i, r in enumerate(results): - logger.info(f"结果{i+1}: 分数={r.score:.4f}, 内容={r.content[:50]}...") - - client.disconnect() - return True - - except Exception as e: - logger.error(f"文档处理验证失败: {e}") - return False - - -def main(): - """主验证流程""" - logger.info("\n" + "=" * 60) - logger.info("AI+合规智能中枢 MVP功能验证") +def verify_service_wiring() -> bool: + """Verify the new module layout and service entrypoints can be imported.""" + logger.info("=" * 60) + logger.info("Step 1: verify module wiring") logger.info("=" * 60) - results = [] + try: + from app.api.main import app + from app.application.agent import AgentConversationService + from app.application.documents import DocumentCommandService, DocumentQueryService + from app.application.knowledge import KnowledgeRetrievalService + from app.shared import bootstrap - # 1. Milvus连接验证 - results.append(("Milvus连接", verify_milvus_connection())) + assert app is not None + assert DocumentCommandService is not None + assert DocumentQueryService is not None + assert KnowledgeRetrievalService is not None + assert AgentConversationService is not None + assert bootstrap is not None + logger.success("module wiring ok") + return True + except Exception as exc: + logger.error(f"module wiring failed: {exc}") + return False - # 2. 嵌入模型验证 - results.append(("嵌入模型", verify_embedding_model())) - # 3. 文档处理验证 - results.append(("文档处理", verify_sample_document())) +def verify_migration_config() -> bool: + """Verify migration-critical config values.""" + logger.info("=" * 60) + logger.info("Step 2: verify migration config") + logger.info("=" * 60) + + try: + assert settings.embedding_model == "text-embedding-v3" + assert settings.embedding_dim == 1536 + assert settings.milvus_collection == "regulations_dense_1536" + logger.info(f"embedding_model={settings.embedding_model}") + logger.info(f"embedding_dim={settings.embedding_dim}") + logger.info(f"milvus_collection={settings.milvus_collection}") + logger.success("migration config ok") + return True + except Exception as exc: + logger.error(f"migration config mismatch: {exc}") + return False + + +def verify_minio_connection() -> bool: + """Verify MinIO connectivity for the binary store path.""" + logger.info("=" * 60) + logger.info("Step 3: verify MinIO connection") + logger.info("=" * 60) + + try: + binary_store = get_document_command_service().binary_store + assert binary_store is not None + logger.success("MinIO connection ok") + return True + except Exception as exc: + logger.error(f"MinIO connection failed: {exc}") + logger.info("start MinIO first or update .env storage settings") + return False + + +def verify_milvus_connection() -> bool: + """Verify dense-only Milvus adapter connectivity.""" + logger.info("=" * 60) + logger.info("Step 4: verify Milvus connection") + logger.info("=" * 60) + + try: + health = get_vector_index().health() + logger.info(f"Milvus health: {health}") + logger.success("Milvus connection ok") + return True + except Exception as exc: + logger.error(f"Milvus connection failed: {exc}") + logger.info("start Milvus first or update .env vector settings") + return False + + +def verify_ingest_pipeline(sample_file: Path) -> bool: + """Verify upload -> parse -> embed -> index using a real file.""" + logger.info("=" * 60) + logger.info("Step 5: verify real ingest pipeline") + logger.info("=" * 60) + + if not sample_file.exists(): + logger.error(f"sample file not found: {sample_file}") + return False + + if sample_file.suffix.lower() not in {".pdf", ".doc", ".docx"}: + logger.error("sample file must be PDF, DOC, or DOCX") + return False + + if not settings.alibaba_access_key_id or not settings.alibaba_access_key_secret: + logger.error("missing Aliyun parser credentials") + return False + + try: + result = get_document_command_service().upload_and_process( + file_name=sample_file.name, + content=sample_file.read_bytes(), + content_type=_guess_content_type(sample_file), + doc_name=sample_file.stem, + regulation_type="smoke-test", + version="migration", + generate_summary=False, + ) + logger.info(f"process result: doc_id={result.doc_id}, status={result.status}, chunks={result.num_chunks}") + if result.status != "indexed": + logger.error(f"ingest failed: {result.message}") + return False + + retrieval_results = get_retrieval_service().retrieve( + query=sample_file.stem, + top_k=3, + filters=f'doc_id == "{result.doc_id}"', + ) + logger.info(f"retrieval count: {len(retrieval_results)}") + logger.success("real ingest pipeline ok") + return True + except Exception as exc: + logger.error(f"real ingest pipeline failed: {exc}") + return False + + +def _guess_content_type(sample_file: Path) -> str: + suffix = sample_file.suffix.lower() + if suffix == ".pdf": + return "application/pdf" + if suffix == ".doc": + return "application/msword" + if suffix == ".docx": + return "application/vnd.openxmlformats-officedocument.wordprocessingml.document" + return "application/octet-stream" + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description="Verify the migrated backend path") + parser.add_argument("--sample-file", type=Path, help="Optional PDF/DOC/DOCX for real ingest verification") + return parser.parse_args() + + +def main() -> bool: + args = parse_args() + + results = [ + ("module_wiring", verify_service_wiring()), + ("migration_config", verify_migration_config()), + ("minio_connection", verify_minio_connection()), + ("milvus_connection", verify_milvus_connection()), + ] + + if args.sample_file: + results.append(("real_ingest_pipeline", verify_ingest_pipeline(args.sample_file))) + else: + logger.info("no sample file provided; skip real ingest check") - # 输出结果汇总 logger.info("\n" + "=" * 60) - logger.info("验证结果汇总") + logger.info("check summary") logger.info("=" * 60) all_passed = True for name, passed in results: - status = "✅ 通过" if passed else "❌ 失败" + status = "PASS" if passed else "FAIL" logger.info(f"{name}: {status}") if not passed: all_passed = False if all_passed: - logger.success("\n🎉 所有验证通过!MVP功能正常") + logger.success("all executed checks passed") else: - logger.warning("\n⚠️ 部分验证失败,请检查配置和环境") + logger.warning("some checks failed; inspect environment dependencies") return all_passed if __name__ == "__main__": - success = main() - sys.exit(0 if success else 1) + sys.exit(0 if main() else 1) diff --git a/uv.lock b/uv.lock index acad454..5eb7dac 100644 --- a/uv.lock +++ b/uv.lock @@ -16,33 +16,16 @@ resolution-markers = [ "python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux' and sys_platform != 'win32'", ] -[[package]] -name = "accelerate" -version = "1.13.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "huggingface-hub" }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "packaging" }, - { name = "psutil" }, - { name = "pyyaml" }, - { name = "safetensors" }, - { name = "torch" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ca/14/787e5498cd062640f0f3d92ef4ae4063174f76f9afd29d13fc52a319daae/accelerate-1.13.0.tar.gz", hash = "sha256:d631b4e0f5b3de4aff2d7e9e6857d164810dfc3237d54d017f075122d057b236", size = 402835, upload-time = "2026-03-04T19:34:12.359Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/46/02ac5e262d4af18054b3e922b2baedbb2a03289ee792162de60a865defc5/accelerate-1.13.0-py3-none-any.whl", hash = "sha256:cf1a3efb96c18f7b152eb0fa7490f3710b19c3f395699358f08decca2b8b62e0", size = 383744, upload-time = "2026-03-04T19:34:10.313Z" }, -] - [[package]] name = "ai-compliance-hub" version = "0.1.0" source = { editable = "." } dependencies = [ + { name = "alibabacloud-docmind-api20220711" }, + { name = "alibabacloud-tea-openapi" }, + { name = "alibabacloud-tea-util" }, { name = "celery" }, { name = "fastapi" }, - { name = "flagembedding" }, { name = "httpx" }, { name = "langchain" }, { name = "langchain-milvus" }, @@ -57,16 +40,10 @@ dependencies = [ { name = "python-dotenv" }, { name = "python-multipart" }, { name = "redis" }, - { name = "sentence-transformers" }, { name = "tenacity" }, { name = "uvicorn", extra = ["standard"] }, ] -[package.optional-dependencies] -mineru = [ - { name = "magic-pdf" }, -] - [package.dev-dependencies] dev = [ { name = "isort" }, @@ -76,14 +53,15 @@ dev = [ [package.metadata] requires-dist = [ + { name = "alibabacloud-docmind-api20220711", specifier = ">=1.0.6" }, + { name = "alibabacloud-tea-openapi", specifier = ">=0.3.11" }, + { name = "alibabacloud-tea-util", specifier = ">=0.3.13" }, { name = "celery", specifier = ">=5.3.0" }, { name = "fastapi", specifier = ">=0.100.0" }, - { name = "flagembedding", specifier = ">=1.2.0" }, { name = "httpx", specifier = ">=0.24.0" }, { name = "langchain", specifier = ">=0.1.0" }, { name = "langchain-milvus", specifier = ">=0.1.0" }, { name = "loguru", specifier = ">=0.7.0" }, - { name = "magic-pdf", extras = ["full"], marker = "extra == 'mineru'", specifier = ">=0.6.0" }, { name = "minio", specifier = ">=7.1.0" }, { name = "psycopg2-binary", specifier = ">=2.9.0" }, { name = "pydantic", specifier = ">=2.0.0" }, @@ -94,11 +72,9 @@ requires-dist = [ { name = "python-dotenv", specifier = ">=1.0.0" }, { name = "python-multipart", specifier = ">=0.0.6" }, { name = "redis", specifier = ">=4.5.0" }, - { name = "sentence-transformers", specifier = ">=2.2.0" }, { name = "tenacity", specifier = ">=8.2.0" }, { name = "uvicorn", extras = ["standard"], specifier = ">=0.23.0" }, ] -provides-extras = ["mineru"] [package.metadata.requires-dev] dev = [ @@ -107,6 +83,15 @@ dev = [ { name = "pytest-asyncio", specifier = ">=0.21.0" }, ] +[[package]] +name = "aiofiles" +version = "24.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/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://files.pythonhosted.org/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" @@ -249,6 +234,88 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, ] +[[package]] +name = "alibabacloud-credentials" +version = "1.0.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiofiles" }, + { name = "alibabacloud-credentials-api" }, + { name = "alibabacloud-tea" }, + { name = "apscheduler" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d2/15/2b01b4a6cbed4cc2c8a1c801efec43af945af22fd3ca5f78c932117fd4ce/alibabacloud_credentials-1.0.8.tar.gz", hash = "sha256:364c22abef2d240b259ceadf1ce6800017f19a336729553956928a1edd12e769", size = 40465, upload-time = "2026-03-11T09:13:59.398Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/24/7c47501b24897a1379cd57cc8b8de376161f2487548fc8233b2b74ab25c7/alibabacloud_credentials-1.0.8-py3-none-any.whl", hash = "sha256:66677c3fa54aeb66cfb9cc97da4a787534f38a04d09bbfa0bc6c815fe1af7e28", size = 48799, upload-time = "2026-03-11T09:13:58.113Z" }, +] + +[[package]] +name = "alibabacloud-credentials-api" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a0/87/1d7019d23891897cb076b2f7e3c81ab3c2ba91de3bb067196f675d60d34c/alibabacloud-credentials-api-1.0.0.tar.gz", hash = "sha256:8c340038d904f0218d7214a8f4088c31912bfcf279af2cbc7d9be4897a97dd2f", size = 2330, upload-time = "2025-01-13T05:53:04.931Z" } + +[[package]] +name = "alibabacloud-docmind-api20220711" +version = "1.4.14" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alibabacloud-credentials" }, + { name = "alibabacloud-tea-openapi" }, + { name = "darabonba-core" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d9/4e/ba8ace1903851b70efe05365ae5de9021e33733d1c38a676bebd2d4dfd06/alibabacloud_docmind_api20220711-1.4.14.tar.gz", hash = "sha256:61026b2ce05e8922167288ef3bb745b218c6fb9d644bb387598c44a96d88d641", size = 26452, upload-time = "2026-03-27T03:35:01.535Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/da/914b7b95c5bace933c38b057919db439697e44409aa4818adf0f61a46573/alibabacloud_docmind_api20220711-1.4.14-py3-none-any.whl", hash = "sha256:c562003ae3075f63c9ea1e1f3f74e6a2957b3b5ef44270c56e7d6e6621ce5afb", size = 85754, upload-time = "2026-03-27T03:35:00.31Z" }, +] + +[[package]] +name = "alibabacloud-gateway-spi" +version = "0.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alibabacloud-credentials" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ab/98/d7111245f17935bf72ee9bea60bbbeff2bc42cdfe24d2544db52bc517e1a/alibabacloud_gateway_spi-0.0.3.tar.gz", hash = "sha256:10d1c53a3fc5f87915fbd6b4985b98338a776e9b44a0263f56643c5048223b8b", size = 4249, upload-time = "2025-02-23T16:29:54.222Z" } + +[[package]] +name = "alibabacloud-tea" +version = "0.4.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9a/7d/b22cb9a0d4f396ee0f3f9d7f26b76b9ed93d4101add7867a2c87ed2534f5/alibabacloud-tea-0.4.3.tar.gz", hash = "sha256:ec8053d0aa8d43ebe1deb632d5c5404339b39ec9a18a0707d57765838418504a", size = 8785, upload-time = "2025-03-24T07:34:42.958Z" } + +[[package]] +name = "alibabacloud-tea-openapi" +version = "0.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alibabacloud-credentials" }, + { name = "alibabacloud-gateway-spi" }, + { name = "alibabacloud-tea-util" }, + { name = "cryptography" }, + { name = "darabonba-core" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/30/93/138bcdc8fc596add73e37cf2073798f285284d1240bda9ee02f9384fc6be/alibabacloud_tea_openapi-0.4.4.tar.gz", hash = "sha256:1b0917bc03cd49417da64945e92731716d53e2eb8707b235f54e45b7473221ce", size = 21960, upload-time = "2026-03-26T10:16:16.792Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/5a/6bfc4506438c1809c486f66217ad11eab78157192b3d5707b4e2f4212f6c/alibabacloud_tea_openapi-0.4.4-py3-none-any.whl", hash = "sha256:cea6bc1fe35b0319a8752cb99eb0ecb0dab7ca1a71b99c12970ba0867410995f", size = 26236, upload-time = "2026-03-26T10:16:15.861Z" }, +] + +[[package]] +name = "alibabacloud-tea-util" +version = "0.3.14" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alibabacloud-tea" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/ee/ea90be94ad781a5055db29556744681fc71190ef444ae53adba45e1be5f3/alibabacloud_tea_util-0.3.14.tar.gz", hash = "sha256:708e7c9f64641a3c9e0e566365d2f23675f8d7c2a3e2971d9402ceede0408cdb", size = 7515, upload-time = "2025-11-19T06:01:08.504Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/9e/c394b4e2104766fb28a1e44e3ed36e4c7773b4d05c868e482be99d5635c9/alibabacloud_tea_util-0.3.14-py3-none-any.whl", hash = "sha256:10d3e5c340d8f7ec69dd27345eb2fc5a1dab07875742525edf07bbe86db93bfe", size = 6697, upload-time = "2025-11-19T06:01:07.355Z" }, +] + [[package]] name = "amqp" version = "5.3.1" @@ -293,6 +360,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" }, ] +[[package]] +name = "apscheduler" +version = "3.11.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "tzlocal" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/07/12/3e4389e5920b4c1763390c6d371162f3784f86f85cd6d6c1bfe68eef14e2/apscheduler-3.11.2.tar.gz", hash = "sha256:2a9966b052ec805f020c8c4c3ae6e6a06e24b1bf19f2e11d91d8cca0473eef41", size = 108683, upload-time = "2025-12-22T00:39:34.884Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/64/2e54428beba8d9992aa478bb8f6de9e4ecaa5f8f513bcfd567ed7fb0262d/apscheduler-3.11.2-py3-none-any.whl", hash = "sha256:ce005177f741409db4e4dd40a7431b76feb856b9dd69d57e0da49d6715bfd26d", size = 64439, upload-time = "2025-12-22T00:39:33.303Z" }, +] + [[package]] name = "argon2-cffi" version = "25.1.0" @@ -368,19 +447,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a0/59/76ab57e3fe74484f48a53f8e337171b4a2349e506eabe136d7e01d059086/backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5", size = 12313, upload-time = "2025-07-02T02:27:14.263Z" }, ] -[[package]] -name = "beautifulsoup4" -version = "4.14.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "soupsieve" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" }, -] - [[package]] name = "billiard" version = "4.2.4" @@ -390,92 +456,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cb/87/8bab77b323f16d67be364031220069f79159117dd5e43eeb4be2fef1ac9b/billiard-4.2.4-py3-none-any.whl", hash = "sha256:525b42bdec68d2b983347ac312f892db930858495db601b5836ac24e6477cde5", size = 87070, upload-time = "2025-11-30T13:28:47.016Z" }, ] -[[package]] -name = "boto3" -version = "1.43.7" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "botocore" }, - { name = "jmespath" }, - { name = "s3transfer" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6b/0d/67ebf496fe061397f7eb907504e950fe6d2fa5945fd05891f3033376e471/boto3-1.43.7.tar.gz", hash = "sha256:b1e4b40f4a828c67291b12ebefd17d87a57321101e4a0c969b2f593a0310f343", size = 113170, upload-time = "2026-05-13T19:35:48.556Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/40/87/68fdfc7eaed69c4c77de1b9f1ddbd52e529510161baf9b2bf863be07aad1/boto3-1.43.7-py3-none-any.whl", hash = "sha256:7060f603ca0f645153ee2244506db4db5968a858cd513399d8df70637c362159", size = 140524, upload-time = "2026-05-13T19:35:44.879Z" }, -] - -[[package]] -name = "botocore" -version = "1.43.7" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jmespath" }, - { name = "python-dateutil" }, - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ed/be/59144884fa71908e2ac389cfe0fd2ebe8e8adb47bcc994188eb59967406a/botocore-1.43.7.tar.gz", hash = "sha256:abbbc623c52dce86ea9d4534d35e2d6ce447d98edfdaced1695ee0278d6063e3", size = 15350131, upload-time = "2026-05-13T19:35:33.106Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/19/f3/a9beaae1cda959fa438b3937fc995d82b2a08cb117c67c31547580f19849/botocore-1.43.7-py3-none-any.whl", hash = "sha256:e93f25dc186a9de033c87128c0f2016aedd74aea9057d918bfc0703a946b1ad1", size = 15031637, upload-time = "2026-05-13T19:35:27.288Z" }, -] - -[[package]] -name = "brotli" -version = "1.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f7/16/c92ca344d646e71a43b8bb353f0a6490d7f6e06210f8554c8f874e454285/brotli-1.2.0.tar.gz", hash = "sha256:e310f77e41941c13340a95976fe66a8a95b01e783d430eeaf7a2f87e0a57dd0a", size = 7388632, upload-time = "2025-11-05T18:39:42.86Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/64/10/a090475284fc4a71aed40a96f32e44a7fe5bda39687353dd977720b211b6/brotli-1.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3b90b767916ac44e93a8e28ce6adf8d551e43affb512f2377c732d486ac6514e", size = 863089, upload-time = "2025-11-05T18:38:01.181Z" }, - { url = "https://files.pythonhosted.org/packages/03/41/17416630e46c07ac21e378c3464815dd2e120b441e641bc516ac32cc51d2/brotli-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6be67c19e0b0c56365c6a76e393b932fb0e78b3b56b711d180dd7013cb1fd984", size = 445442, upload-time = "2025-11-05T18:38:02.434Z" }, - { url = "https://files.pythonhosted.org/packages/24/31/90cc06584deb5d4fcafc0985e37741fc6b9717926a78674bbb3ce018957e/brotli-1.2.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0bbd5b5ccd157ae7913750476d48099aaf507a79841c0d04a9db4415b14842de", size = 1532658, upload-time = "2025-11-05T18:38:03.588Z" }, - { url = "https://files.pythonhosted.org/packages/62/17/33bf0c83bcbc96756dfd712201d87342732fad70bb3472c27e833a44a4f9/brotli-1.2.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3f3c908bcc404c90c77d5a073e55271a0a498f4e0756e48127c35d91cf155947", size = 1631241, upload-time = "2025-11-05T18:38:04.582Z" }, - { url = "https://files.pythonhosted.org/packages/48/10/f47854a1917b62efe29bc98ac18e5d4f71df03f629184575b862ef2e743b/brotli-1.2.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1b557b29782a643420e08d75aea889462a4a8796e9a6cf5621ab05a3f7da8ef2", size = 1424307, upload-time = "2025-11-05T18:38:05.587Z" }, - { url = "https://files.pythonhosted.org/packages/e4/b7/f88eb461719259c17483484ea8456925ee057897f8e64487d76e24e5e38d/brotli-1.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:81da1b229b1889f25adadc929aeb9dbc4e922bd18561b65b08dd9343cfccca84", size = 1488208, upload-time = "2025-11-05T18:38:06.613Z" }, - { url = "https://files.pythonhosted.org/packages/26/59/41bbcb983a0c48b0b8004203e74706c6b6e99a04f3c7ca6f4f41f364db50/brotli-1.2.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ff09cd8c5eec3b9d02d2408db41be150d8891c5566addce57513bf546e3d6c6d", size = 1597574, upload-time = "2025-11-05T18:38:07.838Z" }, - { url = "https://files.pythonhosted.org/packages/8e/e6/8c89c3bdabbe802febb4c5c6ca224a395e97913b5df0dff11b54f23c1788/brotli-1.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a1778532b978d2536e79c05dac2d8cd857f6c55cd0c95ace5b03740824e0e2f1", size = 1492109, upload-time = "2025-11-05T18:38:08.816Z" }, - { url = "https://files.pythonhosted.org/packages/ed/9a/4b19d4310b2dbd545c0c33f176b0528fa68c3cd0754e34b2f2bcf56548ae/brotli-1.2.0-cp310-cp310-win32.whl", hash = "sha256:b232029d100d393ae3c603c8ffd7e3fe6f798c5e28ddca5feabb8e8fdb732997", size = 334461, upload-time = "2025-11-05T18:38:10.729Z" }, - { url = "https://files.pythonhosted.org/packages/ac/39/70981d9f47705e3c2b95c0847dfa3e7a37aa3b7c6030aedc4873081ed005/brotli-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:ef87b8ab2704da227e83a246356a2b179ef826f550f794b2c52cddb4efbd0196", size = 369035, upload-time = "2025-11-05T18:38:11.827Z" }, - { url = "https://files.pythonhosted.org/packages/7a/ef/f285668811a9e1ddb47a18cb0b437d5fc2760d537a2fe8a57875ad6f8448/brotli-1.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:15b33fe93cedc4caaff8a0bd1eb7e3dab1c61bb22a0bf5bdfdfd97cd7da79744", size = 863110, upload-time = "2025-11-05T18:38:12.978Z" }, - { url = "https://files.pythonhosted.org/packages/50/62/a3b77593587010c789a9d6eaa527c79e0848b7b860402cc64bc0bc28a86c/brotli-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:898be2be399c221d2671d29eed26b6b2713a02c2119168ed914e7d00ceadb56f", size = 445438, upload-time = "2025-11-05T18:38:14.208Z" }, - { url = "https://files.pythonhosted.org/packages/cd/e1/7fadd47f40ce5549dc44493877db40292277db373da5053aff181656e16e/brotli-1.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:350c8348f0e76fff0a0fd6c26755d2653863279d086d3aa2c290a6a7251135dd", size = 1534420, upload-time = "2025-11-05T18:38:15.111Z" }, - { url = "https://files.pythonhosted.org/packages/12/8b/1ed2f64054a5a008a4ccd2f271dbba7a5fb1a3067a99f5ceadedd4c1d5a7/brotli-1.2.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e1ad3fda65ae0d93fec742a128d72e145c9c7a99ee2fcd667785d99eb25a7fe", size = 1632619, upload-time = "2025-11-05T18:38:16.094Z" }, - { url = "https://files.pythonhosted.org/packages/89/5a/7071a621eb2d052d64efd5da2ef55ecdac7c3b0c6e4f9d519e9c66d987ef/brotli-1.2.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:40d918bce2b427a0c4ba189df7a006ac0c7277c180aee4617d99e9ccaaf59e6a", size = 1426014, upload-time = "2025-11-05T18:38:17.177Z" }, - { url = "https://files.pythonhosted.org/packages/26/6d/0971a8ea435af5156acaaccec1a505f981c9c80227633851f2810abd252a/brotli-1.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2a7f1d03727130fc875448b65b127a9ec5d06d19d0148e7554384229706f9d1b", size = 1489661, upload-time = "2025-11-05T18:38:18.41Z" }, - { url = "https://files.pythonhosted.org/packages/f3/75/c1baca8b4ec6c96a03ef8230fab2a785e35297632f402ebb1e78a1e39116/brotli-1.2.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:9c79f57faa25d97900bfb119480806d783fba83cd09ee0b33c17623935b05fa3", size = 1599150, upload-time = "2025-11-05T18:38:19.792Z" }, - { url = "https://files.pythonhosted.org/packages/0d/1a/23fcfee1c324fd48a63d7ebf4bac3a4115bdb1b00e600f80f727d850b1ae/brotli-1.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:844a8ceb8483fefafc412f85c14f2aae2fb69567bf2a0de53cdb88b73e7c43ae", size = 1493505, upload-time = "2025-11-05T18:38:20.913Z" }, - { url = "https://files.pythonhosted.org/packages/36/e5/12904bbd36afeef53d45a84881a4810ae8810ad7e328a971ebbfd760a0b3/brotli-1.2.0-cp311-cp311-win32.whl", hash = "sha256:aa47441fa3026543513139cb8926a92a8e305ee9c71a6209ef7a97d91640ea03", size = 334451, upload-time = "2025-11-05T18:38:21.94Z" }, - { url = "https://files.pythonhosted.org/packages/02/8b/ecb5761b989629a4758c394b9301607a5880de61ee2ee5fe104b87149ebc/brotli-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:022426c9e99fd65d9475dce5c195526f04bb8be8907607e27e747893f6ee3e24", size = 369035, upload-time = "2025-11-05T18:38:22.941Z" }, - { url = "https://files.pythonhosted.org/packages/11/ee/b0a11ab2315c69bb9b45a2aaed022499c9c24a205c3a49c3513b541a7967/brotli-1.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:35d382625778834a7f3061b15423919aa03e4f5da34ac8e02c074e4b75ab4f84", size = 861543, upload-time = "2025-11-05T18:38:24.183Z" }, - { url = "https://files.pythonhosted.org/packages/e1/2f/29c1459513cd35828e25531ebfcbf3e92a5e49f560b1777a9af7203eb46e/brotli-1.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7a61c06b334bd99bc5ae84f1eeb36bfe01400264b3c352f968c6e30a10f9d08b", size = 444288, upload-time = "2025-11-05T18:38:25.139Z" }, - { url = "https://files.pythonhosted.org/packages/3d/6f/feba03130d5fceadfa3a1bb102cb14650798c848b1df2a808356f939bb16/brotli-1.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:acec55bb7c90f1dfc476126f9711a8e81c9af7fb617409a9ee2953115343f08d", size = 1528071, upload-time = "2025-11-05T18:38:26.081Z" }, - { url = "https://files.pythonhosted.org/packages/2b/38/f3abb554eee089bd15471057ba85f47e53a44a462cfce265d9bf7088eb09/brotli-1.2.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:260d3692396e1895c5034f204f0db022c056f9e2ac841593a4cf9426e2a3faca", size = 1626913, upload-time = "2025-11-05T18:38:27.284Z" }, - { url = "https://files.pythonhosted.org/packages/03/a7/03aa61fbc3c5cbf99b44d158665f9b0dd3d8059be16c460208d9e385c837/brotli-1.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:072e7624b1fc4d601036ab3f4f27942ef772887e876beff0301d261210bca97f", size = 1419762, upload-time = "2025-11-05T18:38:28.295Z" }, - { url = "https://files.pythonhosted.org/packages/21/1b/0374a89ee27d152a5069c356c96b93afd1b94eae83f1e004b57eb6ce2f10/brotli-1.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adedc4a67e15327dfdd04884873c6d5a01d3e3b6f61406f99b1ed4865a2f6d28", size = 1484494, upload-time = "2025-11-05T18:38:29.29Z" }, - { url = "https://files.pythonhosted.org/packages/cf/57/69d4fe84a67aef4f524dcd075c6eee868d7850e85bf01d778a857d8dbe0a/brotli-1.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7a47ce5c2288702e09dc22a44d0ee6152f2c7eda97b3c8482d826a1f3cfc7da7", size = 1593302, upload-time = "2025-11-05T18:38:30.639Z" }, - { url = "https://files.pythonhosted.org/packages/d5/3b/39e13ce78a8e9a621c5df3aeb5fd181fcc8caba8c48a194cd629771f6828/brotli-1.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:af43b8711a8264bb4e7d6d9a6d004c3a2019c04c01127a868709ec29962b6036", size = 1487913, upload-time = "2025-11-05T18:38:31.618Z" }, - { url = "https://files.pythonhosted.org/packages/62/28/4d00cb9bd76a6357a66fcd54b4b6d70288385584063f4b07884c1e7286ac/brotli-1.2.0-cp312-cp312-win32.whl", hash = "sha256:e99befa0b48f3cd293dafeacdd0d191804d105d279e0b387a32054c1180f3161", size = 334362, upload-time = "2025-11-05T18:38:32.939Z" }, - { url = "https://files.pythonhosted.org/packages/1c/4e/bc1dcac9498859d5e353c9b153627a3752868a9d5f05ce8dedd81a2354ab/brotli-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:b35c13ce241abdd44cb8ca70683f20c0c079728a36a996297adb5334adfc1c44", size = 369115, upload-time = "2025-11-05T18:38:33.765Z" }, - { url = "https://files.pythonhosted.org/packages/6c/d4/4ad5432ac98c73096159d9ce7ffeb82d151c2ac84adcc6168e476bb54674/brotli-1.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9e5825ba2c9998375530504578fd4d5d1059d09621a02065d1b6bfc41a8e05ab", size = 861523, upload-time = "2025-11-05T18:38:34.67Z" }, - { url = "https://files.pythonhosted.org/packages/91/9f/9cc5bd03ee68a85dc4bc89114f7067c056a3c14b3d95f171918c088bf88d/brotli-1.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0cf8c3b8ba93d496b2fae778039e2f5ecc7cff99df84df337ca31d8f2252896c", size = 444289, upload-time = "2025-11-05T18:38:35.6Z" }, - { url = "https://files.pythonhosted.org/packages/2e/b6/fe84227c56a865d16a6614e2c4722864b380cb14b13f3e6bef441e73a85a/brotli-1.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c8565e3cdc1808b1a34714b553b262c5de5fbda202285782173ec137fd13709f", size = 1528076, upload-time = "2025-11-05T18:38:36.639Z" }, - { url = "https://files.pythonhosted.org/packages/55/de/de4ae0aaca06c790371cf6e7ee93a024f6b4bb0568727da8c3de112e726c/brotli-1.2.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:26e8d3ecb0ee458a9804f47f21b74845cc823fd1bb19f02272be70774f56e2a6", size = 1626880, upload-time = "2025-11-05T18:38:37.623Z" }, - { url = "https://files.pythonhosted.org/packages/5f/16/a1b22cbea436642e071adcaf8d4b350a2ad02f5e0ad0da879a1be16188a0/brotli-1.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67a91c5187e1eec76a61625c77a6c8c785650f5b576ca732bd33ef58b0dff49c", size = 1419737, upload-time = "2025-11-05T18:38:38.729Z" }, - { url = "https://files.pythonhosted.org/packages/46/63/c968a97cbb3bdbf7f974ef5a6ab467a2879b82afbc5ffb65b8acbb744f95/brotli-1.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4ecdb3b6dc36e6d6e14d3a1bdc6c1057c8cbf80db04031d566eb6080ce283a48", size = 1484440, upload-time = "2025-11-05T18:38:39.916Z" }, - { url = "https://files.pythonhosted.org/packages/06/9d/102c67ea5c9fc171f423e8399e585dabea29b5bc79b05572891e70013cdd/brotli-1.2.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3e1b35d56856f3ed326b140d3c6d9db91740f22e14b06e840fe4bb1923439a18", size = 1593313, upload-time = "2025-11-05T18:38:41.24Z" }, - { url = "https://files.pythonhosted.org/packages/9e/4a/9526d14fa6b87bc827ba1755a8440e214ff90de03095cacd78a64abe2b7d/brotli-1.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:54a50a9dad16b32136b2241ddea9e4df159b41247b2ce6aac0b3276a66a8f1e5", size = 1487945, upload-time = "2025-11-05T18:38:42.277Z" }, - { url = "https://files.pythonhosted.org/packages/5b/e8/3fe1ffed70cbef83c5236166acaed7bb9c766509b157854c80e2f766b38c/brotli-1.2.0-cp313-cp313-win32.whl", hash = "sha256:1b1d6a4efedd53671c793be6dd760fcf2107da3a52331ad9ea429edf0902f27a", size = 334368, upload-time = "2025-11-05T18:38:43.345Z" }, - { url = "https://files.pythonhosted.org/packages/ff/91/e739587be970a113b37b821eae8097aac5a48e5f0eca438c22e4c7dd8648/brotli-1.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:b63daa43d82f0cdabf98dee215b375b4058cce72871fd07934f179885aad16e8", size = 369116, upload-time = "2025-11-05T18:38:44.609Z" }, - { url = "https://files.pythonhosted.org/packages/17/e1/298c2ddf786bb7347a1cd71d63a347a79e5712a7c0cba9e3c3458ebd976f/brotli-1.2.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:6c12dad5cd04530323e723787ff762bac749a7b256a5bece32b2243dd5c27b21", size = 863080, upload-time = "2025-11-05T18:38:45.503Z" }, - { url = "https://files.pythonhosted.org/packages/84/0c/aac98e286ba66868b2b3b50338ffbd85a35c7122e9531a73a37a29763d38/brotli-1.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:3219bd9e69868e57183316ee19c84e03e8f8b5a1d1f2667e1aa8c2f91cb061ac", size = 445453, upload-time = "2025-11-05T18:38:46.433Z" }, - { url = "https://files.pythonhosted.org/packages/ec/f1/0ca1f3f99ae300372635ab3fe2f7a79fa335fee3d874fa7f9e68575e0e62/brotli-1.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:963a08f3bebd8b75ac57661045402da15991468a621f014be54e50f53a58d19e", size = 1528168, upload-time = "2025-11-05T18:38:47.371Z" }, - { url = "https://files.pythonhosted.org/packages/d6/a6/2ebfc8f766d46df8d3e65b880a2e220732395e6d7dc312c1e1244b0f074a/brotli-1.2.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9322b9f8656782414b37e6af884146869d46ab85158201d82bab9abbcb971dc7", size = 1627098, upload-time = "2025-11-05T18:38:48.385Z" }, - { url = "https://files.pythonhosted.org/packages/f3/2f/0976d5b097ff8a22163b10617f76b2557f15f0f39d6a0fe1f02b1a53e92b/brotli-1.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cf9cba6f5b78a2071ec6fb1e7bd39acf35071d90a81231d67e92d637776a6a63", size = 1419861, upload-time = "2025-11-05T18:38:49.372Z" }, - { url = "https://files.pythonhosted.org/packages/9c/97/d76df7176a2ce7616ff94c1fb72d307c9a30d2189fe877f3dd99af00ea5a/brotli-1.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7547369c4392b47d30a3467fe8c3330b4f2e0f7730e45e3103d7d636678a808b", size = 1484594, upload-time = "2025-11-05T18:38:50.655Z" }, - { url = "https://files.pythonhosted.org/packages/d3/93/14cf0b1216f43df5609f5b272050b0abd219e0b54ea80b47cef9867b45e7/brotli-1.2.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:fc1530af5c3c275b8524f2e24841cbe2599d74462455e9bae5109e9ff42e9361", size = 1593455, upload-time = "2025-11-05T18:38:51.624Z" }, - { url = "https://files.pythonhosted.org/packages/b3/73/3183c9e41ca755713bdf2cc1d0810df742c09484e2e1ddd693bee53877c1/brotli-1.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d2d085ded05278d1c7f65560aae97b3160aeb2ea2c0b3e26204856beccb60888", size = 1488164, upload-time = "2025-11-05T18:38:53.079Z" }, - { url = "https://files.pythonhosted.org/packages/64/6a/0c78d8f3a582859236482fd9fa86a65a60328a00983006bcf6d83b7b2253/brotli-1.2.0-cp314-cp314-win32.whl", hash = "sha256:832c115a020e463c2f67664560449a7bea26b0c1fdd690352addad6d0a08714d", size = 339280, upload-time = "2025-11-05T18:38:54.02Z" }, - { url = "https://files.pythonhosted.org/packages/f5/10/56978295c14794b2c12007b07f3e41ba26acda9257457d7085b0bb3bb90c/brotli-1.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:e7c0af964e0b4e3412a0ebf341ea26ec767fa0b4cf81abb5e897c9338b5ad6a3", size = 375639, upload-time = "2025-11-05T18:38:55.67Z" }, -] - [[package]] name = "cachetools" version = "7.1.1" @@ -485,12 +465,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bf/0f/f897abe4ea0a8c408ae65c8c83bffab4936ad65d6032d4fb4cd35bbdc3ee/cachetools-7.1.1-py3-none-any.whl", hash = "sha256:0335cd7a0952d2b22327441fb0628139e234c565559eeb91a8a4ac7551c5353d", size = 16775, upload-time = "2026-05-03T20:00:27.857Z" }, ] -[[package]] -name = "cbor" -version = "1.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/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 = "celery" version = "5.6.3" @@ -766,182 +740,77 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] -[[package]] -name = "colorlog" -version = "6.10.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a2/61/f083b5ac52e505dfc1c624eafbf8c7589a0d7f32daa398d2e7590efa5fda/colorlog-6.10.1.tar.gz", hash = "sha256:eb4ae5cb65fe7fec7773c2306061a8e63e02efc2c72eba9d27b0fa23c94f1321", size = 17162, upload-time = "2025-10-16T16:14:11.978Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/c1/e419ef3723a074172b68aaa89c9f3de486ed4c2399e2dbd8113a4fdcaf9e/colorlog-6.10.1-py3-none-any.whl", hash = "sha256:2d7e8348291948af66122cff006c9f8da6255d224e7cf8e37d8de2df3bad8c9c", size = 11743, upload-time = "2025-10-16T16:14:10.512Z" }, -] - [[package]] name = "cryptography" -version = "48.0.0" +version = "46.0.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9f/a9/db8f313fdcd85d767d4973515e1db101f9c71f95fced83233de224673757/cryptography-48.0.0.tar.gz", hash = "sha256:5c3932f4436d1cccb036cb0eaef46e6e2db91035166f1ad6505c3c9d5a635920", size = 832984, upload-time = "2026-05-04T22:59:38.133Z" } +sdist = { url = "https://files.pythonhosted.org/packages/47/93/ac8f3d5ff04d54bc814e961a43ae5b0b146154c89c61b47bb07557679b18/cryptography-46.0.7.tar.gz", hash = "sha256:e4cfd68c5f3e0bfdad0d38e023239b96a2fe84146481852dffbcca442c245aa5", size = 750652, upload-time = "2026-04-08T01:57:54.692Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/df/3d/01f6dd9190170a5a241e0e98c2d04be3664a9e6f5b9b872cde63aff1c3dd/cryptography-48.0.0-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:0c558d2cdffd8f4bbb30fc7134c74d2ca9a476f830bb053074498fbc86f41ed6", size = 8001587, upload-time = "2026-05-04T22:57:36.803Z" }, - { url = "https://files.pythonhosted.org/packages/b2/6e/e90527eef33f309beb811cf7c982c3aeffcce8e3edb178baa4ca3ae4a6fa/cryptography-48.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f5333311663ea94f75dd408665686aaf426563556bb5283554a3539177e03b8c", size = 4690433, upload-time = "2026-05-04T22:57:40.373Z" }, - { url = "https://files.pythonhosted.org/packages/90/04/673510ed51ddff56575f306cf1617d80411ee76831ccd3097599140efdfe/cryptography-48.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7995ef305d7165c3f11ae07f2517e5a4f1d5c18da1376a0a9ed496336b69e5f3", size = 4710620, upload-time = "2026-05-04T22:57:42.935Z" }, - { url = "https://files.pythonhosted.org/packages/14/d5/e9c4ef932c8d800490c34d8bd589d64a31d5890e27ec9e9ad532be893294/cryptography-48.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:40ba1f85eaa6959837b1d51c9767e230e14612eea4ef110ee8854ada22da1bf5", size = 4696283, upload-time = "2026-05-04T22:57:45.294Z" }, - { url = "https://files.pythonhosted.org/packages/0c/29/174b9dfb60b12d59ecfc6cfa04bc88c21b42a54f01b8aae09bb6e51e4c7f/cryptography-48.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:369a6348999f94bbd53435c894377b20ab95f25a9065c283570e70150d8abc3c", size = 5296573, upload-time = "2026-05-04T22:57:47.933Z" }, - { url = "https://files.pythonhosted.org/packages/95/38/0d29a6fd7d0d1373f0c0c88a04ba20e359b257753ac497564cd660fc1d55/cryptography-48.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a0e692c683f4df67815a2d258b324e66f4738bd7a96a218c826dce4f4bd05d8f", size = 4743677, upload-time = "2026-05-04T22:57:50.067Z" }, - { url = "https://files.pythonhosted.org/packages/30/be/eef653013d5c63b6a490529e0316f9ac14a37602965d4903efed1399f32b/cryptography-48.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:18349bbc56f4743c8b12dc32e2bccb2cf83ee8b69a3bba74ef8ae857e26b3d25", size = 4330808, upload-time = "2026-05-04T22:57:52.301Z" }, - { url = "https://files.pythonhosted.org/packages/84/9e/500463e87abb7a0a0f9f256ec21123ecde0a7b5541a15e840ea54551fd81/cryptography-48.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:7e8eac43dfca5c4cccc6dad9a80504436fca53bb9bc3100a2386d730fbe6b602", size = 4695941, upload-time = "2026-05-04T22:57:54.603Z" }, - { url = "https://files.pythonhosted.org/packages/e3/dc/7303087450c2ec9e7fbb750e17c2abfbc658f23cbd0e54009509b7cc4091/cryptography-48.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9ccdac7d40688ecb5a3b4a604b8a88c8002e3442d6c60aead1db2a89a041560c", size = 5252579, upload-time = "2026-05-04T22:57:57.207Z" }, - { url = "https://files.pythonhosted.org/packages/d0/c0/7101d3b7215edcdc90c45da544961fd8ed2d6448f77577460fa75a8443f7/cryptography-48.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:bd72e68b06bb1e96913f97dd4901119bc17f39d4586a5adf2d3e47bc2b9d58b5", size = 4743326, upload-time = "2026-05-04T22:57:59.535Z" }, - { url = "https://files.pythonhosted.org/packages/ac/d8/5b833bad13016f562ab9d063d68199a4bd121d18458e439515601d3357ec/cryptography-48.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:59baa2cb386c4f0b9905bd6eb4c2a79a69a128408fd31d32ca4d7102d4156321", size = 4826672, upload-time = "2026-05-04T22:58:01.996Z" }, - { url = "https://files.pythonhosted.org/packages/98/e1/7074eb8bf3c135558c73fc2bcf0f5633f912e6fb87e868a55c454080ef09/cryptography-48.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9249e3cd978541d665967ac2cb2787fd6a62bddf1e75b3e347a594d7dacf4f74", size = 4972574, upload-time = "2026-05-04T22:58:03.968Z" }, - { url = "https://files.pythonhosted.org/packages/04/70/e5a1b41d325f797f39427aa44ef8baf0be500065ab6d8e10369d850d4a4f/cryptography-48.0.0-cp311-abi3-win32.whl", hash = "sha256:9c459db21422be75e2809370b829a87eb37f74cd785fc4aa9ea1e5f43b47cda4", size = 3294868, upload-time = "2026-05-04T22:58:06.467Z" }, - { url = "https://files.pythonhosted.org/packages/f4/ac/8ac51b4a5fc5932eb7ee5c517ba7dc8cd834f0048962b6b352f00f41ebf9/cryptography-48.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:5b012212e08b8dd5edc78ef54da83dd9892fd9105323b3993eff6bea65dc21d7", size = 3817107, upload-time = "2026-05-04T22:58:08.845Z" }, - { url = "https://files.pythonhosted.org/packages/6b/84/70e3feea9feea87fd7cbe77efb2712ae1e3e6edf10749dc6e95f4e60e455/cryptography-48.0.0-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:3cb07a3ed6431663cd321ea8a000a1314c74211f823e4177fefa2255e057d1ec", size = 7986556, upload-time = "2026-05-04T22:58:11.172Z" }, - { url = "https://files.pythonhosted.org/packages/89/6e/18e07a618bb5442ba10cf4df16e99c071365528aa570dfcb8c02e25a303b/cryptography-48.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8c7378637d7d88016fa6791c159f698b3d3eed28ebf844ac36b9dc04a14dae18", size = 4684776, upload-time = "2026-05-04T22:58:13.712Z" }, - { url = "https://files.pythonhosted.org/packages/be/6a/4ea3b4c6c6759794d5ee2103c304a5076dc4b19ae1f9fe47dba439e159e9/cryptography-48.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc90c0b39b2e3c65ef52c804b72e3c58f8a04ab2a1871272798e5f9572c17d20", size = 4698121, upload-time = "2026-05-04T22:58:16.448Z" }, - { url = "https://files.pythonhosted.org/packages/2f/59/6ff6ad6cae03bb887da2a5860b2c9805f8dac969ef01ce563336c49bd1d1/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:76341972e1eff8b4bea859f09c0d3e64b96ce931b084f9b9b7db8ef364c30eff", size = 4690042, upload-time = "2026-05-04T22:58:18.544Z" }, - { url = "https://files.pythonhosted.org/packages/ca/b4/fc334ed8cfd705aca282fe4d8f5ae64a8e0f74932e9feecb344610cf6e4d/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:55b7718303bf06a5753dcdccf2f3945cf18ad7bffde41b61226e4db31ab89a9c", size = 5282526, upload-time = "2026-05-04T22:58:20.75Z" }, - { url = "https://files.pythonhosted.org/packages/11/08/9f8c5386cc4cd90d8255c7cdd0f5baf459a08502a09de30dc51f553d38dc/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:a64697c641c7b1b2178e573cbc31c7c6684cd56883a478d75143dbb7118036db", size = 4733116, upload-time = "2026-05-04T22:58:23.627Z" }, - { url = "https://files.pythonhosted.org/packages/b8/77/99307d7574045699f8805aa500fa0fb83422d115b5400a064ddd306d7750/cryptography-48.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:561215ea3879cb1cbbf272867e2efda62476f240fb58c64de6b393ae19246741", size = 4316030, upload-time = "2026-05-04T22:58:25.581Z" }, - { url = "https://files.pythonhosted.org/packages/fd/36/a608b98337af3cb2aff4818e406649d30572b7031918b04c87d979495348/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:ad64688338ed4bc1a6618076ba75fd7194a5f1797ac60b47afe926285adb3166", size = 4689640, upload-time = "2026-05-04T22:58:27.747Z" }, - { url = "https://files.pythonhosted.org/packages/dd/a6/825010a291b4438aecc1f568bc428189fc1175515223632477c07dc0a6df/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:906cbf0670286c6e0044156bc7d4af9cbb0ef6db9f73e52c3ec56ba6bdde5336", size = 5237657, upload-time = "2026-05-04T22:58:29.848Z" }, - { url = "https://files.pythonhosted.org/packages/b9/09/4e76a09b4caa29aad535ddc806f5d4c5d01885bd978bd984fbc6ca032cae/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:ea8990436d914540a40ab24b6a77c0969695ed52f4a4874c5137ccf7045a7057", size = 4732362, upload-time = "2026-05-04T22:58:32.009Z" }, - { url = "https://files.pythonhosted.org/packages/18/78/444fa04a77d0cb95f417dda20d450e13c56ba8e5220fc892a1658f44f882/cryptography-48.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c18684a7f0cc9a3cb60328f496b8e3372def7c5d2df39ac267878b05565aaaae", size = 4819580, upload-time = "2026-05-04T22:58:34.254Z" }, - { url = "https://files.pythonhosted.org/packages/38/85/ea67067c70a1fd4be2c63d35eeed82658023021affccc7b17705f8527dd2/cryptography-48.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9be5aafa5736574f8f15f262adc81b2a9869e2cfe9014d52a44633905b40d52c", size = 4963283, upload-time = "2026-05-04T22:58:36.376Z" }, - { url = "https://files.pythonhosted.org/packages/75/54/cc6d0f3deac3e81c7f847e8a189a12b6cdd65059b43dad25d4316abd849a/cryptography-48.0.0-cp314-cp314t-win32.whl", hash = "sha256:c17dfe85494deaeddc5ce251aebd1d60bbe6afc8b62071bb0b469431a000124f", size = 3270954, upload-time = "2026-05-04T22:58:38.791Z" }, - { url = "https://files.pythonhosted.org/packages/49/67/cc947e288c0758a4e5473d1dcb743037ab7785541265a969240b8885441a/cryptography-48.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27241b1dc9962e056062a8eef1991d02c3a24569c95975bd2322a8a52c6e5e12", size = 3797313, upload-time = "2026-05-04T22:58:40.746Z" }, - { url = "https://files.pythonhosted.org/packages/f2/63/61d4a4e1c6b6bab6ce1e213cd36a24c415d90e76d78c5eb8577c5541d2e8/cryptography-48.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:58d00498e8933e4a194f3076aee1b4a97dfec1a6da444535755822fe5d8b0b86", size = 7983482, upload-time = "2026-05-04T22:58:43.769Z" }, - { url = "https://files.pythonhosted.org/packages/d5/ac/f5b5995b87770c693e2596559ffafe195b4033a57f14a82268a2842953f3/cryptography-48.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:614d0949f4790582d2cc25553abd09dd723025f0c0e7c67376a1d77196743d6e", size = 4683266, upload-time = "2026-05-04T22:58:46.064Z" }, - { url = "https://files.pythonhosted.org/packages/ec/c6/8b14f67e18338fbc4adb76f66c001f5c3610b3e2d1837f268f47a347dbbb/cryptography-48.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7ce4bfae76319a532a2dc68f82cc32f5676ee792a983187dac07183690e5c66f", size = 4696228, upload-time = "2026-05-04T22:58:48.22Z" }, - { url = "https://files.pythonhosted.org/packages/ea/73/f808fbae9514bd91b47875b003f13e284c8c6bdfd904b7944e803937eec1/cryptography-48.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:2eb992bbd4661238c5a397594c83f5b4dc2bc5b848c365c8f991b6780efcc5c7", size = 4689097, upload-time = "2026-05-04T22:58:50.9Z" }, - { url = "https://files.pythonhosted.org/packages/93/01/d86632d7d28db8ae83221995752eeb6639ffb374c2d22955648cf8d52797/cryptography-48.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:22a5cb272895dce158b2cacdfdc3debd299019659f42947dbdac6f32d68fe832", size = 5283582, upload-time = "2026-05-04T22:58:53.017Z" }, - { url = "https://files.pythonhosted.org/packages/02/e1/50edc7a50334807cc4791fc4a0ce7468b4a1416d9138eab358bfc9a3d70b/cryptography-48.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2b4d59804e8408e2fea7d1fbaf218e5ec984325221db76e6a241a9abd6cdd95c", size = 4730479, upload-time = "2026-05-04T22:58:55.611Z" }, - { url = "https://files.pythonhosted.org/packages/6f/af/99a582b1b1641ff5911ac559beb45097cf79efd4ead4657f578ef1af2d47/cryptography-48.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:984a20b0f62a26f48a3396c72e4bc34c66e356d356bf370053066b3b6d54634a", size = 4326481, upload-time = "2026-05-04T22:58:57.607Z" }, - { url = "https://files.pythonhosted.org/packages/90/ee/89aa26a06ef0a7d7611788ffd571a7c50e368cc6a4d5eef8b4884e866edb/cryptography-48.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5a5ed8fde7a1d09376ca0b40e68cd59c69fe23b1f9768bd5824f54681626032a", size = 4688713, upload-time = "2026-05-04T22:59:00.077Z" }, - { url = "https://files.pythonhosted.org/packages/70/ba/bcb1b0bb7a33d4c7c0c4d4c7874b4a62ae4f56113a5f4baefa362dfb1f0f/cryptography-48.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:8cd666227ef7af430aa5914a9910e0ddd703e75f039cef0825cd0da71b6b711a", size = 5238165, upload-time = "2026-05-04T22:59:02.317Z" }, - { url = "https://files.pythonhosted.org/packages/c9/70/ca4003b1ce5ca3dc3186ada51908c8a9b9ff7d5cab83cc0d43ee14ec144f/cryptography-48.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9071196d81abc88b3516ac8cdfad32e2b66dd4a5393a8e68a961e9161ddc6239", size = 4729947, upload-time = "2026-05-04T22:59:05.255Z" }, - { url = "https://files.pythonhosted.org/packages/44/a0/4ec7cf774207905aef1a8d11c3750d5a1db805eb380ee4e16df317870128/cryptography-48.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1e2d54c8be6152856a36f0882ab231e70f8ec7f14e93cf87db8a2ed056bf160c", size = 4822059, upload-time = "2026-05-04T22:59:07.802Z" }, - { url = "https://files.pythonhosted.org/packages/1e/75/a2e55f99c16fcac7b5d6c1eb19ad8e00799854d6be5ca845f9259eae1681/cryptography-48.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a5da777e32ffed6f85a7b2b3f7c5cbc88c146bfcd0a1d7baf5fcc6c52ee35dd4", size = 4960575, upload-time = "2026-05-04T22:59:09.851Z" }, - { url = "https://files.pythonhosted.org/packages/b8/23/6e6f32143ab5d8b36ca848a502c4bcd477ae75b9e1677e3530d669062578/cryptography-48.0.0-cp39-abi3-win32.whl", hash = "sha256:77a2ccbbe917f6710e05ba9adaa25fb5075620bf3ea6fb751997875aff4ae4bd", size = 3279117, upload-time = "2026-05-04T22:59:12.019Z" }, - { url = "https://files.pythonhosted.org/packages/9d/9a/0fea98a70cf1749d41d738836f6349d97945f7c89433a259a6c2642eefeb/cryptography-48.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:16cd65b9330583e4619939b3a3843eec1e6e789744bb01e7c7e2e62e33c239c8", size = 3792100, upload-time = "2026-05-04T22:59:14.884Z" }, - { url = "https://files.pythonhosted.org/packages/be/d2/024b5e06be9d44cb021fb0e1a03d34d63989cf56a0fe62f3dfbab695b9b4/cryptography-48.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:84cf79f0dc8b36ac5da873481716e87aef31fcfa0444f9e1d8b4b2cece142855", size = 3950391, upload-time = "2026-05-04T22:59:17.415Z" }, - { url = "https://files.pythonhosted.org/packages/bc/17/3861e17c56fa0fd37491a14a8673fdb77c57fc5693cafe745ea8b06dba75/cryptography-48.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:fdfef35d751d510fcef5252703621574364fec16418c4a1e5e1055248401054b", size = 4637126, upload-time = "2026-05-04T22:59:20.197Z" }, - { url = "https://files.pythonhosted.org/packages/f0/0a/7e226dbff530f21480727eb764973a7bff2b912f8e15cd4f129e71b56d1d/cryptography-48.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:0890f502ddf7d9c6426129c3f49f5c0a39278ed7cd6322c8755ffca6ee675a13", size = 4667270, upload-time = "2026-05-04T22:59:22.647Z" }, - { url = "https://files.pythonhosted.org/packages/3b/f2/5a72274ca9f1b2a8b44a662ee0bf1b435909deb473d6f97bcd035bcdbc71/cryptography-48.0.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:ecde28a596bead48b0cfd2a1b4416c3d43074c2d785e3a398d7ec1fc4d0f7fbb", size = 4636797, upload-time = "2026-05-04T22:59:24.912Z" }, - { url = "https://files.pythonhosted.org/packages/b4/e1/48cedb2fe63626e91ded1edad159e2a4fb8b6906c4425eb7749673077ce7/cryptography-48.0.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:4defde8685ae324a9eb9d818717e93b4638ef67070ac9bc15b8ca85f63048355", size = 4666800, upload-time = "2026-05-04T22:59:27.474Z" }, - { url = "https://files.pythonhosted.org/packages/a2/ca/7e8365deec19afb2b2c7be7c1c0aa8f99633b54e90c570999acda93260fc/cryptography-48.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:db63bf618e5dea46c07de12e900fe1cdd2541e6dc9dbae772a70b7d4d4765f6a", size = 3739536, upload-time = "2026-05-04T22:59:29.61Z" }, + { url = "https://files.pythonhosted.org/packages/0b/5d/4a8f770695d73be252331e60e526291e3df0c9b27556a90a6b47bccca4c2/cryptography-46.0.7-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:ea42cbe97209df307fdc3b155f1b6fa2577c0defa8f1f7d3be7d31d189108ad4", size = 7179869, upload-time = "2026-04-08T01:56:17.157Z" }, + { url = "https://files.pythonhosted.org/packages/5f/45/6d80dc379b0bbc1f9d1e429f42e4cb9e1d319c7a8201beffd967c516ea01/cryptography-46.0.7-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b36a4695e29fe69215d75960b22577197aca3f7a25b9cf9d165dcfe9d80bc325", size = 4275492, upload-time = "2026-04-08T01:56:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9a/1765afe9f572e239c3469f2cb429f3ba7b31878c893b246b4b2994ffe2fe/cryptography-46.0.7-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5ad9ef796328c5e3c4ceed237a183f5d41d21150f972455a9d926593a1dcb308", size = 4426670, upload-time = "2026-04-08T01:56:21.415Z" }, + { url = "https://files.pythonhosted.org/packages/8f/3e/af9246aaf23cd4ee060699adab1e47ced3f5f7e7a8ffdd339f817b446462/cryptography-46.0.7-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:73510b83623e080a2c35c62c15298096e2a5dc8d51c3b4e1740211839d0dea77", size = 4280275, upload-time = "2026-04-08T01:56:23.539Z" }, + { url = "https://files.pythonhosted.org/packages/0f/54/6bbbfc5efe86f9d71041827b793c24811a017c6ac0fd12883e4caa86b8ed/cryptography-46.0.7-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cbd5fb06b62bd0721e1170273d3f4d5a277044c47ca27ee257025146c34cbdd1", size = 4928402, upload-time = "2026-04-08T01:56:25.624Z" }, + { url = "https://files.pythonhosted.org/packages/2d/cf/054b9d8220f81509939599c8bdbc0c408dbd2bdd41688616a20731371fe0/cryptography-46.0.7-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:420b1e4109cc95f0e5700eed79908cef9268265c773d3a66f7af1eef53d409ef", size = 4459985, upload-time = "2026-04-08T01:56:27.309Z" }, + { url = "https://files.pythonhosted.org/packages/f9/46/4e4e9c6040fb01c7467d47217d2f882daddeb8828f7df800cb806d8a2288/cryptography-46.0.7-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:24402210aa54baae71d99441d15bb5a1919c195398a87b563df84468160a65de", size = 3990652, upload-time = "2026-04-08T01:56:29.095Z" }, + { url = "https://files.pythonhosted.org/packages/36/5f/313586c3be5a2fbe87e4c9a254207b860155a8e1f3cca99f9910008e7d08/cryptography-46.0.7-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:8a469028a86f12eb7d2fe97162d0634026d92a21f3ae0ac87ed1c4a447886c83", size = 4279805, upload-time = "2026-04-08T01:56:30.928Z" }, + { url = "https://files.pythonhosted.org/packages/69/33/60dfc4595f334a2082749673386a4d05e4f0cf4df8248e63b2c3437585f2/cryptography-46.0.7-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9694078c5d44c157ef3162e3bf3946510b857df5a3955458381d1c7cfc143ddb", size = 4892883, upload-time = "2026-04-08T01:56:32.614Z" }, + { url = "https://files.pythonhosted.org/packages/c7/0b/333ddab4270c4f5b972f980adef4faa66951a4aaf646ca067af597f15563/cryptography-46.0.7-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:42a1e5f98abb6391717978baf9f90dc28a743b7d9be7f0751a6f56a75d14065b", size = 4459756, upload-time = "2026-04-08T01:56:34.306Z" }, + { url = "https://files.pythonhosted.org/packages/d2/14/633913398b43b75f1234834170947957c6b623d1701ffc7a9600da907e89/cryptography-46.0.7-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:91bbcb08347344f810cbe49065914fe048949648f6bd5c2519f34619142bbe85", size = 4410244, upload-time = "2026-04-08T01:56:35.977Z" }, + { url = "https://files.pythonhosted.org/packages/10/f2/19ceb3b3dc14009373432af0c13f46aa08e3ce334ec6eff13492e1812ccd/cryptography-46.0.7-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5d1c02a14ceb9148cc7816249f64f623fbfee39e8c03b3650d842ad3f34d637e", size = 4674868, upload-time = "2026-04-08T01:56:38.034Z" }, + { url = "https://files.pythonhosted.org/packages/1a/bb/a5c213c19ee94b15dfccc48f363738633a493812687f5567addbcbba9f6f/cryptography-46.0.7-cp311-abi3-win32.whl", hash = "sha256:d23c8ca48e44ee015cd0a54aeccdf9f09004eba9fc96f38c911011d9ff1bd457", size = 3026504, upload-time = "2026-04-08T01:56:39.666Z" }, + { url = "https://files.pythonhosted.org/packages/2b/02/7788f9fefa1d060ca68717c3901ae7fffa21ee087a90b7f23c7a603c32ae/cryptography-46.0.7-cp311-abi3-win_amd64.whl", hash = "sha256:397655da831414d165029da9bc483bed2fe0e75dde6a1523ec2fe63f3c46046b", size = 3488363, upload-time = "2026-04-08T01:56:41.893Z" }, + { url = "https://files.pythonhosted.org/packages/7b/56/15619b210e689c5403bb0540e4cb7dbf11a6bf42e483b7644e471a2812b3/cryptography-46.0.7-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:d151173275e1728cf7839aaa80c34fe550c04ddb27b34f48c232193df8db5842", size = 7119671, upload-time = "2026-04-08T01:56:44Z" }, + { url = "https://files.pythonhosted.org/packages/74/66/e3ce040721b0b5599e175ba91ab08884c75928fbeb74597dd10ef13505d2/cryptography-46.0.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:db0f493b9181c7820c8134437eb8b0b4792085d37dbb24da050476ccb664e59c", size = 4268551, upload-time = "2026-04-08T01:56:46.071Z" }, + { url = "https://files.pythonhosted.org/packages/03/11/5e395f961d6868269835dee1bafec6a1ac176505a167f68b7d8818431068/cryptography-46.0.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ebd6daf519b9f189f85c479427bbd6e9c9037862cf8fe89ee35503bd209ed902", size = 4408887, upload-time = "2026-04-08T01:56:47.718Z" }, + { url = "https://files.pythonhosted.org/packages/40/53/8ed1cf4c3b9c8e611e7122fb56f1c32d09e1fff0f1d77e78d9ff7c82653e/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:b7b412817be92117ec5ed95f880defe9cf18a832e8cafacf0a22337dc1981b4d", size = 4271354, upload-time = "2026-04-08T01:56:49.312Z" }, + { url = "https://files.pythonhosted.org/packages/50/46/cf71e26025c2e767c5609162c866a78e8a2915bbcfa408b7ca495c6140c4/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:fbfd0e5f273877695cb93baf14b185f4878128b250cc9f8e617ea0c025dfb022", size = 4905845, upload-time = "2026-04-08T01:56:50.916Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ea/01276740375bac6249d0a971ebdf6b4dc9ead0ee0a34ef3b5a88c1a9b0d4/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:ffca7aa1d00cf7d6469b988c581598f2259e46215e0140af408966a24cf086ce", size = 4444641, upload-time = "2026-04-08T01:56:52.882Z" }, + { url = "https://files.pythonhosted.org/packages/3d/4c/7d258f169ae71230f25d9f3d06caabcff8c3baf0978e2b7d65e0acac3827/cryptography-46.0.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:60627cf07e0d9274338521205899337c5d18249db56865f943cbe753aa96f40f", size = 3967749, upload-time = "2026-04-08T01:56:54.597Z" }, + { url = "https://files.pythonhosted.org/packages/b5/2a/2ea0767cad19e71b3530e4cad9605d0b5e338b6a1e72c37c9c1ceb86c333/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:80406c3065e2c55d7f49a9550fe0c49b3f12e5bfff5dedb727e319e1afb9bf99", size = 4270942, upload-time = "2026-04-08T01:56:56.416Z" }, + { url = "https://files.pythonhosted.org/packages/41/3d/fe14df95a83319af25717677e956567a105bb6ab25641acaa093db79975d/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:c5b1ccd1239f48b7151a65bc6dd54bcfcc15e028c8ac126d3fada09db0e07ef1", size = 4871079, upload-time = "2026-04-08T01:56:58.31Z" }, + { url = "https://files.pythonhosted.org/packages/9c/59/4a479e0f36f8f378d397f4eab4c850b4ffb79a2f0d58704b8fa0703ddc11/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:d5f7520159cd9c2154eb61eb67548ca05c5774d39e9c2c4339fd793fe7d097b2", size = 4443999, upload-time = "2026-04-08T01:57:00.508Z" }, + { url = "https://files.pythonhosted.org/packages/28/17/b59a741645822ec6d04732b43c5d35e4ef58be7bfa84a81e5ae6f05a1d33/cryptography-46.0.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fcd8eac50d9138c1d7fc53a653ba60a2bee81a505f9f8850b6b2888555a45d0e", size = 4399191, upload-time = "2026-04-08T01:57:02.654Z" }, + { url = "https://files.pythonhosted.org/packages/59/6a/bb2e166d6d0e0955f1e9ff70f10ec4b2824c9cfcdb4da772c7dd69cc7d80/cryptography-46.0.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:65814c60f8cc400c63131584e3e1fad01235edba2614b61fbfbfa954082db0ee", size = 4655782, upload-time = "2026-04-08T01:57:04.592Z" }, + { url = "https://files.pythonhosted.org/packages/95/b6/3da51d48415bcb63b00dc17c2eff3a651b7c4fed484308d0f19b30e8cb2c/cryptography-46.0.7-cp314-cp314t-win32.whl", hash = "sha256:fdd1736fed309b4300346f88f74cd120c27c56852c3838cab416e7a166f67298", size = 3002227, upload-time = "2026-04-08T01:57:06.91Z" }, + { url = "https://files.pythonhosted.org/packages/32/a8/9f0e4ed57ec9cebe506e58db11ae472972ecb0c659e4d52bbaee80ca340a/cryptography-46.0.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e06acf3c99be55aa3b516397fe42f5855597f430add9c17fa46bf2e0fb34c9bb", size = 3475332, upload-time = "2026-04-08T01:57:08.807Z" }, + { url = "https://files.pythonhosted.org/packages/a7/7f/cd42fc3614386bc0c12f0cb3c4ae1fc2bbca5c9662dfed031514911d513d/cryptography-46.0.7-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:462ad5cb1c148a22b2e3bcc5ad52504dff325d17daf5df8d88c17dda1f75f2a4", size = 7165618, upload-time = "2026-04-08T01:57:10.645Z" }, + { url = "https://files.pythonhosted.org/packages/a5/d0/36a49f0262d2319139d2829f773f1b97ef8aef7f97e6e5bd21455e5a8fb5/cryptography-46.0.7-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:84d4cced91f0f159a7ddacad249cc077e63195c36aac40b4150e7a57e84fffe7", size = 4270628, upload-time = "2026-04-08T01:57:12.885Z" }, + { url = "https://files.pythonhosted.org/packages/8a/6c/1a42450f464dda6ffbe578a911f773e54dd48c10f9895a23a7e88b3e7db5/cryptography-46.0.7-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:128c5edfe5e5938b86b03941e94fac9ee793a94452ad1365c9fc3f4f62216832", size = 4415405, upload-time = "2026-04-08T01:57:14.923Z" }, + { url = "https://files.pythonhosted.org/packages/9a/92/4ed714dbe93a066dc1f4b4581a464d2d7dbec9046f7c8b7016f5286329e2/cryptography-46.0.7-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5e51be372b26ef4ba3de3c167cd3d1022934bc838ae9eaad7e644986d2a3d163", size = 4272715, upload-time = "2026-04-08T01:57:16.638Z" }, + { url = "https://files.pythonhosted.org/packages/b7/e6/a26b84096eddd51494bba19111f8fffe976f6a09f132706f8f1bf03f51f7/cryptography-46.0.7-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cdf1a610ef82abb396451862739e3fc93b071c844399e15b90726ef7470eeaf2", size = 4918400, upload-time = "2026-04-08T01:57:19.021Z" }, + { url = "https://files.pythonhosted.org/packages/c7/08/ffd537b605568a148543ac3c2b239708ae0bd635064bab41359252ef88ed/cryptography-46.0.7-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1d25aee46d0c6f1a501adcddb2d2fee4b979381346a78558ed13e50aa8a59067", size = 4450634, upload-time = "2026-04-08T01:57:21.185Z" }, + { url = "https://files.pythonhosted.org/packages/16/01/0cd51dd86ab5b9befe0d031e276510491976c3a80e9f6e31810cce46c4ad/cryptography-46.0.7-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:cdfbe22376065ffcf8be74dc9a909f032df19bc58a699456a21712d6e5eabfd0", size = 3985233, upload-time = "2026-04-08T01:57:22.862Z" }, + { url = "https://files.pythonhosted.org/packages/92/49/819d6ed3a7d9349c2939f81b500a738cb733ab62fbecdbc1e38e83d45e12/cryptography-46.0.7-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:abad9dac36cbf55de6eb49badd4016806b3165d396f64925bf2999bcb67837ba", size = 4271955, upload-time = "2026-04-08T01:57:24.814Z" }, + { url = "https://files.pythonhosted.org/packages/80/07/ad9b3c56ebb95ed2473d46df0847357e01583f4c52a85754d1a55e29e4d0/cryptography-46.0.7-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:935ce7e3cfdb53e3536119a542b839bb94ec1ad081013e9ab9b7cfd478b05006", size = 4879888, upload-time = "2026-04-08T01:57:26.88Z" }, + { url = "https://files.pythonhosted.org/packages/b8/c7/201d3d58f30c4c2bdbe9b03844c291feb77c20511cc3586daf7edc12a47b/cryptography-46.0.7-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:35719dc79d4730d30f1c2b6474bd6acda36ae2dfae1e3c16f2051f215df33ce0", size = 4449961, upload-time = "2026-04-08T01:57:29.068Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ef/649750cbf96f3033c3c976e112265c33906f8e462291a33d77f90356548c/cryptography-46.0.7-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7bbc6ccf49d05ac8f7d7b5e2e2c33830d4fe2061def88210a126d130d7f71a85", size = 4401696, upload-time = "2026-04-08T01:57:31.029Z" }, + { url = "https://files.pythonhosted.org/packages/41/52/a8908dcb1a389a459a29008c29966c1d552588d4ae6d43f3a1a4512e0ebe/cryptography-46.0.7-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a1529d614f44b863a7b480c6d000fe93b59acee9c82ffa027cfadc77521a9f5e", size = 4664256, upload-time = "2026-04-08T01:57:33.144Z" }, + { url = "https://files.pythonhosted.org/packages/4b/fa/f0ab06238e899cc3fb332623f337a7364f36f4bb3f2534c2bb95a35b132c/cryptography-46.0.7-cp38-abi3-win32.whl", hash = "sha256:f247c8c1a1fb45e12586afbb436ef21ff1e80670b2861a90353d9b025583d246", size = 3013001, upload-time = "2026-04-08T01:57:34.933Z" }, + { url = "https://files.pythonhosted.org/packages/d2/f1/00ce3bde3ca542d1acd8f8cfa38e446840945aa6363f9b74746394b14127/cryptography-46.0.7-cp38-abi3-win_amd64.whl", hash = "sha256:506c4ff91eff4f82bdac7633318a526b1d1309fc07ca76a3ad182cb5b686d6d3", size = 3472985, upload-time = "2026-04-08T01:57:36.714Z" }, + { url = "https://files.pythonhosted.org/packages/63/0c/dca8abb64e7ca4f6b2978769f6fea5ad06686a190cec381f0a796fdcaaba/cryptography-46.0.7-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:fc9ab8856ae6cf7c9358430e49b368f3108f050031442eaeb6b9d87e4dcf4e4f", size = 3476879, upload-time = "2026-04-08T01:57:38.664Z" }, + { url = "https://files.pythonhosted.org/packages/3a/ea/075aac6a84b7c271578d81a2f9968acb6e273002408729f2ddff517fed4a/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d3b99c535a9de0adced13d159c5a9cf65c325601aa30f4be08afd680643e9c15", size = 4219700, upload-time = "2026-04-08T01:57:40.625Z" }, + { url = "https://files.pythonhosted.org/packages/6c/7b/1c55db7242b5e5612b29fc7a630e91ee7a6e3c8e7bf5406d22e206875fbd/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d02c738dacda7dc2a74d1b2b3177042009d5cab7c7079db74afc19e56ca1b455", size = 4385982, upload-time = "2026-04-08T01:57:42.725Z" }, + { url = "https://files.pythonhosted.org/packages/cb/da/9870eec4b69c63ef5925bf7d8342b7e13bc2ee3d47791461c4e49ca212f4/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:04959522f938493042d595a736e7dbdff6eb6cc2339c11465b3ff89343b65f65", size = 4219115, upload-time = "2026-04-08T01:57:44.939Z" }, + { url = "https://files.pythonhosted.org/packages/f4/72/05aa5832b82dd341969e9a734d1812a6aadb088d9eb6f0430fc337cc5a8f/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:3986ac1dee6def53797289999eabe84798ad7817f3e97779b5061a95b0ee4968", size = 4385479, upload-time = "2026-04-08T01:57:46.86Z" }, + { url = "https://files.pythonhosted.org/packages/20/2a/1b016902351a523aa2bd446b50a5bc1175d7a7d1cf90fe2ef904f9b84ebc/cryptography-46.0.7-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:258514877e15963bd43b558917bc9f54cf7cf866c38aa576ebf47a77ddbc43a4", size = 3412829, upload-time = "2026-04-08T01:57:48.874Z" }, ] [[package]] -name = "cuda-bindings" -version = "13.2.0" +name = "darabonba-core" +version = "1.0.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cuda-pathfinder", marker = "sys_platform == 'linux'" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/fe/7351d7e586a8b4c9f89731bfe4cf0148223e8f9903ff09571f78b3fb0682/cuda_bindings-13.2.0-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08b395f79cb89ce0cd8effff07c4a1e20101b873c256a1aeb286e8fd7bd0f556", size = 5744254, upload-time = "2026-03-11T00:12:29.798Z" }, - { url = "https://files.pythonhosted.org/packages/aa/ef/184aa775e970fc089942cd9ec6302e6e44679d4c14549c6a7ea45bf7f798/cuda_bindings-13.2.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6f3682ec3c4769326aafc67c2ba669d97d688d0b7e63e659d36d2f8b72f32d6", size = 6329075, upload-time = "2026-03-11T00:12:32.319Z" }, - { url = "https://files.pythonhosted.org/packages/e0/a9/3a8241c6e19483ac1f1dcf5c10238205dcb8a6e9d0d4d4709240dff28ff4/cuda_bindings-13.2.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:721104c603f059780d287969be3d194a18d0cc3b713ed9049065a1107706759d", size = 5730273, upload-time = "2026-03-11T00:12:37.18Z" }, - { url = "https://files.pythonhosted.org/packages/e9/94/2748597f47bb1600cd466b20cab4159f1530a3a33fe7f70fee199b3abb9e/cuda_bindings-13.2.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1eba9504ac70667dd48313395fe05157518fd6371b532790e96fbb31bbb5a5e1", size = 6313924, upload-time = "2026-03-11T00:12:39.462Z" }, - { url = "https://files.pythonhosted.org/packages/52/c8/b2589d68acf7e3d63e2be330b84bc25712e97ed799affbca7edd7eae25d6/cuda_bindings-13.2.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e865447abfb83d6a98ad5130ed3c70b1fc295ae3eeee39fd07b4ddb0671b6788", size = 5722404, upload-time = "2026-03-11T00:12:44.041Z" }, - { url = "https://files.pythonhosted.org/packages/1f/92/f899f7bbb5617bb65ec52a6eac1e9a1447a86b916c4194f8a5001b8cde0c/cuda_bindings-13.2.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:46d8776a55d6d5da9dd6e9858fba2efcda2abe6743871dee47dd06eb8cb6d955", size = 6320619, upload-time = "2026-03-11T00:12:45.939Z" }, - { url = "https://files.pythonhosted.org/packages/df/93/eef988860a3ca985f82c4f3174fc0cdd94e07331ba9a92e8e064c260337f/cuda_bindings-13.2.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6629ca2df6f795b784752409bcaedbd22a7a651b74b56a165ebc0c9dcbd504d0", size = 5614610, upload-time = "2026-03-11T00:12:50.337Z" }, - { url = "https://files.pythonhosted.org/packages/18/23/6db3aba46864aee357ab2415135b3fe3da7e9f1fa0221fa2a86a5968099c/cuda_bindings-13.2.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7dca0da053d3b4cc4869eff49c61c03f3c5dbaa0bcd712317a358d5b8f3f385d", size = 6149914, upload-time = "2026-03-11T00:12:52.374Z" }, - { url = "https://files.pythonhosted.org/packages/c0/87/87a014f045b77c6de5c8527b0757fe644417b184e5367db977236a141602/cuda_bindings-13.2.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6464b30f46692d6c7f65d4a0e0450d81dd29de3afc1bb515653973d01c2cd6e", size = 5685673, upload-time = "2026-03-11T00:12:56.371Z" }, - { url = "https://files.pythonhosted.org/packages/ee/5e/c0fe77a73aaefd3fff25ffaccaac69c5a63eafdf8b9a4c476626ef0ac703/cuda_bindings-13.2.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f4af9f3e1be603fa12d5ad6cfca7844c9d230befa9792b5abdf7dd79979c3626", size = 6191386, upload-time = "2026-03-11T00:12:58.965Z" }, - { url = "https://files.pythonhosted.org/packages/5f/58/ed2c3b39c8dd5f96aa7a4abef0d47a73932c7a988e30f5fa428f00ed0da1/cuda_bindings-13.2.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df850a1ff8ce1b3385257b08e47b70e959932f5f432d0a4e46a355962b4e4771", size = 5507469, upload-time = "2026-03-11T00:13:04.063Z" }, - { url = "https://files.pythonhosted.org/packages/1f/01/0c941b112ceeb21439b05895eace78ca1aa2eaaf695c8521a068fd9b4c00/cuda_bindings-13.2.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8a16384c6494e5485f39314b0b4afb04bee48d49edb16d5d8593fd35bbd231b", size = 6059693, upload-time = "2026-03-11T00:13:06.003Z" }, -] - -[[package]] -name = "cuda-pathfinder" -version = "1.5.4" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/11/d0/c177e29701cf1d3008d7d2b16b5fc626592ce13bd535f8795c5f57187e0e/cuda_pathfinder-1.5.4-py3-none-any.whl", hash = "sha256:9563d3175ce1828531acf4b94e1c1c7d67208c347ca002493e2654878b26f4b7", size = 51657, upload-time = "2026-04-27T22:42:07.712Z" }, -] - -[[package]] -name = "cuda-toolkit" -version = "13.0.2" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/57/b2/453099f5f3b698d7d0eab38916aac44c7f76229f451709e2eb9db6615dcd/cuda_toolkit-13.0.2-py2.py3-none-any.whl", hash = "sha256:b198824cf2f54003f50d64ada3a0f184b42ca0846c1c94192fa269ecd97a66eb", size = 2364, upload-time = "2025-12-19T23:24:07.328Z" }, -] - -[package.optional-dependencies] -cudart = [ - { name = "nvidia-cuda-runtime", marker = "sys_platform == 'linux'" }, -] -cufft = [ - { name = "nvidia-cufft", marker = "sys_platform == 'linux'" }, -] -cufile = [ - { name = "nvidia-cufile", marker = "sys_platform == 'linux'" }, -] -cupti = [ - { name = "nvidia-cuda-cupti", marker = "sys_platform == 'linux'" }, -] -curand = [ - { name = "nvidia-curand", marker = "sys_platform == 'linux'" }, -] -cusolver = [ - { name = "nvidia-cusolver", marker = "sys_platform == 'linux'" }, -] -cusparse = [ - { name = "nvidia-cusparse", marker = "sys_platform == 'linux'" }, -] -nvjitlink = [ - { name = "nvidia-nvjitlink", marker = "sys_platform == 'linux'" }, -] -nvrtc = [ - { name = "nvidia-cuda-nvrtc", marker = "sys_platform == 'linux'" }, -] -nvtx = [ - { name = "nvidia-nvtx", marker = "sys_platform == 'linux'" }, -] - -[[package]] -name = "datasets" -version = "4.8.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "dill" }, - { name = "filelock" }, - { name = "fsspec", extra = ["http"] }, - { name = "httpx" }, - { name = "huggingface-hub" }, - { name = "multiprocess" }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "packaging" }, - { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "pandas", version = "3.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "pyarrow" }, - { name = "pyyaml" }, + { name = "aiohttp" }, + { name = "alibabacloud-tea" }, { name = "requests" }, - { name = "tqdm" }, - { name = "xxhash" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/66/34/14cd8e76f907f7d4dca2334cfeec9f81d30fd15c25a015f99aaea694eaed/datasets-4.8.5.tar.gz", hash = "sha256:0f0c1c3d56ffff2c93b2f4c63c95bac94f3d7e8621aea2a2a576275233bba772", size = 605649, upload-time = "2026-04-27T15:43:57.384Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/65/99/00f3196036501b53032c4b1ab8337a0b978dee832ed276dae3815df4e8b5/datasets-4.8.5-py3-none-any.whl", hash = "sha256:5079900781719c0e063a8efdd2cd95a31ad0c63209178669cd23cf1b926149ff", size = 528973, upload-time = "2026-04-27T15:43:53.702Z" }, -] - -[[package]] -name = "dill" -version = "0.4.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/81/e1/56027a71e31b02ddc53c7d65b01e68edf64dea2932122fe7746a516f75d5/dill-0.4.1.tar.gz", hash = "sha256:423092df4182177d4d8ba8290c8a5b640c66ab35ec7da59ccfa00f6fa3eea5fa", size = 187315, upload-time = "2026-01-19T02:36:56.85Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl", hash = "sha256:1e1ce33e978ae97fcfcff5638477032b801c46c7c65cf717f95fbc2248f79a9d", size = 120019, upload-time = "2026-01-19T02:36:55.663Z" }, + { url = "https://files.pythonhosted.org/packages/66/d3/a7daaee544c904548e665829b51a9fa2572acb82c73ad787a8ff90273002/darabonba_core-1.0.5-py3-none-any.whl", hash = "sha256:671ab8dbc4edc2a8f88013da71646839bb8914f1259efc069353243ef52ea27c", size = 24580, upload-time = "2025-12-12T07:53:59.494Z" }, ] [[package]] @@ -956,20 +825,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, ] -[[package]] -name = "fast-langdetect" -version = "1.0.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "fasttext-predict" }, - { name = "requests" }, - { name = "robust-downloader" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/24/83/639b1afc54b4cc73bf56c7f8efe50218b967364a9503947612b2b49212fa/fast_langdetect-1.0.1.tar.gz", hash = "sha256:22c651bf576aff3cb90edf070071f256996eabe612111ea9ced5b935e4376413", size = 796296, upload-time = "2026-05-06T04:24:14.984Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/46/1c/e4171f5235c2052ffcba1e19f8243cc6643ff678615d6cc803924343ed6f/fast_langdetect-1.0.1-py3-none-any.whl", hash = "sha256:d965844dfe44bb5e6042779dbc592618f227d447b752c4e2e503b0fd6abe5a4f", size = 789970, upload-time = "2026-05-06T04:24:13.436Z" }, -] - [[package]] name = "fastapi" version = "0.136.1" @@ -986,107 +841,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5a/ff/2e4eca3ade2c22fe1dea7043b8ee9dabe47753349eb1b56a202de8af6349/fastapi-0.136.1-py3-none-any.whl", hash = "sha256:a6e9d7eeada96c93a4d69cb03836b44fa34e2854accb7244a1ece36cd4781c3f", size = 117683, upload-time = "2026-04-23T16:49:42.437Z" }, ] -[[package]] -name = "fasttext-predict" -version = "0.9.2.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fc/0e/9defbb9385bcb1104cc1d686a14f7d9fafe5fe43f220cccb00f33d91bb47/fasttext_predict-0.9.2.4.tar.gz", hash = "sha256:18a6fb0d74c7df9280db1f96cb75d990bfd004fa9d669493ea3dd3d54f84dbc7", size = 16332, upload-time = "2024-11-23T17:24:44.801Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/ee/2350a58c071f873a454aae6bf60900fc3ddb024da3478407ac2057cbc757/fasttext_predict-0.9.2.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ba432f33228928df5f2af6dfa50560cd77f9859914cffd652303fb02ba100456", size = 103885, upload-time = "2024-11-23T17:22:42.533Z" }, - { url = "https://files.pythonhosted.org/packages/fd/68/e2f8a82c02b6c4333d454a1b0464942d3dae92e4657c08411035c99fe074/fasttext_predict-0.9.2.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6a8e8f17eb894d450168d2590e23d809e845bd4fad5e39b5708dacb2fdb9b2c7", size = 96415, upload-time = "2024-11-23T17:22:44.452Z" }, - { url = "https://files.pythonhosted.org/packages/a0/77/0c045793c56b9d143c44fab50f05506c47585532cc5a8f1668bf7b899ddf/fasttext_predict-0.9.2.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19565fdf0bb9427831cfc75fca736ab9d71ba7ce02e3ea951e5839beb66560b6", size = 281643, upload-time = "2024-11-23T17:22:46.342Z" }, - { url = "https://files.pythonhosted.org/packages/5b/ce/c735a67b858bbdb915f3de6d12bc0ad47f0bf0dfce8fc4d42b2ce65e1226/fasttext_predict-0.9.2.4-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb6986815506e3261c0b3f6227dce49eeb4fd3422dab9cd37e2db2fb3691c68b", size = 306088, upload-time = "2024-11-23T17:22:47.571Z" }, - { url = "https://files.pythonhosted.org/packages/52/a1/b5838f96b6b10f9d4166fd5a5bdc2c32fc42500c236c6318512c5ede99a9/fasttext_predict-0.9.2.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:229dfdf8943dd76231206c7c9179e3f99d45879e5b654626ee7b73b7fa495d53", size = 294031, upload-time = "2024-11-23T17:22:49.617Z" }, - { url = "https://files.pythonhosted.org/packages/76/0c/c655919969568ffc7667185595ae56c9cb35a3c4ec3c351654eebea75de5/fasttext_predict-0.9.2.4-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:397016ebfa9ec06d6dba09c29e295eea583ea3f45fa4592cc832b257dc84522e", size = 234108, upload-time = "2024-11-23T17:22:51.525Z" }, - { url = "https://files.pythonhosted.org/packages/f1/f0/b88ea4e4549d49b3202e0b4312f9bc1a42742618aaf2696d63508f861282/fasttext_predict-0.9.2.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fc93f9f8f7e982eb635bc860688be04f355fab3d76a243037e26862646f50430", size = 1209246, upload-time = "2024-11-23T17:22:53.656Z" }, - { url = "https://files.pythonhosted.org/packages/59/dc/1a916fe673f67066f6bb25b5372c282db8924a231662e250646e3ce90f93/fasttext_predict-0.9.2.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:f4be96ac0b01a3cda82be90e7f6afdafab98919995825c27babd2749a8319be9", size = 1098972, upload-time = "2024-11-23T17:22:55.792Z" }, - { url = "https://files.pythonhosted.org/packages/b5/9e/ef3aba387a9efdeefce2d9537ee9404b4b8bafe3f1209c8efa6a6cb8022d/fasttext_predict-0.9.2.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:f505f737f9493d22ee0c54af7c7eb7828624d5089a1e85072bdb1bd7d3f8f82e", size = 1385514, upload-time = "2024-11-23T17:22:58.022Z" }, - { url = "https://files.pythonhosted.org/packages/29/d8/b930d3eda35da0ad66335bfb154cb063cfc071dc9b7affe64ae0d90ac04c/fasttext_predict-0.9.2.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9ce69f28862dd551d43e27aa0a8de924b6b34412bff998c23c3d4abd70813183", size = 1275740, upload-time = "2024-11-23T17:23:00.376Z" }, - { url = "https://files.pythonhosted.org/packages/da/13/2611784710956acc1195bcc1ad476fb4d115a30a64175e8064bb83bc30ec/fasttext_predict-0.9.2.4-cp310-cp310-win32.whl", hash = "sha256:864b6bb543275aee74360eee1d2cc23a440f09991e97efcdcf0b9a5af00f9aa9", size = 90247, upload-time = "2024-11-23T17:23:01.534Z" }, - { url = "https://files.pythonhosted.org/packages/6d/33/df75b2a1e207eda91efe35766e09dba41ef735e390b156c9c3adc0014e68/fasttext_predict-0.9.2.4-cp310-cp310-win_amd64.whl", hash = "sha256:7e72abe12c13fd12f8bb137b1f7561096fbd3bb24905a27d9e93a4921ee68dc6", size = 103099, upload-time = "2024-11-23T17:23:02.526Z" }, - { url = "https://files.pythonhosted.org/packages/c7/12/5c1ddcc721c569132f6340498527b421dcb523470a0aee1b39fcb76c9fe3/fasttext_predict-0.9.2.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:147996c86aa0928c7118f85d18b6a77c458db9ca236db26d44ee5ceaab0c0b6b", size = 105258, upload-time = "2024-11-23T17:23:04.364Z" }, - { url = "https://files.pythonhosted.org/packages/9f/69/6efd7db47f95a5e2e6e71f69ab3271f5002e99bb88c8f1639c109609cf12/fasttext_predict-0.9.2.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5342f7363709e22524a31750c21e4b735b6666749a167fc03cc3bbf18ea8eccd", size = 97636, upload-time = "2024-11-23T17:23:06.166Z" }, - { url = "https://files.pythonhosted.org/packages/a7/67/953ca1707fdb2c4bfc5b495b78f98116b45e7b5c39a76875c8b6dcf81ce4/fasttext_predict-0.9.2.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6cbecd3908909339316f61db38030ce43890c25bddb06c955191458af13ccfc5", size = 284910, upload-time = "2024-11-23T17:23:07.296Z" }, - { url = "https://files.pythonhosted.org/packages/ef/c7/b60cf7e58baab5c798f616b2fa0692b8f78d6fc6279574fcfbd7c5235edb/fasttext_predict-0.9.2.4-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9de4fcfb54bec35be6b0dffcdc5ace1a3a07f79ee3e8d33d13b82cc4116c5f2f", size = 308768, upload-time = "2024-11-23T17:23:08.5Z" }, - { url = "https://files.pythonhosted.org/packages/ee/4d/fe2d0619494700f2a85db4cf8050977e1215f484ac8596187301655dc516/fasttext_predict-0.9.2.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5af82e09227d993befc00271407b9d3c8aae81d34b35f96208223faf609f4b0c", size = 298342, upload-time = "2024-11-23T17:23:10.408Z" }, - { url = "https://files.pythonhosted.org/packages/ea/06/f31dc802a2c9acab454eae47902674d92e381a97a363034d359961a955ce/fasttext_predict-0.9.2.4-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:337ee60179f32e8b0efa822e59316de15709c7684e7854021b4f6af82b7767ac", size = 236374, upload-time = "2024-11-23T17:23:12.435Z" }, - { url = "https://files.pythonhosted.org/packages/33/91/a4252c22f2fda855298ef683981d9986f89f9d36fd40ef4c2868cd84e4fd/fasttext_predict-0.9.2.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aa9da0c52e65a45dbc87df67015ec1d2712f04de47733e197176550521feea87", size = 1212640, upload-time = "2024-11-23T17:23:13.804Z" }, - { url = "https://files.pythonhosted.org/packages/ca/3e/47e9e844d15e2b7f64e1a2f1e5897b5c1d59b23ebff64494000259610d2c/fasttext_predict-0.9.2.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:495efde8afb622266c0e4de41978a6db731a0a685e1db032e7d22937850c9b44", size = 1100895, upload-time = "2024-11-23T17:23:16.109Z" }, - { url = "https://files.pythonhosted.org/packages/8a/f1/d49bfb81cd3b544021698f6feab1dca2fcca432097978828ad8322eab50b/fasttext_predict-0.9.2.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e5726ba34d79a143b69426e29905eb4d3f4ee8aee94927b3bea3dd566712986b", size = 1385913, upload-time = "2024-11-23T17:23:17.806Z" }, - { url = "https://files.pythonhosted.org/packages/ed/4a/37bd8d31e46116fcb203b4899aeec32a3adbacf8b09ba3f5d9e3f864b7e4/fasttext_predict-0.9.2.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5ac2f35830705c61dd848314c4c077a393608c181725dc353a69361821aa69a8", size = 1279522, upload-time = "2024-11-23T17:23:20.205Z" }, - { url = "https://files.pythonhosted.org/packages/64/4c/3ec10626e0b25527d10ed258ac1d7c56e5f88100d5c7abba093f918deff6/fasttext_predict-0.9.2.4-cp311-cp311-win32.whl", hash = "sha256:7b2f8a5cf5f2c451777dbb7ea4957c7919a57ce29a4157a0a381933c9ea6fa70", size = 91396, upload-time = "2024-11-23T17:23:22.194Z" }, - { url = "https://files.pythonhosted.org/packages/34/b0/456578e7269dace3d7a80a34b30c7757aea6aa34601853c58e5ad186d3d6/fasttext_predict-0.9.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:83a3c00fdb73a304bc529bc0ae0e225bc2cb956fcfb8e1c7a882b2a1aaa97e19", size = 104390, upload-time = "2024-11-23T17:23:23.332Z" }, - { url = "https://files.pythonhosted.org/packages/fb/fa/612bf85ce8928120843279ae256f4fffbb9758af81536ddf25f9136b1759/fasttext_predict-0.9.2.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:dcf8661da4f515551523470a745df246121f7e19736fcf3f48f04287963e6279", size = 104836, upload-time = "2024-11-23T17:23:25.219Z" }, - { url = "https://files.pythonhosted.org/packages/7a/04/106b6fe3f980d6a4f41bfb3106be22d42f87b1e8beb2959361ee4ee08960/fasttext_predict-0.9.2.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:99dbfcc3f353da2639fd04fc574a65ff4195b018311f790583147cdc6eb122f4", size = 97377, upload-time = "2024-11-23T17:23:26.319Z" }, - { url = "https://files.pythonhosted.org/packages/57/b9/b4962c92bd93dd234ea1d1cab643a86d948dab3f269e34a554a004ed6524/fasttext_predict-0.9.2.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:427e99ba963b2c744ed7233304037a83b7adece97de6f361cfd356aa43cb87f3", size = 283102, upload-time = "2024-11-23T17:23:27.497Z" }, - { url = "https://files.pythonhosted.org/packages/1d/18/92203820cf00b9a34f40f10456e4ed3019010a9b13a87e11d8b98cd98933/fasttext_predict-0.9.2.4-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8b9480cc75a906571a8e5fc717b91b4783f1820aaa5ed36a304d689280de8602", size = 307416, upload-time = "2024-11-23T17:23:28.68Z" }, - { url = "https://files.pythonhosted.org/packages/06/8d/334cd9acb84e569d37617444661ca7b59d1bc1a83abe42aa845d23fb1273/fasttext_predict-0.9.2.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11ef7af2a4431c76d2226e47334e86b9c4a78a98f6cb68b1ce9a1fc20e04c904", size = 296055, upload-time = "2024-11-23T17:23:29.934Z" }, - { url = "https://files.pythonhosted.org/packages/08/0b/2c83cc67eb5a29f182c8ea425e4b026db0593712edb8eaaf082501ca349f/fasttext_predict-0.9.2.4-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:ecb0b854596ba847742597b35c2d0134fcf3a59214d09351d01535854078d56b", size = 237279, upload-time = "2024-11-23T17:23:31.358Z" }, - { url = "https://files.pythonhosted.org/packages/14/81/0f1b3bda499ffeb7109fe51d9321dc74100db5a4801e3f9a9efe2348922d/fasttext_predict-0.9.2.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:fbbcfefac10f625d95fc42f28d76cc5bf0c12875f147b5a79108a2669e64a2dc", size = 1214253, upload-time = "2024-11-23T17:23:33.529Z" }, - { url = "https://files.pythonhosted.org/packages/d1/e6/b1a177a990c29b043a9658f9f4ec7234576ad31939362f9760c237f91d6d/fasttext_predict-0.9.2.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a8cb78a00c04b7eb7da18b4805f8557b36911dc4375c947d8938897d2e131841", size = 1099909, upload-time = "2024-11-23T17:23:34.983Z" }, - { url = "https://files.pythonhosted.org/packages/09/a0/7f23c7c4398f399552f39144849868991da543b66b9bfa8f49a6550fdd46/fasttext_predict-0.9.2.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:299ae56ad53e1381c65030143da7bcae12546fd32bc019215592ec1ee40fd19e", size = 1384102, upload-time = "2024-11-23T17:23:37.237Z" }, - { url = "https://files.pythonhosted.org/packages/e4/2c/568cf15fd48e4cefd0e605af62da5f5f51db3b012f8441d201d0a1173eb1/fasttext_predict-0.9.2.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:091938062002fe30d214f6e493a3a1e6180d401212d37eea23c29f4b55f3f347", size = 1281283, upload-time = "2024-11-23T17:23:39.676Z" }, - { url = "https://files.pythonhosted.org/packages/e7/68/0967ec3d5333c23fae1f1bdb851fa896f8f6068ef0ca3a8afee1aa2ee57d/fasttext_predict-0.9.2.4-cp312-cp312-win32.whl", hash = "sha256:981b8d9734623f8f9a8003970f765e14b1d91ee82c59c35e8eba6b76368fa95e", size = 91089, upload-time = "2024-11-23T17:23:41.082Z" }, - { url = "https://files.pythonhosted.org/packages/a7/c5/11c1f50b47f492d562974878ec34b6a0b84699f8b05e1cc3a75c65349784/fasttext_predict-0.9.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:bd3c33971c241577b0767e55d97acfda790f77378f9d5ee7872b6ee4bd63130b", size = 104889, upload-time = "2024-11-23T17:23:42.193Z" }, - { url = "https://files.pythonhosted.org/packages/89/fc/5cd65224c33e33d6faec3fa1047162dc266ed2213016139d936bd36fb7c3/fasttext_predict-0.9.2.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ddb85e62c95e4e02d417c782e3434ef65554df19e3522f5230f6be15a9373c05", size = 104916, upload-time = "2024-11-23T17:23:43.367Z" }, - { url = "https://files.pythonhosted.org/packages/d9/53/8d542773e32c9d98dd8c680e390fe7e6d4fc92ab3439dc1bb8e70c46c7ad/fasttext_predict-0.9.2.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:102129d45cf98dda871e83ae662f71d999b9ef6ff26bc842ffc1520a1f82930c", size = 97502, upload-time = "2024-11-23T17:23:44.447Z" }, - { url = "https://files.pythonhosted.org/packages/50/99/049fd6b01937705889bd9a00c31e5c55f0ae4b7704007b2ef7a82bf2b867/fasttext_predict-0.9.2.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05ba6a0fbf8cb2141b8ca2bc461db97af8ac31a62341e4696a75048b9de39e10", size = 282951, upload-time = "2024-11-23T17:23:46.31Z" }, - { url = "https://files.pythonhosted.org/packages/83/cb/79b71709edbb53c3c5f8a8b60fe2d3bc98d28a8e75367c89afedf3307aa9/fasttext_predict-0.9.2.4-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c7a779215571296ecfcf86545cb30ec3f1c6f43cbcd69f83cc4f67049375ea1", size = 307377, upload-time = "2024-11-23T17:23:47.685Z" }, - { url = "https://files.pythonhosted.org/packages/7c/4a/b15b7be003e76613173cc77d9c6cce4bf086073079354e0177deaa768f59/fasttext_predict-0.9.2.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddd2f03f3f206585543f5274b1dbc5f651bae141a1b14c9d5225c2a12e5075c2", size = 295746, upload-time = "2024-11-23T17:23:49.024Z" }, - { url = "https://files.pythonhosted.org/packages/e3/d3/f030cd45bdd4b052fcf23e730fdf0804e024b0cad43d7c7f8704faaec2f5/fasttext_predict-0.9.2.4-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:748f9edc3222a1fb7a61331c4e06d3b7f2390ae493f91f09d372a00b81762a8d", size = 236939, upload-time = "2024-11-23T17:23:50.306Z" }, - { url = "https://files.pythonhosted.org/packages/a2/01/6f2985afd58fdc5f4ecd058d5d9427d03081d468960982df97316c03f6bb/fasttext_predict-0.9.2.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1aee47a40757cd24272b34eaf9ceeea86577fd0761b0fd0e41599c6549abdf04", size = 1214189, upload-time = "2024-11-23T17:23:51.647Z" }, - { url = "https://files.pythonhosted.org/packages/75/07/931bcdd4e2406e45e54d57e056c2e0766616a5280a18fbf6ef078aa439ab/fasttext_predict-0.9.2.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:6ff0f152391ee03ffc18495322100c01735224f7843533a7c4ff33c8853d7be1", size = 1099889, upload-time = "2024-11-23T17:23:53.127Z" }, - { url = "https://files.pythonhosted.org/packages/a2/eb/6521b4bbf387252a96a6dc0f54986f078a93db0a9d4ba77258dcf1fa8be7/fasttext_predict-0.9.2.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4d92f5265318b41d6e68659fd459babbff692484e492c5013995b90a56b517c9", size = 1383959, upload-time = "2024-11-23T17:23:54.521Z" }, - { url = "https://files.pythonhosted.org/packages/b7/6b/d56606761afb3a3912c52971f0f804e2e9065f049c412b96c47d6fca6218/fasttext_predict-0.9.2.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3a7720cce1b8689d88df76cac1425e84f9911c69a4e40a5309d7d3435e1bb97c", size = 1281097, upload-time = "2024-11-23T17:23:55.9Z" }, - { url = "https://files.pythonhosted.org/packages/91/83/55bb4a37bb3b3a428941f4e1323c345a662254f576f8860b3098d9742510/fasttext_predict-0.9.2.4-cp313-cp313-win32.whl", hash = "sha256:d16acfced7871ed0cd55b476f0dbdddc7a5da1ffc9745a3c5674846cf1555886", size = 91137, upload-time = "2024-11-23T17:23:57.886Z" }, - { url = "https://files.pythonhosted.org/packages/9c/1d/c1ccc8790ce54200c84164d99282f088dddb9760aeefc8860856aafa40b4/fasttext_predict-0.9.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:96a23328729ce62a851f8953582e576ca075ee78d637df4a78a2b3609784849e", size = 104896, upload-time = "2024-11-23T17:23:59.028Z" }, - { url = "https://files.pythonhosted.org/packages/a4/c9/a1ccc749c59e2480767645ecc03bd842a7fa5b2b780d69ac370e6f8298d2/fasttext_predict-0.9.2.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:b1357d0d9d8568db84668b57e7c6880b9c46f757e8954ad37634402d36f09dba", size = 109401, upload-time = "2024-11-23T17:24:00.191Z" }, - { url = "https://files.pythonhosted.org/packages/90/1f/33182b76eb0524155e8ff93e7939feaf5325385e5ff2a154f383d9a02317/fasttext_predict-0.9.2.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9604c464c5d86c7eba34b040080be7012e246ef512b819e428b7deb817290dae", size = 102131, upload-time = "2024-11-23T17:24:02.052Z" }, - { url = "https://files.pythonhosted.org/packages/2b/df/1886daea373382e573f28ce49e3fc8fb6b0ee0c84e2b0becf5b254cd93fb/fasttext_predict-0.9.2.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc6da186c2e4497cbfaba9c5424e58c7b72728b25d980829eb96daccd7cface1", size = 287396, upload-time = "2024-11-23T17:24:03.294Z" }, - { url = "https://files.pythonhosted.org/packages/35/8f/d1c2c0f0251bee898d508253a437683b0480a1074cfb25ded1f7fdbb925a/fasttext_predict-0.9.2.4-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:366ed2ca4f4170418f3585e92059cf17ee2c963bf179111c5b8ba48f06cd69d1", size = 311090, upload-time = "2024-11-23T17:24:04.625Z" }, - { url = "https://files.pythonhosted.org/packages/5d/52/07d6ed46148662fae84166bc69d944caca87fabc850ebfbd9640b20dafe7/fasttext_predict-0.9.2.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f1877edbb815a43e7d38cc7332202e759054cf0b5a4b7e34a743c0f5d6e7333", size = 300359, upload-time = "2024-11-23T17:24:06.486Z" }, - { url = "https://files.pythonhosted.org/packages/fa/a1/751ff471a991e5ed0bae9e7fa6fc8d8ab76b233a7838a27d70d62bed0c8e/fasttext_predict-0.9.2.4-cp313-cp313t-manylinux_2_31_armv7l.whl", hash = "sha256:f63c31352ba6fc910290b0fe12733770acd8cfa0945fcb9cf3984d241abcfc9d", size = 241164, upload-time = "2024-11-23T17:24:08.501Z" }, - { url = "https://files.pythonhosted.org/packages/94/19/e251f699a0e9c001fa672ea0929c456160faa68ecfafc19e8def09982b6a/fasttext_predict-0.9.2.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:898e14b03fbfb0a8d9a5185a0a00ff656772b3baa37cad122e06e8e4d6da3832", size = 1218629, upload-time = "2024-11-23T17:24:10.04Z" }, - { url = "https://files.pythonhosted.org/packages/1d/46/1af2f779f8cfd746496a226581f747d3051888e3e3c5b2ca37231e5d04f8/fasttext_predict-0.9.2.4-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:a33bb5832a69fc54d18cadcf015677c1acb5ccc7f0125d261df2a89f8aff01f6", size = 1100535, upload-time = "2024-11-23T17:24:11.5Z" }, - { url = "https://files.pythonhosted.org/packages/4c/b7/900ccd74a9ba8be7ca6d04bba684e9c43fb0dbed8a3d12ec0536228e2c32/fasttext_predict-0.9.2.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7fe9e98bd0701d598bf245eb2fbf592145cd03551684a2102a4b301294b9bd87", size = 1387651, upload-time = "2024-11-23T17:24:13.135Z" }, - { url = "https://files.pythonhosted.org/packages/0b/5a/99fdaed054079f7c96e70df0d7016c4eb6b9e487a614396dd8f849244a52/fasttext_predict-0.9.2.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dcb8c5a74c1785f005fd83d445137437b79ac70a2dfbfe4bb1b09aa5643be545", size = 1286189, upload-time = "2024-11-23T17:24:14.615Z" }, - { url = "https://files.pythonhosted.org/packages/87/6a/9114d65b3f7a9c20a62b9d2ca3b770ee65de849e4131cc7aa58cdc50cb07/fasttext_predict-0.9.2.4-cp313-cp313t-win32.whl", hash = "sha256:a85c7de3d4480faa12b930637fca9c23144d1520786fedf9ba8edd8642ed4aea", size = 95905, upload-time = "2024-11-23T17:24:15.868Z" }, - { url = "https://files.pythonhosted.org/packages/31/fb/6d251f3fdfe3346ee60d091f55106513e509659ee005ad39c914182c96f4/fasttext_predict-0.9.2.4-cp313-cp313t-win_amd64.whl", hash = "sha256:be0933fa4af7abae09c703d28f9e17c80e7069eb6f92100b21985b777f4ea275", size = 110325, upload-time = "2024-11-23T17:24:16.984Z" }, - { url = "https://files.pythonhosted.org/packages/20/ba/c7fe88fbf59935118b9a756e3ae671d8ddcdd58170f4e53d60d9863b29e6/fasttext_predict-0.9.2.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b11ba9414aa71754f798a102cf7d3df53307055b2b0f0b258a3f2d59c5a12cfa", size = 133206, upload-time = "2024-11-23T17:24:38.986Z" }, - { url = "https://files.pythonhosted.org/packages/50/a6/8807d54b25905d3d91e7b16705632a3ccf4adf6457daae959c4f42987c27/fasttext_predict-0.9.2.4-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c89c769e3646bdb341487a68835239f35a4a0959cc1a8d8a7d215f40b22a230", size = 149227, upload-time = "2024-11-23T17:24:40.285Z" }, - { url = "https://files.pythonhosted.org/packages/27/4a/55ae88864d5711822ecf6f37d54d655dc2e3617ae70d07bf28c08d9bea5f/fasttext_predict-0.9.2.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f3b9cd4a2cf4c4853323f57c5da6ecffca6aeb9b6d8751ee40fe611d6edf8dd", size = 140205, upload-time = "2024-11-23T17:24:42.307Z" }, - { url = "https://files.pythonhosted.org/packages/a0/33/1b5baa8960548100fddc40908780f0c18fddff8a514f9cd3dd0f6676746d/fasttext_predict-0.9.2.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1c92905396c74e5cb29ddbfa763b5addec1581b6e0eae4cbe82248dfe733557e", size = 102845, upload-time = "2024-11-23T17:24:43.64Z" }, -] - -[[package]] -name = "filelock" -version = "3.29.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b5/fe/997687a931ab51049acce6fa1f23e8f01216374ea81374ddee763c493db5/filelock-3.29.0.tar.gz", hash = "sha256:69974355e960702e789734cb4871f884ea6fe50bd8404051a3530bc07809cf90", size = 57571, upload-time = "2026-04-19T15:39:10.068Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl", hash = "sha256:96f5f6344709aa1572bbf631c640e4ebeeb519e08da902c39a001882f30ac258", size = 39812, upload-time = "2026-04-19T15:39:08.752Z" }, -] - -[[package]] -name = "flagembedding" -version = "1.4.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "accelerate" }, - { name = "datasets" }, - { name = "ir-datasets" }, - { name = "peft" }, - { name = "protobuf" }, - { name = "sentence-transformers" }, - { name = "sentencepiece" }, - { name = "torch" }, - { name = "transformers" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/07/51/22dc9c448577542b3c3d75ae48eda0a97dfa29552a56cddd87437fec5940/flagembedding-1.4.0.tar.gz", hash = "sha256:2079c14ecf0341d64519785e53c0401122a752e5b6a0ac497f23c02f0d21b3b9", size = 175024, upload-time = "2026-04-22T16:09:54.326Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/73/70/3593ccb1299f8440369fd3c542ef2f5c09718eb8c7628614303e65dd38f8/flagembedding-1.4.0-py3-none-any.whl", hash = "sha256:fb1856b312851591341cf4533187350e9ce43f66bbf195c66f25a73266ff7db9", size = 247714, upload-time = "2026-04-22T16:09:52.448Z" }, -] - [[package]] name = "frozenlist" version = "1.8.0" @@ -1208,20 +962,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, ] -[[package]] -name = "fsspec" -version = "2026.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/51/7c/f60c259dcbf4f0c47cc4ddb8f7720d2dcdc8888c8e5ad84c73ea4531cc5b/fsspec-2026.2.0.tar.gz", hash = "sha256:6544e34b16869f5aacd5b90bdf1a71acb37792ea3ddf6125ee69a22a53fb8bff", size = 313441, upload-time = "2026-02-05T21:50:53.743Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl", hash = "sha256:98de475b5cb3bd66bedd5c4679e87b4fdfe1a3bf4d707b151b3c07e58c9a2437", size = 202505, upload-time = "2026-02-05T21:50:51.819Z" }, -] - -[package.optional-dependencies] -http = [ - { name = "aiohttp" }, -] - [[package]] name = "grpcio" version = "1.80.0" @@ -1292,38 +1032,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, ] -[[package]] -name = "hf-xet" -version = "1.5.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/74/d8/5c06fc76461418326a7decf8367480c35be11a41fd938633929c60a9ec6b/hf_xet-1.5.0.tar.gz", hash = "sha256:e0fb0a34d9f406eed88233e829a67ec016bec5af19e480eac65a233ea289a948", size = 837196, upload-time = "2026-05-06T06:18:15.583Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/68/9b/6912c99070915a4f28119e3c5b52a9abd1eec0ad5cb293b8c967a0c6f5a2/hf_xet-1.5.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:7d70fe2ce97b9db73b9c9b9c81fe3693640aec83416a966c446afea54acfae3c", size = 4023383, upload-time = "2026-05-06T06:17:53.947Z" }, - { url = "https://files.pythonhosted.org/packages/0f/6d/9563cfde59b5d8128a9c7ec972a087f4c782e4f7bac5a85234edfd5d5e49/hf_xet-1.5.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:73a0dae8c71de3b0633a45c73f4a4a5ed09e94b43441d82981a781d4f12baa42", size = 3792751, upload-time = "2026-05-06T06:17:51.791Z" }, - { url = "https://files.pythonhosted.org/packages/07/a5/ed5a0cf35b49a0571af5a8f53416dad1877a718c021c9937c3a53cb45781/hf_xet-1.5.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a60290ec57e9b71767fba7c3645ddafdd0759974b540441510c629c6db6db24a", size = 4456058, upload-time = "2026-05-06T06:17:40.735Z" }, - { url = "https://files.pythonhosted.org/packages/60/fb/3ae8bf2a7a37a4197d0195d7247fd25b3952e15cb8a599e285dfaa6f52b3/hf_xet-1.5.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:e5de0f6deada0dada870bb376a11bcd1f08abf3a968a6d118f33e72d1b1eb480", size = 4250783, upload-time = "2026-05-06T06:17:38.412Z" }, - { url = "https://files.pythonhosted.org/packages/a2/9b/8bae40d4d91525085137196e84eb0ed49cf65b5e96e5c3ecdadd8bd0fac2/hf_xet-1.5.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c799d49f1a5544a0ef7591c0ee75e0d6b93d6f56dc7a4979f59f7518d2872216", size = 4445594, upload-time = "2026-05-06T06:18:04.219Z" }, - { url = "https://files.pythonhosted.org/packages/13/59/c74efbbd4e8728172b2cc72a2bc014d2947a4b7bdced932fbd3f5da1a4e5/hf_xet-1.5.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2baea1b0b989e5c152fe81425f7745ddc8901280ba3d97c98d8cdece7b706c60", size = 4663995, upload-time = "2026-05-06T06:18:06.1Z" }, - { url = "https://files.pythonhosted.org/packages/73/32/8e1e0410af64cda9b139d1dcebdc993a8ff9c8c7c0e2696ae356d75ccc0d/hf_xet-1.5.0-cp313-cp313t-win_amd64.whl", hash = "sha256:526345b3ed45f374f6317349df489167606736c876241ba984105afe7fd4839d", size = 3966608, upload-time = "2026-05-06T06:18:19.74Z" }, - { url = "https://files.pythonhosted.org/packages/fc/34/a8febc8f4edbea8b3e21b02ebc8b628679b84ba7e45cde624a7736b51500/hf_xet-1.5.0-cp313-cp313t-win_arm64.whl", hash = "sha256:786d28e2eb8315d5035544b9d137b4a842d600c434bb91bf7d0d953cce906ad4", size = 3796946, upload-time = "2026-05-06T06:18:17.568Z" }, - { url = "https://files.pythonhosted.org/packages/2a/20/8fc8996afe5815fa1a6be8e9e5c02f24500f409d599e905800d498a4e14d/hf_xet-1.5.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:872d5601e6deea30d15865ede55d29eac6daf5a534ab417b99b6ef6b076dd96c", size = 4023495, upload-time = "2026-05-06T06:18:01.94Z" }, - { url = "https://files.pythonhosted.org/packages/32/6a/93d84463c00cecb561a7508aa6303e35ee2894294eac14245526924415fe/hf_xet-1.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9929561f5abf4581c8ea79587881dfef6b8abb2a0d8a51915936fc2a614f4e73", size = 3792731, upload-time = "2026-05-06T06:18:00.021Z" }, - { url = "https://files.pythonhosted.org/packages/9d/5a/8ec8e0c863b382d00b3c2e2af6ded6b06371be617144a625903a6d562f4b/hf_xet-1.5.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f7b7bbae318e583a86fb21e5a4a175d6721d628a2874f4bd022d0e660c32a682", size = 4456738, upload-time = "2026-05-06T06:17:49.574Z" }, - { url = "https://files.pythonhosted.org/packages/c5/ca/f7effa1a67717da2bcc6b6c28f71c6ca648c77acaec4e2c32f40cbe16d85/hf_xet-1.5.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:cf7b2dc6f31a4ea754bb50f74cde482dcf5d366d184076d8530b9872787f3761", size = 4251622, upload-time = "2026-05-06T06:17:47.096Z" }, - { url = "https://files.pythonhosted.org/packages/65/f2/19247dba3e231cf77dec59ddfb878f00057635ff773d099c9b59d37812c3/hf_xet-1.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8dbcbab554c9ef158ef2c991545c3e970ddd8cc7acdcd0a78c5a41095dab4ded", size = 4445667, upload-time = "2026-05-06T06:18:11.983Z" }, - { url = "https://files.pythonhosted.org/packages/7f/64/6f116801a3bcfb6f59f5c251f48cadc47ea54026441c4a385079286a94fa/hf_xet-1.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5906bf7718d3636dc13402914736abe723492cb730f744834f5f5b67d3a12702", size = 4664619, upload-time = "2026-05-06T06:18:13.771Z" }, - { url = "https://files.pythonhosted.org/packages/5c/e8/069542d37946ed08669b127e1496fa99e78196d71de8d41eda5e9f1b7a58/hf_xet-1.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:5f3dc2248fc01cc0a00cd392ab497f1ca373fcbc7e3f2da1f452480b384e839e", size = 3966802, upload-time = "2026-05-06T06:18:28.162Z" }, - { url = "https://files.pythonhosted.org/packages/f9/91/fc6fdec27b14d04e88c386ac0a0129732b53fa23f7c4a78f4b83a039c567/hf_xet-1.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:b285cea1b5bab46b758772716ba8d6854a1a0310fed1c249d678a8b38601e5a0", size = 3797168, upload-time = "2026-05-06T06:18:26.287Z" }, - { url = "https://files.pythonhosted.org/packages/3d/fb/69ff198a82cae7eb1a69fb84d93b3a3e4816564d76817fe541ddc96874eb/hf_xet-1.5.0-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:dad0dc84e941b8ba3c860659fe1fdc35c049d47cce293f003287757e971a8f56", size = 4030814, upload-time = "2026-05-06T06:17:57.933Z" }, - { url = "https://files.pythonhosted.org/packages/9b/ff/edcc2b40162bef3ff78e14ab637e5f3b89243d6aee72f5949d3bb6a5af83/hf_xet-1.5.0-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:fd6e5a9b0fdac4ed03ed45ef79254a655b1aaab514a02202617fbf643f5fdf7a", size = 3798444, upload-time = "2026-05-06T06:17:55.79Z" }, - { url = "https://files.pythonhosted.org/packages/49/4d/103f76b04310e5e57656696cc184690d20c466af0bca3ca88f8c8ea5d4f3/hf_xet-1.5.0-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3531b1823a0e6d77d80f9ed15ca0e00f0d115094f8ac033d5cae88f4564cc949", size = 4465986, upload-time = "2026-05-06T06:17:44.886Z" }, - { url = "https://files.pythonhosted.org/packages/c4/a2/546f47f464737b3edbab6f8ddb57f2599b93d2cbb66f06abb475ccb48651/hf_xet-1.5.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:9a0ee58cd18d5ea799f7ed11290bbccbe56bdd8b1d97ca74b9cc49a3945d7a3b", size = 4259865, upload-time = "2026-05-06T06:17:42.639Z" }, - { url = "https://files.pythonhosted.org/packages/95/7f/1be593c1f28613be2e196473481cd81bfc5910795e30a34e8f744f6cac4f/hf_xet-1.5.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1e60df5a42e9bed8628b6416af2cba4cba57ae9f02de226a06b020d98e1aab18", size = 4459835, upload-time = "2026-05-06T06:18:08.026Z" }, - { url = "https://files.pythonhosted.org/packages/aa/b2/703569fc881f3284487e68cda7b42179978480da3c438042a6bbbb4a671c/hf_xet-1.5.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4b35549ce62601b84da4ff9b24d970032ace3d4430f52d91bcbb26c901d6c690", size = 4672414, upload-time = "2026-05-06T06:18:09.864Z" }, - { url = "https://files.pythonhosted.org/packages/af/37/1b6def445c567286b50aa3b33828158e135b1be44938dde59f11382a500c/hf_xet-1.5.0-cp37-abi3-win_amd64.whl", hash = "sha256:2806c7c17b4d23f8d88f7c4814f838c3b6150773fe339c20af23e1cfaf2797e4", size = 3977238, upload-time = "2026-05-06T06:18:23.621Z" }, - { url = "https://files.pythonhosted.org/packages/62/94/3b66b148778ee100dcfd69c2ca22b57b41b44d3063ceec934f209e9184ce/hf_xet-1.5.0-cp37-abi3-win_arm64.whl", hash = "sha256:b6c9df403040248c76d808d3e047d64db2d923bae593eb244c41e425cf6cd7be", size = 3806916, upload-time = "2026-05-06T06:18:21.7Z" }, -] - [[package]] name = "httpcore" version = "1.0.9" @@ -1395,26 +1103,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, ] -[[package]] -name = "huggingface-hub" -version = "1.14.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "filelock" }, - { name = "fsspec" }, - { name = "hf-xet", marker = "platform_machine == 'AMD64' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, - { name = "httpx" }, - { name = "packaging" }, - { name = "pyyaml" }, - { name = "tqdm" }, - { name = "typer" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/39/40/43109e943fd718b0ccd0cd61eb4f1c347df22bf81f5874c6f22adf44bcff/huggingface_hub-1.14.0.tar.gz", hash = "sha256:d6d2c9cd6be1d02ae9ec6672d5587d10a427f377db688e82528f426a041622c2", size = 782365, upload-time = "2026-05-06T14:14:34.278Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/89/a5/33b49ba7bea7c41bb37f74ec0f8beea0831e052330196633fe2c77516ea6/huggingface_hub-1.14.0-py3-none-any.whl", hash = "sha256:efe075535c62e130b30e836b138e13785f6f043d1f0539e0a39aa411a99e90b8", size = 661479, upload-time = "2026-05-06T14:14:32.029Z" }, -] - [[package]] name = "idna" version = "3.15" @@ -1424,97 +1112,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d2/23/408243171aa9aaba178d3e2559159c24c1171a641aa83b67bdd3394ead8e/idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8", size = 72340, upload-time = "2026-05-12T22:45:55.733Z" }, ] -[[package]] -name = "ijson" -version = "3.5.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f4/57/60d1a6a512f2f0508d0bc8b4f1cc5616fd3196619b66bd6a01f9155a1292/ijson-3.5.0.tar.gz", hash = "sha256:94688760720e3f5212731b3cb8d30267f9a045fb38fb3870254e7b9504246f31", size = 68658, upload-time = "2026-02-24T03:58:30.974Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6e/32/21c1b47a1afb7319944d0b9685c0997a9d574a77b030c82f6a1ac2cef4eb/ijson-3.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ea8dcac10d86adaeead454bc25c97b68d0bda573d5fd6f86f5e21cf8f7906f88", size = 88935, upload-time = "2026-02-24T03:56:40.591Z" }, - { url = "https://files.pythonhosted.org/packages/86/f7/6ac7ebbb3cd767c87cdcbb950a6754afd1c0977756347bfe03eb8e5b866d/ijson-3.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:92b0495bbb2150bbf14fc5d98fb6d76bcd1c526605a172709e602e6fedc96495", size = 60567, upload-time = "2026-02-24T03:56:41.919Z" }, - { url = "https://files.pythonhosted.org/packages/c4/98/1140de9ae872468a8bc2e87c171228e25e58b1eb696b7fb430f7590fea44/ijson-3.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7af0c4c8943be8b09a4e57bdc1da6001dae7b36526d4154fe5c8224738d0921f", size = 60620, upload-time = "2026-02-24T03:56:42.764Z" }, - { url = "https://files.pythonhosted.org/packages/60/e1/67dfe0774e4c7ca6ec8702e280e8764d356f3db54358999818cda6df7679/ijson-3.5.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:45887d5e84ff0d2b138c926cebd9071830733968afe8d9d12080b3c178c7f918", size = 126558, upload-time = "2026-02-24T03:56:43.922Z" }, - { url = "https://files.pythonhosted.org/packages/1f/ef/23d614fc773d428caeb6e197218b7e32adcc668ff5b98777039149571208/ijson-3.5.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9a70b575be8e57a28c80e90ed349ad3a851c3478524c70e36e07d6092ecd12c9", size = 133091, upload-time = "2026-02-24T03:56:45.291Z" }, - { url = "https://files.pythonhosted.org/packages/b8/80/99727603cd8a1d32edafa4392f4056b2420bf48c15afd34481c68a2d4435/ijson-3.5.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2adeecd45830bfd5580ca79a584154713aabef0b9607e16249133df5d2859813", size = 130249, upload-time = "2026-02-24T03:56:46.333Z" }, - { url = "https://files.pythonhosted.org/packages/0b/94/3a3d623ca80768e834be8a834ef05960e3b9e79af1a911704ff10c9e8792/ijson-3.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d873e72889e7fc5962ab58909f1adff338d7c2f49e450e5b5fe844eff8155a14", size = 133501, upload-time = "2026-02-24T03:56:47.54Z" }, - { url = "https://files.pythonhosted.org/packages/cf/f6/df2c14ad340834eccee379046f155e4b66a16ddafd445429dee7b3323614/ijson-3.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9a88c559456a79708592234d697645d92b599718f4cbbeaa6515f83ac63ca0ae", size = 128438, upload-time = "2026-02-24T03:56:48.455Z" }, - { url = "https://files.pythonhosted.org/packages/0c/7e/9ff5b8b5fee113f5607bc4149b707382a898eeb545153189b075e5ec8d59/ijson-3.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cf83f58ad50dc0d39a2105cb26d4f359b38f42cef68b913170d4d47d97d97ba5", size = 131116, upload-time = "2026-02-24T03:56:49.737Z" }, - { url = "https://files.pythonhosted.org/packages/64/20/954ce0d440d7cf72a3d8361b14406f9cdbf624b1625c10f8488857c769d6/ijson-3.5.0-cp310-cp310-win32.whl", hash = "sha256:aec4580a7712a19b1f95cd41bed260fc6a31266d37ef941827772a4c199e8143", size = 52724, upload-time = "2026-02-24T03:56:50.932Z" }, - { url = "https://files.pythonhosted.org/packages/24/33/ece87d60502c6115642cbabeb8c122fa982212b392bc4f4ff5aab8e02dac/ijson-3.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:9a9c4c70501e23e8eb1675330686d1598eebfa14b6f0dbc8f00c2e081cc628fa", size = 55125, upload-time = "2026-02-24T03:56:51.942Z" }, - { url = "https://files.pythonhosted.org/packages/65/da/644343198abca5e0f6e2486063f8d8f3c443ca0ef5e5c890e51ef6032e33/ijson-3.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5616311404b858d32740b7ad8b9a799c62165f5ecb85d0a8ed16c21665a90533", size = 88964, upload-time = "2026-02-24T03:56:53.099Z" }, - { url = "https://files.pythonhosted.org/packages/5b/63/8621190aa2baf96156dfd4c632b6aa9f1464411e50b98750c09acc0505ea/ijson-3.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e9733f94029dd41702d573ef64752e2556e72aea14623d6dbb7a44ca1ccf30fd", size = 60582, upload-time = "2026-02-24T03:56:54.261Z" }, - { url = "https://files.pythonhosted.org/packages/20/31/6a3f041fdd17dacff33b7d7d3ba3df6dca48740108340c6042f974b2ad20/ijson-3.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:db8398c6721b98412a4f618da8022550c8b9c5d9214040646071b5deb4d4a393", size = 60632, upload-time = "2026-02-24T03:56:55.159Z" }, - { url = "https://files.pythonhosted.org/packages/e4/68/474541998abbdecfd46a744536878335de89aceb9f085bff1aaf35575ceb/ijson-3.5.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c061314845c08163b1784b6076ea5f075372461a32e6916f4e5f211fd4130b64", size = 131988, upload-time = "2026-02-24T03:56:56.35Z" }, - { url = "https://files.pythonhosted.org/packages/cd/32/e05ff8b72a44fe9d192f41c5dcbc35cfa87efc280cdbfe539ffaf4a7535e/ijson-3.5.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1111a1c5ac79119c5d6e836f900c1a53844b50a18af38311baa6bb61e2645aca", size = 138669, upload-time = "2026-02-24T03:56:57.555Z" }, - { url = "https://files.pythonhosted.org/packages/49/b5/955a83b031102c7a602e2c06d03aff0a0e584212f09edb94ccc754d203ac/ijson-3.5.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1e74aff8c681c24002b61b1822f9511d4c384f324f7dbc08c78538e01fdc9fcb", size = 135093, upload-time = "2026-02-24T03:56:59.267Z" }, - { url = "https://files.pythonhosted.org/packages/e8/f2/30250cfcb4d2766669b31f6732689aab2bb91de426a15a3ebe482df7ee48/ijson-3.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:739a7229b1b0cc5f7e2785a6e7a5fc915e850d3fed9588d0e89a09f88a417253", size = 138715, upload-time = "2026-02-24T03:57:00.491Z" }, - { url = "https://files.pythonhosted.org/packages/a2/05/785a145d7e75e04e04480d59b6323cd4b1d9013a6cd8643fa635fbc93490/ijson-3.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ef88712160360cab3ca6471a4e5418243f8b267cf1fe1620879d1b5558babc71", size = 133194, upload-time = "2026-02-24T03:57:01.759Z" }, - { url = "https://files.pythonhosted.org/packages/14/eb/80d6f8a748dead4034cea0939494a67d10ccf88d6413bf6e860393139676/ijson-3.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6ca0d1b6b5f8166a6248f4309497585fb8553b04bc8179a0260fad636cfdb798", size = 135588, upload-time = "2026-02-24T03:57:03.131Z" }, - { url = "https://files.pythonhosted.org/packages/ee/a8/bbc21f9400ebdbca48fab272593e0d1f875691be1e927d264d90d48b8c47/ijson-3.5.0-cp311-cp311-win32.whl", hash = "sha256:966039cf9047c7967febf7b9a52ec6f38f5464a4c7fbb5565e0224b7376fefff", size = 52721, upload-time = "2026-02-24T03:57:04.365Z" }, - { url = "https://files.pythonhosted.org/packages/0d/2e/4e8c0208b8f920ee80c88c956f93e78318f2cfb646455353b182738b490c/ijson-3.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:6bad6a1634cb7c9f3f4c7e52325283b35b565f5b6cc27d42660c6912ce883422", size = 55121, upload-time = "2026-02-24T03:57:05.498Z" }, - { url = "https://files.pythonhosted.org/packages/aa/17/9c63c7688025f3a8c47ea717b8306649c8c7244e49e20a2be4e3515dc75c/ijson-3.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1ebefbe149a6106cc848a3eaf536af51a9b5ccc9082de801389f152dba6ab755", size = 88536, upload-time = "2026-02-24T03:57:06.809Z" }, - { url = "https://files.pythonhosted.org/packages/6f/dd/e15c2400244c117b06585452ebc63ae254f5a6964f712306afd1422daae0/ijson-3.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:19e30d9f00f82e64de689c0b8651b9cfed879c184b139d7e1ea5030cec401c21", size = 60499, upload-time = "2026-02-24T03:57:09.155Z" }, - { url = "https://files.pythonhosted.org/packages/77/a9/bf4fe3538a0c965f16b406f180a06105b875da83f0743e36246be64ef550/ijson-3.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a04a33ee78a6f27b9b8528c1ca3c207b1df3b8b867a4cf2fcc4109986f35c227", size = 60330, upload-time = "2026-02-24T03:57:10.574Z" }, - { url = "https://files.pythonhosted.org/packages/31/76/6f91bdb019dd978fce1bc5ea1cd620cfc096d258126c91db2c03a20a7f34/ijson-3.5.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7d48dc2984af02eb3c56edfb3f13b3f62f2f3e4fe36f058c8cfc75d93adf4fed", size = 138977, upload-time = "2026-02-24T03:57:11.932Z" }, - { url = "https://files.pythonhosted.org/packages/11/be/bbc983059e48a54b0121ee60042979faed7674490bbe7b2c41560db3f436/ijson-3.5.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1e73a44844d9adbca9cf2c4132cd875933e83f3d4b23881fcaf82be83644c7d", size = 149785, upload-time = "2026-02-24T03:57:13.255Z" }, - { url = "https://files.pythonhosted.org/packages/6d/81/2fee58f9024a3449aee83edfa7167fb5ccd7e1af2557300e28531bb68e16/ijson-3.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7389a56b8562a19948bdf1d7bae3a2edc8c7f86fb59834dcb1c4c722818e645a", size = 149729, upload-time = "2026-02-24T03:57:14.191Z" }, - { url = "https://files.pythonhosted.org/packages/c7/56/f1706761fcc096c9d414b3dcd000b1e6e5c24364c21cfba429837f98ee8d/ijson-3.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3176f23f8ebec83f374ed0c3b4e5a0c4db7ede54c005864efebbed46da123608", size = 150697, upload-time = "2026-02-24T03:57:15.855Z" }, - { url = "https://files.pythonhosted.org/packages/d9/6e/ee0d9c875a0193b632b3e9ccd1b22a50685fb510256ad57ba483b6529f77/ijson-3.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:6babd88e508630c6ef86c9bebaaf13bb2fb8ec1d8f8868773a03c20253f599bc", size = 142873, upload-time = "2026-02-24T03:57:16.831Z" }, - { url = "https://files.pythonhosted.org/packages/d2/bf/f9d4399d0e6e3fd615035290a71e97c843f17f329b43638c0a01cf112d73/ijson-3.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dc1b3836b174b6db2fa8319f1926fb5445abd195dc963368092103f8579cb8ed", size = 151583, upload-time = "2026-02-24T03:57:17.757Z" }, - { url = "https://files.pythonhosted.org/packages/b2/71/a7254a065933c0e2ffd3586f46187d84830d3d7b6f41cfa5901820a4f87d/ijson-3.5.0-cp312-cp312-win32.whl", hash = "sha256:6673de9395fb9893c1c79a43becd8c8fbee0a250be6ea324bfd1487bb5e9ee4c", size = 53079, upload-time = "2026-02-24T03:57:18.703Z" }, - { url = "https://files.pythonhosted.org/packages/8f/7b/2edca79b359fc9f95d774616867a03ecccdf333797baf5b3eea79733918c/ijson-3.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:f4f7fabd653459dcb004175235f310435959b1bb5dfa8878578391c6cc9ad944", size = 55500, upload-time = "2026-02-24T03:57:20.428Z" }, - { url = "https://files.pythonhosted.org/packages/a2/71/d67e764a712c3590627480643a3b51efcc3afa4ef3cb54ee4c989073c97e/ijson-3.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e9cedc10e40dd6023c351ed8bfc7dcfce58204f15c321c3c1546b9c7b12562a4", size = 88544, upload-time = "2026-02-24T03:57:21.293Z" }, - { url = "https://files.pythonhosted.org/packages/1a/39/f1c299371686153fa3cf5c0736b96247a87a1bee1b7145e6d21f359c505a/ijson-3.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3647649f782ee06c97490b43680371186651f3f69bebe64c6083ee7615d185e5", size = 60495, upload-time = "2026-02-24T03:57:22.501Z" }, - { url = "https://files.pythonhosted.org/packages/16/94/b1438e204d75e01541bebe3e668fe3e68612d210e9931ae1611062dd0a56/ijson-3.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:90e74be1dce05fce73451c62d1118671f78f47c9f6be3991c82b91063bf01fc9", size = 60325, upload-time = "2026-02-24T03:57:23.332Z" }, - { url = "https://files.pythonhosted.org/packages/30/e2/4aa9c116fa86cc8b0f574f3c3a47409edc1cd4face05d0e589a5a176b05d/ijson-3.5.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:78e9ad73e7be2dd80627504bd5cbf512348c55ce2c06e362ed7683b5220e8568", size = 138774, upload-time = "2026-02-24T03:57:24.683Z" }, - { url = "https://files.pythonhosted.org/packages/d2/d2/738b88752a70c3be1505faa4dcd7110668c2712e582a6a36488ed1e295d4/ijson-3.5.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9577449313cc94be89a4fe4b3e716c65f09cc19636d5a6b2861c4e80dddebd58", size = 149820, upload-time = "2026-02-24T03:57:26.062Z" }, - { url = "https://files.pythonhosted.org/packages/ed/df/0b3ab9f393ca8f72ea03bc896ba9fdc987e90ae08cdb51c32a4ee0c14d5e/ijson-3.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3e4c1178fb50aff5f5701a30a5152ead82a14e189ce0f6102fa1b5f10b2f54ff", size = 149747, upload-time = "2026-02-24T03:57:27.308Z" }, - { url = "https://files.pythonhosted.org/packages/cc/a3/b0037119f75131b78cb00acc2657b1a9d0435475f1f2c5f8f5a170b66b9c/ijson-3.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0eb402ab026ffb37a918d75af2b7260fe6cfbce13232cc83728a714dd30bd81d", size = 151027, upload-time = "2026-02-24T03:57:28.522Z" }, - { url = "https://files.pythonhosted.org/packages/22/a0/cb344de1862bf09d8f769c9d25c944078c87dd59a1b496feec5ad96309a4/ijson-3.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5b08ee08355f9f729612a8eb9bf69cc14f9310c3b2a487c6f1c3c65d85216ec4", size = 142996, upload-time = "2026-02-24T03:57:29.774Z" }, - { url = "https://files.pythonhosted.org/packages/ca/32/a8ffd67182e02ea61f70f62daf43ded4fa8a830a2520a851d2782460aba8/ijson-3.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bda62b6d48442903e7bf56152108afb7f0f1293c2b9bef2f2c369defea76ab18", size = 152068, upload-time = "2026-02-24T03:57:30.969Z" }, - { url = "https://files.pythonhosted.org/packages/3c/d1/3578df8e75d446aab0ae92e27f641341f586b85e1988536adebc65300cb4/ijson-3.5.0-cp313-cp313-win32.whl", hash = "sha256:8d073d9b13574cfa11083cc7267c238b7a6ed563c2661e79192da4a25f09c82c", size = 53065, upload-time = "2026-02-24T03:57:31.93Z" }, - { url = "https://files.pythonhosted.org/packages/fb/a2/f7cdaf5896710da3e69e982e44f015a83d168aa0f3a89b6f074b5426779d/ijson-3.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:2419f9e32e0968a876b04d8f26aeac042abd16f582810b576936bbc4c6015069", size = 55499, upload-time = "2026-02-24T03:57:32.773Z" }, - { url = "https://files.pythonhosted.org/packages/42/65/13e2492d17e19a2084523e18716dc2809159f2287fd2700c735f311e76c4/ijson-3.5.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:4d4b0cd676b8c842f7648c1a783448fac5cd3b98289abd83711b3e275e143524", size = 93019, upload-time = "2026-02-24T03:57:33.976Z" }, - { url = "https://files.pythonhosted.org/packages/33/92/483fc97ece0c3f1cecabf48f6a7a36e89d19369eec462faaeaa34c788992/ijson-3.5.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:252dec3680a48bb82d475e36b4ae1b3a9d7eb690b951bb98a76c5fe519e30188", size = 62714, upload-time = "2026-02-24T03:57:34.819Z" }, - { url = "https://files.pythonhosted.org/packages/4b/88/793fe020a0fe9d9eed4c285cf4a5cfdb0a935708b3bde0d72f35c794b513/ijson-3.5.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:aa1b5dca97d323931fde2501172337384c958914d81a9dac7f00f0d4bfc76bc7", size = 62460, upload-time = "2026-02-24T03:57:35.874Z" }, - { url = "https://files.pythonhosted.org/packages/51/69/f1a2690aa8d4df1f4e262b385e65a933ffdc250b091531bac9a449c19e16/ijson-3.5.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7a5ec7fd86d606094bba6f6f8f87494897102fa4584ef653f3005c51a784c320", size = 199273, upload-time = "2026-02-24T03:57:37.07Z" }, - { url = "https://files.pythonhosted.org/packages/ea/a2/f1346d5299e79b988ab472dc773d5381ec2d57c23cb2f1af3ede4a810e62/ijson-3.5.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:009f41443e1521847701c6d87fa3923c0b1961be3c7e7de90947c8cb92ea7c44", size = 216884, upload-time = "2026-02-24T03:57:38.346Z" }, - { url = "https://files.pythonhosted.org/packages/28/3c/8b637e869be87799e6c2c3c275a30a546f086b1aed77e2b7f11512168c5a/ijson-3.5.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e4c3651d1f9fe2839a93fdf8fd1d5ca3a54975349894249f3b1b572bcc4bd577", size = 207306, upload-time = "2026-02-24T03:57:39.718Z" }, - { url = "https://files.pythonhosted.org/packages/7f/7c/18b1c1df6951ca056782d7580ec40cea4ff9a27a0947d92640d1cc8c4ae3/ijson-3.5.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:945b7abcfcfeae2cde17d8d900870f03536494245dda7ad4f8d056faa303256c", size = 211364, upload-time = "2026-02-24T03:57:40.953Z" }, - { url = "https://files.pythonhosted.org/packages/f3/55/e795812e82851574a9dba8a53fde045378f531ef14110c6fb55dbd23b443/ijson-3.5.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:0574b0a841ff97495c13e9d7260fbf3d85358b061f540c52a123db9dbbaa2ed6", size = 200608, upload-time = "2026-02-24T03:57:42.272Z" }, - { url = "https://files.pythonhosted.org/packages/5c/cd/013c85b4749b57a4cb4c2670014d1b32b8db4ab1a7be92ea7aeb5d7fe7b5/ijson-3.5.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f969ffb2b89c5cdf686652d7fb66252bc72126fa54d416317411497276056a18", size = 205127, upload-time = "2026-02-24T03:57:43.286Z" }, - { url = "https://files.pythonhosted.org/packages/0e/7c/faf643733e3ab677f180018f6a855c4ef70b7c46540987424c563c959e42/ijson-3.5.0-cp313-cp313t-win32.whl", hash = "sha256:59d3f9f46deed1332ad669518b8099920512a78bda64c1f021fcd2aff2b36693", size = 55282, upload-time = "2026-02-24T03:57:44.353Z" }, - { url = "https://files.pythonhosted.org/packages/69/22/94ddb47c24b491377aca06cd8fc9202cad6ab50619842457d2beefde21ea/ijson-3.5.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5c2839fa233746d8aad3b8cd2354e441613f5df66d721d59da4a09394bd1db2b", size = 58016, upload-time = "2026-02-24T03:57:45.237Z" }, - { url = "https://files.pythonhosted.org/packages/7a/93/0868efe753dc1df80cc405cf0c1f2527a6991643607c741bff8dcb899b3b/ijson-3.5.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:25a5a6b2045c90bb83061df27cfa43572afa43ba9408611d7bfe237c20a731a9", size = 89094, upload-time = "2026-02-24T03:57:46.115Z" }, - { url = "https://files.pythonhosted.org/packages/24/94/fd5a832a0df52ef5e4e740f14ac8640725d61034a1b0c561e8b5fb424706/ijson-3.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:8976c54c0b864bc82b951bae06567566ac77ef63b90a773a69cd73aab47f4f4f", size = 60715, upload-time = "2026-02-24T03:57:47.552Z" }, - { url = "https://files.pythonhosted.org/packages/70/79/1b9a90af5732491f9eec751ee211b86b11011e1158c555c06576d52c3919/ijson-3.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:859eb2038f7f1b0664df4241957694cc35e6295992d71c98659b22c69b3cbc10", size = 60638, upload-time = "2026-02-24T03:57:48.428Z" }, - { url = "https://files.pythonhosted.org/packages/23/6f/2c551ea980fe56f68710a8d5389cfbd015fc45aaafd17c3c52c346db6aa1/ijson-3.5.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c911aa02991c7c0d3639b6619b93a93210ff1e7f58bf7225d613abea10adc78e", size = 140667, upload-time = "2026-02-24T03:57:49.314Z" }, - { url = "https://files.pythonhosted.org/packages/25/0e/27b887879ba6a5bc29766e3c5af4942638c952220fd63e1e442674f7883a/ijson-3.5.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:903cbdc350173605220edc19796fbea9b2203c8b3951fb7335abfa8ed37afda8", size = 149850, upload-time = "2026-02-24T03:57:50.329Z" }, - { url = "https://files.pythonhosted.org/packages/da/1e/23e10e1bc04bf31193b21e2960dce14b17dbd5d0c62204e8401c59d62c08/ijson-3.5.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a4549d96ded5b8efa71639b2160235415f6bdb8c83367615e2dbabcb72755c33", size = 149206, upload-time = "2026-02-24T03:57:51.261Z" }, - { url = "https://files.pythonhosted.org/packages/8e/90/e552f6495063b235cf7fa2c592f6597c057077195e517b842a0374fd470c/ijson-3.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:6b2dcf6349e6042d83f3f8c39ce84823cf7577eba25bac5aae5e39bbbbbe9c1c", size = 150438, upload-time = "2026-02-24T03:57:52.198Z" }, - { url = "https://files.pythonhosted.org/packages/5c/18/45bf8f297c41b42a1c231d261141097babd953d2c28a07be57ae4c3a1a02/ijson-3.5.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:e44af39e6f8a17e5627dcd89715d8279bf3474153ff99aae031a936e5c5572e5", size = 144369, upload-time = "2026-02-24T03:57:53.22Z" }, - { url = "https://files.pythonhosted.org/packages/9b/3a/deb9772bb2c0cead7ad64f00c3598eec9072bdf511818e70e2c512eeabbe/ijson-3.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9260332304b7e7828db56d43f08fc970a3ab741bf84ff10189361ea1b60c395b", size = 151352, upload-time = "2026-02-24T03:57:54.375Z" }, - { url = "https://files.pythonhosted.org/packages/e4/51/67f4d80cd58ad7eab0cd1af5fe28b961886338956b2f88c0979e21914346/ijson-3.5.0-cp314-cp314-win32.whl", hash = "sha256:63bc8121bb422f6969ced270173a3fa692c29d4ae30c860a2309941abd81012a", size = 53610, upload-time = "2026-02-24T03:57:55.655Z" }, - { url = "https://files.pythonhosted.org/packages/70/d3/263672ea22983ba3940f1534316dbc9200952c1c2a2332d7a664e4eaa7ae/ijson-3.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:01b6dad72b7b7df225ef970d334556dfad46c696a2c6767fb5d9ed8889728bca", size = 56301, upload-time = "2026-02-24T03:57:56.584Z" }, - { url = "https://files.pythonhosted.org/packages/9f/d9/86f7fac35e0835faa188085ae0579e813493d5261ce056484015ad533445/ijson-3.5.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:2ea4b676ec98e374c1df400a47929859e4fa1239274339024df4716e802aa7e4", size = 93069, upload-time = "2026-02-24T03:57:57.849Z" }, - { url = "https://files.pythonhosted.org/packages/33/d2/e7366ed9c6e60228d35baf4404bac01a126e7775ea8ce57f560125ed190a/ijson-3.5.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:014586eec043e23c80be9a923c56c3a0920a0f1f7d17478ce7bc20ba443968ef", size = 62767, upload-time = "2026-02-24T03:57:58.758Z" }, - { url = "https://files.pythonhosted.org/packages/35/8b/3e703e8cc4b3ada79f13b28070b51d9550c578f76d1968657905857b2ddd/ijson-3.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d5b8b886b0248652d437f66e7c5ac318bbdcb2c7137a7e5327a68ca00b286f5f", size = 62467, upload-time = "2026-02-24T03:58:00.261Z" }, - { url = "https://files.pythonhosted.org/packages/21/42/0c91af32c1ee8a957fdac2e051b5780756d05fd34e4b60d94a08d51bac1d/ijson-3.5.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:498fd46ae2349297e43acf97cdc421e711dbd7198418677259393d2acdc62d78", size = 200447, upload-time = "2026-02-24T03:58:01.591Z" }, - { url = "https://files.pythonhosted.org/packages/f9/80/796ea0e391b7e2d45c5b1b451734bba03f81c2984cf955ea5eaa6c4920ad/ijson-3.5.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22a51b4f9b81f12793731cf226266d1de2112c3c04ba4a04117ad4e466897e05", size = 217820, upload-time = "2026-02-24T03:58:02.598Z" }, - { url = "https://files.pythonhosted.org/packages/38/14/52b6613fdda4078c62eb5b4fe3efc724ddc55a4ad524c93de51830107aa3/ijson-3.5.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9636c710dc4ac4a281baa266a64f323b4cc165cec26836af702c44328b59a515", size = 208310, upload-time = "2026-02-24T03:58:04.759Z" }, - { url = "https://files.pythonhosted.org/packages/6a/ad/8b3105a78774fd4a65e534a21d975ef3a77e189489fe3029ebcaeba5e243/ijson-3.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f7168a39e8211107666d71b25693fd1b2bac0b33735ef744114c403c6cac21e1", size = 211843, upload-time = "2026-02-24T03:58:05.836Z" }, - { url = "https://files.pythonhosted.org/packages/36/ab/a2739f6072d6e1160581bc3ed32da614c8cced023dcd519d9c5fa66e0425/ijson-3.5.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8696454245415bc617ab03b0dc3ae4c86987df5dc6a90bad378fe72c5409d89e", size = 200906, upload-time = "2026-02-24T03:58:07.788Z" }, - { url = "https://files.pythonhosted.org/packages/6d/5e/e06c2de3c3d4a9cfb655c1ad08a68fb72838d271072cdd3196576ac4431a/ijson-3.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c21bfb61f71f191565885bf1bc29e0a186292d866b4880637b833848360bdc1b", size = 205495, upload-time = "2026-02-24T03:58:09.163Z" }, - { url = "https://files.pythonhosted.org/packages/7c/11/778201eb2e202ddd76b36b0fb29bf3d8e3c167389d8aa883c62524e49f47/ijson-3.5.0-cp314-cp314t-win32.whl", hash = "sha256:a2619460d6795b70d0155e5bf016200ac8a63ab5397aa33588bb02b6c21759e6", size = 56280, upload-time = "2026-02-24T03:58:10.116Z" }, - { url = "https://files.pythonhosted.org/packages/23/28/96711503245339084c8086b892c47415895eba49782d6cc52d9f4ee50301/ijson-3.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:4f24b78d4ef028d17eb57ad1b16c0aed4a17bdd9badbf232dc5d9305b7e13854", size = 58965, upload-time = "2026-02-24T03:58:11.278Z" }, - { url = "https://files.pythonhosted.org/packages/d9/3b/d31ecfa63a218978617446159f3d77aab2417a5bd2885c425b176353ff78/ijson-3.5.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:d64c624da0e9d692d6eb0ff63a79656b59d76bf80773a17c5b0f835e4e8ef627", size = 57715, upload-time = "2026-02-24T03:58:24.545Z" }, - { url = "https://files.pythonhosted.org/packages/30/51/b170e646d378e8cccf9637c05edb5419b00c2c4df64b0258c3af5355608e/ijson-3.5.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:876f7df73b7e0d6474f9caa729b9cdbfc8e76de9075a4887dfd689e29e85c4ca", size = 57205, upload-time = "2026-02-24T03:58:25.681Z" }, - { url = "https://files.pythonhosted.org/packages/ef/83/44dbd0231b0a8c6c14d27473d10c4e27dfbce7d5d9a833c79e3e6c33eb40/ijson-3.5.0-pp311-pypy311_pp73-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e7dbff2c8d9027809b0cde663df44f3210da10ea377121d42896fb6ee405dd31", size = 71229, upload-time = "2026-02-24T03:58:27.103Z" }, - { url = "https://files.pythonhosted.org/packages/c8/98/cf84048b7c6cec888826e696a31f45bee7ebcac15e532b6be1fc4c2c9608/ijson-3.5.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4217a1edc278660679e1197c83a1a2a2d367792bfbb2a3279577f4b59b93730d", size = 71217, upload-time = "2026-02-24T03:58:28.021Z" }, - { url = "https://files.pythonhosted.org/packages/3c/0a/e34c729a87ff67dc6540f6bcc896626158e691d433ab57db0086d73decd2/ijson-3.5.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:04f0fc740311388ee745ba55a12292b722d6f52000b11acbb913982ba5fbdf87", size = 68618, upload-time = "2026-02-24T03:58:28.918Z" }, - { url = "https://files.pythonhosted.org/packages/c1/0f/e849d072f2e0afe49627de3995fc9dae54b4c804c70c0840f928d95c10e1/ijson-3.5.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:fdeee6957f92e0c114f65c55cf8fe7eabb80cfacab64eea6864060913173f66d", size = 55369, upload-time = "2026-02-24T03:58:29.839Z" }, -] - [[package]] name = "iniconfig" version = "2.3.0" @@ -1524,46 +1121,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] -[[package]] -name = "inscriptis" -version = "2.6.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "lxml" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/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://files.pythonhosted.org/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.org/simple" } -dependencies = [ - { name = "beautifulsoup4" }, - { name = "ijson" }, - { name = "inscriptis" }, - { name = "lxml" }, - { name = "lz4" }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { 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://files.pythonhosted.org/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://files.pythonhosted.org/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 = "isort" version = "8.0.1" @@ -1573,36 +1130,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3e/95/c7c34aa53c16353c56d0b802fba48d5f5caa2cdee7958acbcb795c830416/isort-8.0.1-py3-none-any.whl", hash = "sha256:28b89bc70f751b559aeca209e6120393d43fbe2490de0559662be7a9787e3d75", size = 89733, upload-time = "2026-02-28T10:08:19.466Z" }, ] -[[package]] -name = "jinja2" -version = "3.1.6" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markupsafe" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, -] - -[[package]] -name = "jmespath" -version = "1.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d3/59/322338183ecda247fb5d1763a6cbe46eff7222eaeebafd9fa65d4bf5cb11/jmespath-1.1.0.tar.gz", hash = "sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d", size = 27377, upload-time = "2026-01-22T16:35:26.279Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64", size = 20419, upload-time = "2026-01-22T16:35:24.919Z" }, -] - -[[package]] -name = "joblib" -version = "1.5.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/41/f2/d34e8b3a08a9cc79a50b2208a93dce981fe615b64d5a4d4abee421d898df/joblib-1.5.3.tar.gz", hash = "sha256:8561a3269e6801106863fd0d6d84bb737be9e7631e33aaed3fb9ce5953688da3", size = 331603, upload-time = "2025-12-15T08:41:46.427Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl", hash = "sha256:5fc3c5039fc5ca8c0276333a188bbd59d6b7ab37fe6632daa76bc7f9ec18e713", size = 309071, upload-time = "2025-12-15T08:41:44.973Z" }, -] - [[package]] name = "jsonpatch" version = "1.33" @@ -1905,190 +1432,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/40/44/3ee09a5b60cb44c4f2fbc1c9015cfd6ff5afc08f991cab295d3024dcbf2d/lxml-6.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:7da13bb6fbadfafb474e0226a30570a3445cfd47c86296f2446dafbd77079ace", size = 3508860, upload-time = "2026-04-18T04:32:48.619Z" }, ] -[[package]] -name = "lz4" -version = "4.4.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/57/51/f1b86d93029f418033dddf9b9f79c8d2641e7454080478ee2aab5123173e/lz4-4.4.5.tar.gz", hash = "sha256:5f0b9e53c1e82e88c10d7c180069363980136b9d7a8306c4dca4f760d60c39f0", size = 172886, upload-time = "2025-11-03T13:02:36.061Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/45/2466d73d79e3940cad4b26761f356f19fd33f4409c96f100e01a5c566909/lz4-4.4.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d221fa421b389ab2345640a508db57da36947a437dfe31aeddb8d5c7b646c22d", size = 207396, upload-time = "2025-11-03T13:01:24.965Z" }, - { url = "https://files.pythonhosted.org/packages/72/12/7da96077a7e8918a5a57a25f1254edaf76aefb457666fcc1066deeecd609/lz4-4.4.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7dc1e1e2dbd872f8fae529acd5e4839efd0b141eaa8ae7ce835a9fe80fbad89f", size = 207154, upload-time = "2025-11-03T13:01:26.922Z" }, - { url = "https://files.pythonhosted.org/packages/b8/0e/0fb54f84fd1890d4af5bc0a3c1fa69678451c1a6bd40de26ec0561bb4ec5/lz4-4.4.5-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e928ec2d84dc8d13285b4a9288fd6246c5cde4f5f935b479f50d986911f085e3", size = 1291053, upload-time = "2025-11-03T13:01:28.396Z" }, - { url = "https://files.pythonhosted.org/packages/15/45/8ce01cc2715a19c9e72b0e423262072c17d581a8da56e0bd4550f3d76a79/lz4-4.4.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:daffa4807ef54b927451208f5f85750c545a4abbff03d740835fc444cd97f758", size = 1278586, upload-time = "2025-11-03T13:01:29.906Z" }, - { url = "https://files.pythonhosted.org/packages/6d/34/7be9b09015e18510a09b8d76c304d505a7cbc66b775ec0b8f61442316818/lz4-4.4.5-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2a2b7504d2dffed3fd19d4085fe1cc30cf221263fd01030819bdd8d2bb101cf1", size = 1367315, upload-time = "2025-11-03T13:01:31.054Z" }, - { url = "https://files.pythonhosted.org/packages/2a/94/52cc3ec0d41e8d68c985ec3b2d33631f281d8b748fb44955bc0384c2627b/lz4-4.4.5-cp310-cp310-win32.whl", hash = "sha256:0846e6e78f374156ccf21c631de80967e03cc3c01c373c665789dc0c5431e7fc", size = 88173, upload-time = "2025-11-03T13:01:32.643Z" }, - { url = "https://files.pythonhosted.org/packages/ca/35/c3c0bdc409f551404355aeeabc8da343577d0e53592368062e371a3620e1/lz4-4.4.5-cp310-cp310-win_amd64.whl", hash = "sha256:7c4e7c44b6a31de77d4dc9772b7d2561937c9588a734681f70ec547cfbc51ecd", size = 99492, upload-time = "2025-11-03T13:01:33.813Z" }, - { url = "https://files.pythonhosted.org/packages/1d/02/4d88de2f1e97f9d05fd3d278fe412b08969bc94ff34942f5a3f09318144a/lz4-4.4.5-cp310-cp310-win_arm64.whl", hash = "sha256:15551280f5656d2206b9b43262799c89b25a25460416ec554075a8dc568e4397", size = 91280, upload-time = "2025-11-03T13:01:35.081Z" }, - { url = "https://files.pythonhosted.org/packages/93/5b/6edcd23319d9e28b1bedf32768c3d1fd56eed8223960a2c47dacd2cec2af/lz4-4.4.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d6da84a26b3aa5da13a62e4b89ab36a396e9327de8cd48b436a3467077f8ccd4", size = 207391, upload-time = "2025-11-03T13:01:36.644Z" }, - { url = "https://files.pythonhosted.org/packages/34/36/5f9b772e85b3d5769367a79973b8030afad0d6b724444083bad09becd66f/lz4-4.4.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:61d0ee03e6c616f4a8b69987d03d514e8896c8b1b7cc7598ad029e5c6aedfd43", size = 207146, upload-time = "2025-11-03T13:01:37.928Z" }, - { url = "https://files.pythonhosted.org/packages/04/f4/f66da5647c0d72592081a37c8775feacc3d14d2625bbdaabd6307c274565/lz4-4.4.5-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:33dd86cea8375d8e5dd001e41f321d0a4b1eb7985f39be1b6a4f466cd480b8a7", size = 1292623, upload-time = "2025-11-03T13:01:39.341Z" }, - { url = "https://files.pythonhosted.org/packages/85/fc/5df0f17467cdda0cad464a9197a447027879197761b55faad7ca29c29a04/lz4-4.4.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:609a69c68e7cfcfa9d894dc06be13f2e00761485b62df4e2472f1b66f7b405fb", size = 1279982, upload-time = "2025-11-03T13:01:40.816Z" }, - { url = "https://files.pythonhosted.org/packages/25/3b/b55cb577aa148ed4e383e9700c36f70b651cd434e1c07568f0a86c9d5fbb/lz4-4.4.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:75419bb1a559af00250b8f1360d508444e80ed4b26d9d40ec5b09fe7875cb989", size = 1368674, upload-time = "2025-11-03T13:01:42.118Z" }, - { url = "https://files.pythonhosted.org/packages/fb/31/e97e8c74c59ea479598e5c55cbe0b1334f03ee74ca97726e872944ed42df/lz4-4.4.5-cp311-cp311-win32.whl", hash = "sha256:12233624f1bc2cebc414f9efb3113a03e89acce3ab6f72035577bc61b270d24d", size = 88168, upload-time = "2025-11-03T13:01:43.282Z" }, - { url = "https://files.pythonhosted.org/packages/18/47/715865a6c7071f417bef9b57c8644f29cb7a55b77742bd5d93a609274e7e/lz4-4.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:8a842ead8ca7c0ee2f396ca5d878c4c40439a527ebad2b996b0444f0074ed004", size = 99491, upload-time = "2025-11-03T13:01:44.167Z" }, - { url = "https://files.pythonhosted.org/packages/14/e7/ac120c2ca8caec5c945e6356ada2aa5cfabd83a01e3170f264a5c42c8231/lz4-4.4.5-cp311-cp311-win_arm64.whl", hash = "sha256:83bc23ef65b6ae44f3287c38cbf82c269e2e96a26e560aa551735883388dcc4b", size = 91271, upload-time = "2025-11-03T13:01:45.016Z" }, - { url = "https://files.pythonhosted.org/packages/1b/ac/016e4f6de37d806f7cc8f13add0a46c9a7cfc41a5ddc2bc831d7954cf1ce/lz4-4.4.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:df5aa4cead2044bab83e0ebae56e0944cc7fcc1505c7787e9e1057d6d549897e", size = 207163, upload-time = "2025-11-03T13:01:45.895Z" }, - { url = "https://files.pythonhosted.org/packages/8d/df/0fadac6e5bd31b6f34a1a8dbd4db6a7606e70715387c27368586455b7fc9/lz4-4.4.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6d0bf51e7745484d2092b3a51ae6eb58c3bd3ce0300cf2b2c14f76c536d5697a", size = 207150, upload-time = "2025-11-03T13:01:47.205Z" }, - { url = "https://files.pythonhosted.org/packages/b7/17/34e36cc49bb16ca73fb57fbd4c5eaa61760c6b64bce91fcb4e0f4a97f852/lz4-4.4.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7b62f94b523c251cf32aa4ab555f14d39bd1a9df385b72443fd76d7c7fb051f5", size = 1292045, upload-time = "2025-11-03T13:01:48.667Z" }, - { url = "https://files.pythonhosted.org/packages/90/1c/b1d8e3741e9fc89ed3b5f7ef5f22586c07ed6bb04e8343c2e98f0fa7ff04/lz4-4.4.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c3ea562c3af274264444819ae9b14dbbf1ab070aff214a05e97db6896c7597e", size = 1279546, upload-time = "2025-11-03T13:01:50.159Z" }, - { url = "https://files.pythonhosted.org/packages/55/d9/e3867222474f6c1b76e89f3bd914595af69f55bf2c1866e984c548afdc15/lz4-4.4.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24092635f47538b392c4eaeff14c7270d2c8e806bf4be2a6446a378591c5e69e", size = 1368249, upload-time = "2025-11-03T13:01:51.273Z" }, - { url = "https://files.pythonhosted.org/packages/b2/e7/d667d337367686311c38b580d1ca3d5a23a6617e129f26becd4f5dc458df/lz4-4.4.5-cp312-cp312-win32.whl", hash = "sha256:214e37cfe270948ea7eb777229e211c601a3e0875541c1035ab408fbceaddf50", size = 88189, upload-time = "2025-11-03T13:01:52.605Z" }, - { url = "https://files.pythonhosted.org/packages/a5/0b/a54cd7406995ab097fceb907c7eb13a6ddd49e0b231e448f1a81a50af65c/lz4-4.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:713a777de88a73425cf08eb11f742cd2c98628e79a8673d6a52e3c5f0c116f33", size = 99497, upload-time = "2025-11-03T13:01:53.477Z" }, - { url = "https://files.pythonhosted.org/packages/6a/7e/dc28a952e4bfa32ca16fa2eb026e7a6ce5d1411fcd5986cd08c74ec187b9/lz4-4.4.5-cp312-cp312-win_arm64.whl", hash = "sha256:a88cbb729cc333334ccfb52f070463c21560fca63afcf636a9f160a55fac3301", size = 91279, upload-time = "2025-11-03T13:01:54.419Z" }, - { url = "https://files.pythonhosted.org/packages/2f/46/08fd8ef19b782f301d56a9ccfd7dafec5fd4fc1a9f017cf22a1accb585d7/lz4-4.4.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6bb05416444fafea170b07181bc70640975ecc2a8c92b3b658c554119519716c", size = 207171, upload-time = "2025-11-03T13:01:56.595Z" }, - { url = "https://files.pythonhosted.org/packages/8f/3f/ea3334e59de30871d773963997ecdba96c4584c5f8007fd83cfc8f1ee935/lz4-4.4.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b424df1076e40d4e884cfcc4c77d815368b7fb9ebcd7e634f937725cd9a8a72a", size = 207163, upload-time = "2025-11-03T13:01:57.721Z" }, - { url = "https://files.pythonhosted.org/packages/41/7b/7b3a2a0feb998969f4793c650bb16eff5b06e80d1f7bff867feb332f2af2/lz4-4.4.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:216ca0c6c90719731c64f41cfbd6f27a736d7e50a10b70fad2a9c9b262ec923d", size = 1292136, upload-time = "2025-11-03T13:02:00.375Z" }, - { url = "https://files.pythonhosted.org/packages/89/d1/f1d259352227bb1c185288dd694121ea303e43404aa77560b879c90e7073/lz4-4.4.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:533298d208b58b651662dd972f52d807d48915176e5b032fb4f8c3b6f5fe535c", size = 1279639, upload-time = "2025-11-03T13:02:01.649Z" }, - { url = "https://files.pythonhosted.org/packages/d2/fb/ba9256c48266a09012ed1d9b0253b9aa4fe9cdff094f8febf5b26a4aa2a2/lz4-4.4.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:451039b609b9a88a934800b5fc6ee401c89ad9c175abf2f4d9f8b2e4ef1afc64", size = 1368257, upload-time = "2025-11-03T13:02:03.35Z" }, - { url = "https://files.pythonhosted.org/packages/a5/6d/dee32a9430c8b0e01bbb4537573cabd00555827f1a0a42d4e24ca803935c/lz4-4.4.5-cp313-cp313-win32.whl", hash = "sha256:a5f197ffa6fc0e93207b0af71b302e0a2f6f29982e5de0fbda61606dd3a55832", size = 88191, upload-time = "2025-11-03T13:02:04.406Z" }, - { url = "https://files.pythonhosted.org/packages/18/e0/f06028aea741bbecb2a7e9648f4643235279a770c7ffaf70bd4860c73661/lz4-4.4.5-cp313-cp313-win_amd64.whl", hash = "sha256:da68497f78953017deb20edff0dba95641cc86e7423dfadf7c0264e1ac60dc22", size = 99502, upload-time = "2025-11-03T13:02:05.886Z" }, - { url = "https://files.pythonhosted.org/packages/61/72/5bef44afb303e56078676b9f2486f13173a3c1e7f17eaac1793538174817/lz4-4.4.5-cp313-cp313-win_arm64.whl", hash = "sha256:c1cfa663468a189dab510ab231aad030970593f997746d7a324d40104db0d0a9", size = 91285, upload-time = "2025-11-03T13:02:06.77Z" }, - { url = "https://files.pythonhosted.org/packages/49/55/6a5c2952971af73f15ed4ebfdd69774b454bd0dc905b289082ca8664fba1/lz4-4.4.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:67531da3b62f49c939e09d56492baf397175ff39926d0bd5bd2d191ac2bff95f", size = 207348, upload-time = "2025-11-03T13:02:08.117Z" }, - { url = "https://files.pythonhosted.org/packages/4e/d7/fd62cbdbdccc35341e83aabdb3f6d5c19be2687d0a4eaf6457ddf53bba64/lz4-4.4.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a1acbbba9edbcbb982bc2cac5e7108f0f553aebac1040fbec67a011a45afa1ba", size = 207340, upload-time = "2025-11-03T13:02:09.152Z" }, - { url = "https://files.pythonhosted.org/packages/77/69/225ffadaacb4b0e0eb5fd263541edd938f16cd21fe1eae3cd6d5b6a259dc/lz4-4.4.5-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a482eecc0b7829c89b498fda883dbd50e98153a116de612ee7c111c8bcf82d1d", size = 1293398, upload-time = "2025-11-03T13:02:10.272Z" }, - { url = "https://files.pythonhosted.org/packages/c6/9e/2ce59ba4a21ea5dc43460cba6f34584e187328019abc0e66698f2b66c881/lz4-4.4.5-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e099ddfaa88f59dd8d36c8a3c66bd982b4984edf127eb18e30bb49bdba68ce67", size = 1281209, upload-time = "2025-11-03T13:02:12.091Z" }, - { url = "https://files.pythonhosted.org/packages/80/4f/4d946bd1624ec229b386a3bc8e7a85fa9a963d67d0a62043f0af0978d3da/lz4-4.4.5-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2af2897333b421360fdcce895c6f6281dc3fab018d19d341cf64d043fc8d90d", size = 1369406, upload-time = "2025-11-03T13:02:13.683Z" }, - { url = "https://files.pythonhosted.org/packages/02/a2/d429ba4720a9064722698b4b754fb93e42e625f1318b8fe834086c7c783b/lz4-4.4.5-cp313-cp313t-win32.whl", hash = "sha256:66c5de72bf4988e1b284ebdd6524c4bead2c507a2d7f172201572bac6f593901", size = 88325, upload-time = "2025-11-03T13:02:14.743Z" }, - { url = "https://files.pythonhosted.org/packages/4b/85/7ba10c9b97c06af6c8f7032ec942ff127558863df52d866019ce9d2425cf/lz4-4.4.5-cp313-cp313t-win_amd64.whl", hash = "sha256:cdd4bdcbaf35056086d910d219106f6a04e1ab0daa40ec0eeef1626c27d0fddb", size = 99643, upload-time = "2025-11-03T13:02:15.978Z" }, - { url = "https://files.pythonhosted.org/packages/77/4d/a175459fb29f909e13e57c8f475181ad8085d8d7869bd8ad99033e3ee5fa/lz4-4.4.5-cp313-cp313t-win_arm64.whl", hash = "sha256:28ccaeb7c5222454cd5f60fcd152564205bcb801bd80e125949d2dfbadc76bbd", size = 91504, upload-time = "2025-11-03T13:02:17.313Z" }, - { url = "https://files.pythonhosted.org/packages/63/9c/70bdbdb9f54053a308b200b4678afd13efd0eafb6ddcbb7f00077213c2e5/lz4-4.4.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c216b6d5275fc060c6280936bb3bb0e0be6126afb08abccde27eed23dead135f", size = 207586, upload-time = "2025-11-03T13:02:18.263Z" }, - { url = "https://files.pythonhosted.org/packages/b6/cb/bfead8f437741ce51e14b3c7d404e3a1f6b409c440bad9b8f3945d4c40a7/lz4-4.4.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c8e71b14938082ebaf78144f3b3917ac715f72d14c076f384a4c062df96f9df6", size = 207161, upload-time = "2025-11-03T13:02:19.286Z" }, - { url = "https://files.pythonhosted.org/packages/e7/18/b192b2ce465dfbeabc4fc957ece7a1d34aded0d95a588862f1c8a86ac448/lz4-4.4.5-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9b5e6abca8df9f9bdc5c3085f33ff32cdc86ed04c65e0355506d46a5ac19b6e9", size = 1292415, upload-time = "2025-11-03T13:02:20.829Z" }, - { url = "https://files.pythonhosted.org/packages/67/79/a4e91872ab60f5e89bfad3e996ea7dc74a30f27253faf95865771225ccba/lz4-4.4.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3b84a42da86e8ad8537aabef062e7f661f4a877d1c74d65606c49d835d36d668", size = 1279920, upload-time = "2025-11-03T13:02:22.013Z" }, - { url = "https://files.pythonhosted.org/packages/f1/01/d52c7b11eaa286d49dae619c0eec4aabc0bf3cda7a7467eb77c62c4471f3/lz4-4.4.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bba042ec5a61fa77c7e380351a61cb768277801240249841defd2ff0a10742f", size = 1368661, upload-time = "2025-11-03T13:02:23.208Z" }, - { url = "https://files.pythonhosted.org/packages/f7/da/137ddeea14c2cb86864838277b2607d09f8253f152156a07f84e11768a28/lz4-4.4.5-cp314-cp314-win32.whl", hash = "sha256:bd85d118316b53ed73956435bee1997bd06cc66dd2fa74073e3b1322bd520a67", size = 90139, upload-time = "2025-11-03T13:02:24.301Z" }, - { url = "https://files.pythonhosted.org/packages/18/2c/8332080fd293f8337779a440b3a143f85e374311705d243439a3349b81ad/lz4-4.4.5-cp314-cp314-win_amd64.whl", hash = "sha256:92159782a4502858a21e0079d77cdcaade23e8a5d252ddf46b0652604300d7be", size = 101497, upload-time = "2025-11-03T13:02:25.187Z" }, - { url = "https://files.pythonhosted.org/packages/ca/28/2635a8141c9a4f4bc23f5135a92bbcf48d928d8ca094088c962df1879d64/lz4-4.4.5-cp314-cp314-win_arm64.whl", hash = "sha256:d994b87abaa7a88ceb7a37c90f547b8284ff9da694e6afcfaa8568d739faf3f7", size = 93812, upload-time = "2025-11-03T13:02:26.133Z" }, -] - -[[package]] -name = "magic-pdf" -version = "0.6.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "boto3" }, - { name = "brotli" }, - { name = "click" }, - { name = "fast-langdetect" }, - { name = "loguru" }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "pdfminer-six" }, - { name = "pymupdf" }, - { name = "scikit-learn", version = "1.7.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "scikit-learn", version = "1.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "wordninja" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/f6/e5/3a96d48b487c74727009ee3c128c065ab36b2c64bada36225b7bfe07d2e1/magic_pdf-0.6.1-py3-none-any.whl", hash = "sha256:96ae0665315bf44ac4f15964bf45b6306bfefe0845de4bf6dc60fb6f3a9c5b4d", size = 330281, upload-time = "2024-07-13T12:23:21.335Z" }, -] - -[[package]] -name = "markdown-it-py" -version = "4.2.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mdurl" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z" }, -] - -[[package]] -name = "markupsafe" -version = "3.0.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", size = 11631, upload-time = "2025-09-27T18:36:05.558Z" }, - { url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", size = 12057, upload-time = "2025-09-27T18:36:07.165Z" }, - { url = "https://files.pythonhosted.org/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", size = 22050, upload-time = "2025-09-27T18:36:08.005Z" }, - { url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", size = 20681, upload-time = "2025-09-27T18:36:08.881Z" }, - { url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", size = 20705, upload-time = "2025-09-27T18:36:10.131Z" }, - { url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", size = 21524, upload-time = "2025-09-27T18:36:11.324Z" }, - { url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", size = 20282, upload-time = "2025-09-27T18:36:12.573Z" }, - { url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", size = 20745, upload-time = "2025-09-27T18:36:13.504Z" }, - { url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", size = 14571, upload-time = "2025-09-27T18:36:14.779Z" }, - { url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", size = 15056, upload-time = "2025-09-27T18:36:16.125Z" }, - { url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", size = 13932, upload-time = "2025-09-27T18:36:17.311Z" }, - { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, - { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, - { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, - { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, - { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, - { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, - { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, - { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, - { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, - { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, - { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, - { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, - { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, - { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, - { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, - { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, - { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, - { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, - { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, - { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, - { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, - { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, - { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, - { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, - { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, - { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, - { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, - { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, - { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, - { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, - { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, - { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, - { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, - { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, - { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, - { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, - { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, - { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, - { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, - { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, - { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, - { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, - { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, - { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, - { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, - { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, - { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, - { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, - { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, - { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, - { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, - { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, - { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, - { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, - { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, - { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, - { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, - { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, - { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, - { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, - { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, - { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, - { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, - { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, - { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, - { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, -] - -[[package]] -name = "mdurl" -version = "0.1.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, -] - [[package]] name = "minio" version = "7.2.20" @@ -2105,15 +1448,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3e/9a/b697530a882588a84db616580f2ba5d1d515c815e11c30d219145afeec87/minio-7.2.20-py3-none-any.whl", hash = "sha256:eb33dd2fb80e04c3726a76b13241c6be3c4c46f8d81e1d58e757786f6501897e", size = 93751, upload-time = "2025-11-27T00:37:13.993Z" }, ] -[[package]] -name = "mpmath" -version = "1.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/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://files.pythonhosted.org/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 = "multidict" version = "6.7.1" @@ -2252,63 +1586,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" }, ] -[[package]] -name = "multiprocess" -version = "0.70.19" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "dill" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a2/f2/e783ac7f2aeeed14e9e12801f22529cc7e6b7ab80928d6dcce4e9f00922d/multiprocess-0.70.19.tar.gz", hash = "sha256:952021e0e6c55a4a9fe4cd787895b86e239a40e76802a789d6305398d3975897", size = 2079989, upload-time = "2026-01-19T06:47:39.744Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/b6/10832f96b499690854e574360be342a282f5f7dba58eff791299ff6c0637/multiprocess-0.70.19-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:02e5c35d7d6cd2bdc89c1858867f7bde4012837411023a4696c148c1bdd7c80e", size = 135131, upload-time = "2026-01-19T06:47:20.479Z" }, - { url = "https://files.pythonhosted.org/packages/99/50/faef2d8106534b0dc4a0b772668a1a99682696ebf17d3c0f13f2ed6a656a/multiprocess-0.70.19-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:79576c02d1207ec405b00cabf2c643c36070800cca433860e14539df7818b2aa", size = 135131, upload-time = "2026-01-19T06:47:21.879Z" }, - { url = "https://files.pythonhosted.org/packages/94/b1/0b71d18b76bf423c2e8ee00b31db37d17297ab3b4db44e188692afdca628/multiprocess-0.70.19-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c6b6d78d43a03b68014ca1f0b7937d965393a670c5de7c29026beb2258f2f896", size = 135134, upload-time = "2026-01-19T06:47:23.262Z" }, - { url = "https://files.pythonhosted.org/packages/7e/aa/714635c727dbfc251139226fa4eaf1b07f00dc12d9cd2eb25f931adaf873/multiprocess-0.70.19-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1bbf1b69af1cf64cd05f65337d9215b88079ec819cd0ea7bac4dab84e162efe7", size = 144743, upload-time = "2026-01-19T06:47:24.562Z" }, - { url = "https://files.pythonhosted.org/packages/0f/e1/155f6abf5e6b5d9cef29b6d0167c180846157a4aca9b9bee1a217f67c959/multiprocess-0.70.19-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:5be9ec7f0c1c49a4f4a6fd20d5dda4aeabc2d39a50f4ad53720f1cd02b3a7c2e", size = 144738, upload-time = "2026-01-19T06:47:26.636Z" }, - { url = "https://files.pythonhosted.org/packages/af/cb/f421c2869d75750a4f32301cc20c4b63fab6376e9a75c8e5e655bdeb3d9b/multiprocess-0.70.19-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1c3dce098845a0db43b32a0b76a228ca059a668071cfeaa0f40c36c0b1585d45", size = 144741, upload-time = "2026-01-19T06:47:27.985Z" }, - { url = "https://files.pythonhosted.org/packages/e3/45/8004d1e6b9185c1a444d6b55ac5682acf9d98035e54386d967366035a03a/multiprocess-0.70.19-py310-none-any.whl", hash = "sha256:97404393419dcb2a8385910864eedf47a3cadf82c66345b44f036420eb0b5d87", size = 134948, upload-time = "2026-01-19T06:47:32.325Z" }, - { url = "https://files.pythonhosted.org/packages/86/c2/dec9722dc3474c164a0b6bcd9a7ed7da542c98af8cabce05374abab35edd/multiprocess-0.70.19-py311-none-any.whl", hash = "sha256:928851ae7973aea4ce0eaf330bbdafb2e01398a91518d5c8818802845564f45c", size = 144457, upload-time = "2026-01-19T06:47:33.711Z" }, - { url = "https://files.pythonhosted.org/packages/71/70/38998b950a97ea279e6bd657575d22d1a2047256caf707d9a10fbce4f065/multiprocess-0.70.19-py312-none-any.whl", hash = "sha256:3a56c0e85dd5025161bac5ce138dcac1e49174c7d8e74596537e729fd5c53c28", size = 150281, upload-time = "2026-01-19T06:47:35.037Z" }, - { url = "https://files.pythonhosted.org/packages/7f/74/d2c27e03cb84251dfe7249b8e82923643c6d48fa4883b9476b025e7dc7eb/multiprocess-0.70.19-py313-none-any.whl", hash = "sha256:8d5eb4ec5017ba2fab4e34a747c6d2c2b6fecfe9e7236e77988db91580ada952", size = 156414, upload-time = "2026-01-19T06:47:35.915Z" }, - { url = "https://files.pythonhosted.org/packages/a0/61/af9115673a5870fd885247e2f1b68c4f1197737da315b520a91c757a861a/multiprocess-0.70.19-py314-none-any.whl", hash = "sha256:e8cc7fbdff15c0613f0a1f1f8744bef961b0a164c0ca29bdff53e9d2d93c5e5f", size = 160318, upload-time = "2026-01-19T06:47:37.497Z" }, - { url = "https://files.pythonhosted.org/packages/7e/82/69e539c4c2027f1e1697e09aaa2449243085a0edf81ae2c6341e84d769b6/multiprocess-0.70.19-py39-none-any.whl", hash = "sha256:0d4b4397ed669d371c81dcd1ef33fd384a44d6c3de1bd0ca7ac06d837720d3c5", size = 133477, upload-time = "2026-01-19T06:47:38.619Z" }, -] - -[[package]] -name = "networkx" -version = "3.4.2" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.11' and sys_platform == 'win32'", - "python_full_version < '3.11' and sys_platform == 'linux'", - "python_full_version < '3.11' and sys_platform == 'darwin'", - "python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux' and sys_platform != 'win32'", -] -sdist = { url = "https://files.pythonhosted.org/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://files.pythonhosted.org/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.6.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform == 'linux'", - "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'linux'", - "python_full_version >= '3.14' and sys_platform == 'darwin'", - "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'darwin'", - "python_full_version >= '3.14' and sys_platform != 'darwin' and sys_platform != 'linux' and sys_platform != 'win32'", - "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'darwin' and sys_platform != 'linux' and sys_platform != 'win32'", -] -sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025, upload-time = "2025-12-08T17:02:39.908Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504, upload-time = "2025-12-08T17:02:38.159Z" }, -] - [[package]] name = "numpy" version = "2.2.6" @@ -2466,158 +1743,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/04/74/f4c001f4714c3ad9ce037e18cf2b9c64871a84951eaa0baf683a9ca9301c/numpy-2.4.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f2cf083b324a467e1ab358c105f6cad5ea950f50524668a80c486ff1db24e119", size = 12509075, upload-time = "2026-03-29T13:21:57.644Z" }, ] -[[package]] -name = "nvidia-cublas" -version = "13.1.1.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "nvidia-cuda-nvrtc", marker = "sys_platform == 'linux'" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/a1/0bd24ee8c8d03adac032fd2909426a00c88f8c57961b1277ded97f91119f/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:b7a210458267ac818974c53038fbec2e969d5c99f305ab15c72522fa9f001dd5", size = 542848918, upload-time = "2026-04-08T18:46:22.985Z" }, - { url = "https://files.pythonhosted.org/packages/3b/cd/154ca20c38269e05eff77c1464e6c1da89f50a6390b565e9d82e06bc11e1/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:37936a16db8fe4ac1f065c2139360608a543a09275cb1a1af612e08cfa065436", size = 423138758, upload-time = "2026-04-08T18:46:58.655Z" }, -] - -[[package]] -name = "nvidia-cuda-cupti" -version = "13.0.85" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/2a/80353b103fc20ce05ef51e928daed4b6015db4aaa9162ed0997090fe2250/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_aarch64.whl", hash = "sha256:796bd679890ee55fb14a94629b698b6db54bcfd833d391d5e94017dd9d7d3151", size = 10310827, upload-time = "2025-09-04T08:26:42.012Z" }, - { url = "https://files.pythonhosted.org/packages/33/6d/737d164b4837a9bbd202f5ae3078975f0525a55730fe871d8ed4e3b952b0/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_x86_64.whl", hash = "sha256:4eb01c08e859bf924d222250d2e8f8b8ff6d3db4721288cf35d14252a4d933c8", size = 10715597, upload-time = "2025-09-04T08:26:51.312Z" }, -] - -[[package]] -name = "nvidia-cuda-nvrtc" -version = "13.0.88" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/68/483a78f5e8f31b08fb1bb671559968c0ca3a065ac7acabfc7cee55214fd6/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:ad9b6d2ead2435f11cbb6868809d2adeeee302e9bb94bcf0539c7a40d80e8575", size = 90215200, upload-time = "2025-09-04T08:28:44.204Z" }, - { url = "https://files.pythonhosted.org/packages/b7/dc/6bb80850e0b7edd6588d560758f17e0550893a1feaf436807d64d2da040f/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d27f20a0ca67a4bb34268a5e951033496c5b74870b868bacd046b1b8e0c3267b", size = 43015449, upload-time = "2025-09-04T08:28:20.239Z" }, -] - -[[package]] -name = "nvidia-cuda-runtime" -version = "13.0.96" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/87/4f/17d7b9b8e285199c58ce28e31b5c5bbaa4d8271af06a89b6405258245de2/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ef9bcbe90493a2b9d810e43d249adb3d02e98dd30200d86607d8d02687c43f55", size = 2261060, upload-time = "2025-10-09T08:55:15.78Z" }, - { url = "https://files.pythonhosted.org/packages/2e/24/d1558f3b68b1d26e706813b1d10aa1d785e4698c425af8db8edc3dced472/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f82250d7782aa23b6cfe765ecc7db554bd3c2870c43f3d1821f1d18aebf0548", size = 2243632, upload-time = "2025-10-09T08:55:36.117Z" }, -] - -[[package]] -name = "nvidia-cudnn-cu13" -version = "9.20.0.48" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "nvidia-cublas", marker = "sys_platform == 'linux'" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/56/c5/83384d846b2fd17c44bd499b36c75a45ed4f095fbbb2252294e89cea5c5c/nvidia_cudnn_cu13-9.20.0.48-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:e31454ae00094b0c55319d9d15b6fa2fc50a9e1c0f5c8c80fb75258234e731e1", size = 444574296, upload-time = "2026-03-09T19:28:27.751Z" }, - { url = "https://files.pythonhosted.org/packages/6e/5e/edb9c0ae051602c3ccaffe424256463636d639e27d7f302dde9975ef9e7a/nvidia_cudnn_cu13-9.20.0.48-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:0c45dd8eeb50b603f07995b1b300c62ffe6a1980482b82b3bcf94a4ca9d49304", size = 366173588, upload-time = "2026-03-09T19:29:34.474Z" }, -] - -[[package]] -name = "nvidia-cufft" -version = "12.0.0.61" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "nvidia-nvjitlink", marker = "sys_platform == 'linux'" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/ae/f417a75c0259e85c1d2f83ca4e960289a5f814ed0cea74d18c353d3e989d/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2708c852ef8cd89d1d2068bdbece0aa188813a0c934db3779b9b1faa8442e5f5", size = 214053554, upload-time = "2025-09-04T08:31:38.196Z" }, - { url = "https://files.pythonhosted.org/packages/a8/2f/7b57e29836ea8714f81e9898409196f47d772d5ddedddf1592eadb8ab743/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6c44f692dce8fd5ffd3e3df134b6cdb9c2f72d99cf40b62c32dde45eea9ddad3", size = 214085489, upload-time = "2025-09-04T08:31:56.044Z" }, -] - -[[package]] -name = "nvidia-cufile" -version = "1.15.1.6" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/70/4f193de89a48b71714e74602ee14d04e4019ad36a5a9f20c425776e72cd6/nvidia_cufile-1.15.1.6-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:08a3ecefae5a01c7f5117351c64f17c7c62efa5fffdbe24fc7d298da19cd0b44", size = 1223672, upload-time = "2025-09-04T08:32:22.779Z" }, - { url = "https://files.pythonhosted.org/packages/ab/73/cc4a14c9813a8a0d509417cf5f4bdaba76e924d58beb9864f5a7baceefbf/nvidia_cufile-1.15.1.6-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:bdc0deedc61f548bddf7733bdc216456c2fdb101d020e1ab4b88d232d5e2f6d1", size = 1136992, upload-time = "2025-09-04T08:32:14.119Z" }, -] - -[[package]] -name = "nvidia-curand" -version = "10.4.0.35" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/72/7c2ae24fb6b63a32e6ae5d241cc65263ea18d08802aaae087d9f013335a2/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:133df5a7509c3e292aaa2b477afd0194f06ce4ea24d714d616ff36439cee349a", size = 61962106, upload-time = "2025-08-04T10:21:41.128Z" }, - { url = "https://files.pythonhosted.org/packages/a5/9f/be0a41ca4a4917abf5cb9ae0daff1a6060cc5de950aec0396de9f3b52bc5/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:1aee33a5da6e1db083fe2b90082def8915f30f3248d5896bcec36a579d941bfc", size = 59544258, upload-time = "2025-08-04T10:22:03.992Z" }, -] - -[[package]] -name = "nvidia-cusolver" -version = "12.0.4.66" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "nvidia-cublas", marker = "sys_platform == 'linux'" }, - { name = "nvidia-cusparse", marker = "sys_platform == 'linux'" }, - { name = "nvidia-nvjitlink", marker = "sys_platform == 'linux'" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/c3/b30c9e935fc01e3da443ec0116ed1b2a009bb867f5324d3f2d7e533e776b/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:02c2457eaa9e39de20f880f4bd8820e6a1cfb9f9a34f820eb12a155aa5bc92d2", size = 223467760, upload-time = "2025-09-04T08:33:04.222Z" }, - { url = "https://files.pythonhosted.org/packages/5f/67/cba3777620cdacb99102da4042883709c41c709f4b6323c10781a9c3aa34/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:0a759da5dea5c0ea10fd307de75cdeb59e7ea4fcb8add0924859b944babf1112", size = 200941980, upload-time = "2025-09-04T08:33:22.767Z" }, -] - -[[package]] -name = "nvidia-cusparse" -version = "12.6.3.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "nvidia-nvjitlink", marker = "sys_platform == 'linux'" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/94/5c26f33738ae35276672f12615a64bd008ed5be6d1ebcb23579285d960a9/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:80bcc4662f23f1054ee334a15c72b8940402975e0eab63178fc7e670aa59472c", size = 162155568, upload-time = "2025-09-04T08:33:42.864Z" }, - { url = "https://files.pythonhosted.org/packages/fa/18/623c77619c31d62efd55302939756966f3ecc8d724a14dab2b75f1508850/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2b3c89c88d01ee0e477cb7f82ef60a11a4bcd57b6b87c33f789350b59759360b", size = 145942937, upload-time = "2025-09-04T08:33:58.029Z" }, -] - -[[package]] -name = "nvidia-cusparselt-cu13" -version = "0.8.1" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/46/e1/cdc1797eadf82d3a9a575a19b33fdc871a97edbec42c00b5b5e914f4aff4/nvidia_cusparselt_cu13-0.8.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:4dca476c50bf4780d46cd0bfbd82e2bc10a08e4fef7950917ce8d7578d22a23f", size = 221051344, upload-time = "2025-09-05T18:49:51.289Z" }, - { url = "https://files.pythonhosted.org/packages/34/7d/2661f2fb3ac4302f3a246f5fc030213ac60c1fe0bce84f9783dbd831dbb7/nvidia_cusparselt_cu13-0.8.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:786ce87568c303fadb5afcc7102d454cd3040d75f6f8626f5db460d1871f4dd0", size = 170148586, upload-time = "2025-09-05T18:50:50.248Z" }, -] - -[[package]] -name = "nvidia-nccl-cu13" -version = "2.29.7" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/72/0d/daf50d44177ee0cbc7ff0a0c91eb5ff676c82be42f9a970bc7597f440c3a/nvidia_nccl_cu13-2.29.7-py3-none-manylinux_2_18_aarch64.whl", hash = "sha256:674a12383e3c38a1bcccae7d4f3633b37852230b6047883cb2f4c2d1b36d9bf5", size = 206014712, upload-time = "2026-03-03T05:34:20.843Z" }, - { url = "https://files.pythonhosted.org/packages/67/f4/58e4e91b6919367c7aafb8e36fce9aad1a3047e536bf7e2fd560927d3a4c/nvidia_nccl_cu13-2.29.7-py3-none-manylinux_2_18_x86_64.whl", hash = "sha256:edd81538446786ec3b73972543e53bb43bcaf0bfc8ef76cb679fcc390ffe136d", size = 205976000, upload-time = "2026-03-03T05:36:24.472Z" }, -] - -[[package]] -name = "nvidia-nvjitlink" -version = "13.0.88" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/56/7a/123e033aaff487c77107195fa5a2b8686795ca537935a24efae476c41f05/nvidia_nvjitlink-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:13a74f429e23b921c1109976abefacc69835f2f433ebd323d3946e11d804e47b", size = 40713933, upload-time = "2025-09-04T08:35:43.553Z" }, - { url = "https://files.pythonhosted.org/packages/ab/2c/93c5250e64df4f894f1cbb397c6fd71f79813f9fd79d7cd61de3f97b3c2d/nvidia_nvjitlink-13.0.88-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e931536ccc7d467a98ba1d8b89ff7fa7f1fa3b13f2b0069118cd7f47bff07d0c", size = 38768748, upload-time = "2025-09-04T08:35:20.008Z" }, -] - -[[package]] -name = "nvidia-nvshmem-cu13" -version = "3.4.5" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/0f/05cc9c720236dcd2db9c1ab97fff629e96821be2e63103569da0c9b72f19/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dc2a197f38e5d0376ad52cd1a2a3617d3cdc150fd5966f4aee9bcebb1d68fe9", size = 60215947, upload-time = "2025-09-06T00:32:20.022Z" }, - { url = "https://files.pythonhosted.org/packages/3c/35/a9bf80a609e74e3b000fef598933235c908fcefcef9026042b8e6dfde2a9/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:290f0a2ee94c9f3687a02502f3b9299a9f9fe826e6d0287ee18482e78d495b80", size = 60412546, upload-time = "2025-09-06T00:32:41.564Z" }, -] - -[[package]] -name = "nvidia-nvtx" -version = "13.0.85" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/f3/d86c845465a2723ad7e1e5c36dcd75ddb82898b3f53be47ebd429fb2fa5d/nvidia_nvtx-13.0.85-py3-none-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4936d1d6780fbe68db454f5e72a42ff64d1fd6397df9f363ae786930fd5c1cd4", size = 148047, upload-time = "2025-09-04T08:29:01.761Z" }, - { url = "https://files.pythonhosted.org/packages/a8/64/3708a90d1ebe202ffdeb7185f878a3c84d15c2b2c31858da2ce0583e2def/nvidia_nvtx-13.0.85-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb7780edb6b14107373c835bf8b72e7a178bac7367e23da7acb108f973f157a6", size = 148878, upload-time = "2025-09-04T08:28:53.627Z" }, -] - [[package]] name = "orjson" version = "3.11.9" @@ -2901,41 +2026,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0f/54/68a0978d1ef8502b8492099beaa6e7a0c1b32e3b5d4f677f5810cb08711c/pandas-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b2c95f8bfc1ee412bf482605d7bfd30c12d1d26bd59fdd91efeef1d4718decb1", size = 9466464, upload-time = "2026-05-11T18:54:22.754Z" }, ] -[[package]] -name = "pdfminer-six" -version = "20260107" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "charset-normalizer" }, - { name = "cryptography" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/34/a4/5cec1112009f0439a5ca6afa8ace321f0ab2f48da3255b7a1c8953014670/pdfminer_six-20260107.tar.gz", hash = "sha256:96bfd431e3577a55a0efd25676968ca4ce8fd5b53f14565f85716ff363889602", size = 8512094, upload-time = "2026-01-07T13:29:12.937Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/20/8b/28c4eaec9d6b036a52cb44720408f26b1a143ca9bce76cc19e8f5de00ab4/pdfminer_six-20260107-py3-none-any.whl", hash = "sha256:366585ba97e80dffa8f00cebe303d2f381884d8637af4ce422f1df3ef38111a9", size = 6592252, upload-time = "2026-01-07T13:29:10.742Z" }, -] - -[[package]] -name = "peft" -version = "0.19.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "accelerate" }, - { name = "huggingface-hub" }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "packaging" }, - { name = "psutil" }, - { name = "pyyaml" }, - { name = "safetensors" }, - { name = "torch" }, - { name = "tqdm" }, - { name = "transformers" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/86/cf/037f1e3d5186496c05513a6754639e2dab3038a05f384284d49a9bd06a2d/peft-0.19.1.tar.gz", hash = "sha256:0d97542fe96dcdaa20d3b81c06f26f988618f416a73544ab23c3618ccb674a40", size = 763738, upload-time = "2026-04-16T15:46:45.105Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/b6/f54d676ed93cc2dd2234c3b172ea9c8c3d7d29361e66b1b23dec57a67465/peft-0.19.1-py3-none-any.whl", hash = "sha256:2113f72a81621b5913ef28f9022204c742df111890c5f49d812716a4a301e356", size = 680692, upload-time = "2026-04-16T15:46:42.886Z" }, -] - [[package]] name = "pluggy" version = "1.6.0" @@ -3100,34 +2190,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/95/608f665226bca68b736b79e457fded9a2a38c4f4379a4a7614303d9db3bc/protobuf-7.34.1-py3-none-any.whl", hash = "sha256:bb3812cd53aefea2b028ef42bd780f5b96407247f20c6ef7c679807e9d188f11", size = 170715, upload-time = "2026-03-20T17:34:45.384Z" }, ] -[[package]] -name = "psutil" -version = "7.2.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" }, - { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" }, - { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" }, - { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" }, - { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" }, - { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" }, - { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" }, - { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" }, - { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" }, - { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" }, - { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" }, - { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" }, - { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, - { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, - { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, - { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, - { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, - { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, - { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, - { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, -] - [[package]] name = "psycopg2-binary" version = "2.9.12" @@ -3191,63 +2253,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/20/be/b732c8418ffa5bcfda002890f5dc4c869fc17db66ff11f53b17cfe44afc0/psycopg2_binary-2.9.12-cp314-cp314-win_amd64.whl", hash = "sha256:f12ae41fcafadb39b2785e64a40f9db05d6de2ac114077457e0e7c597f3af980", size = 2848762, upload-time = "2026-04-20T23:35:46.421Z" }, ] -[[package]] -name = "pyarrow" -version = "24.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/91/13/13e1069b351bdc3881266e11147ffccf687505dbb0ea74036237f5d454a5/pyarrow-24.0.0.tar.gz", hash = "sha256:85fe721a14dd823aca09127acbb06c3ca723efbd436c004f16bca601b04dcc83", size = 1180261, upload-time = "2026-04-21T10:51:25.837Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a5/bf/a34fee1d624152124fa8355c42f34195ad5fe5233ce5bb87946432047d52/pyarrow-24.0.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:7c2b98645d576a0b9616892ead22b64a83a5f043c5e2ca15ebcefcb5b70c80cb", size = 35076681, upload-time = "2026-04-21T08:51:46.845Z" }, - { url = "https://files.pythonhosted.org/packages/1d/41/64180033d7027afce12dc96d0fe1f504c6fa112190582b458acea2399530/pyarrow-24.0.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:644a246325b8c69c595ad1dd4b463eba4b0cdb731370e4a86137d433208d6147", size = 36684260, upload-time = "2026-04-21T08:51:53.642Z" }, - { url = "https://files.pythonhosted.org/packages/57/02/9b9320e673dd8a99411fac78690f3df92f6dd6f59754c750110bca66d64e/pyarrow-24.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:3a577bd840ca83f646f0a625dbc571dba7044c43c2d1503afc378b570954345c", size = 45698566, upload-time = "2026-04-21T10:46:02.133Z" }, - { url = "https://files.pythonhosted.org/packages/67/33/f75e91b9a64c3f33c787e263c93b871ad91b8a4a68c1d5cebddd9840e835/pyarrow-24.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:e3268e43984d0b1a185c89b4cfff282a7ead12fc93f56cfd7088bdbcbe727041", size = 48835562, upload-time = "2026-04-21T10:46:10.278Z" }, - { url = "https://files.pythonhosted.org/packages/a5/63/097510448e47e4091faa41c43ba92f97cecaab8f4535b56a3d149578f634/pyarrow-24.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2392d954fcb920f42d230284b677605e4e2fbb11f2821e823e642abd67fbb491", size = 49394997, upload-time = "2026-04-21T10:46:18.08Z" }, - { url = "https://files.pythonhosted.org/packages/60/6b/c047d6222ab279024a062742d1807e2fbaf27bba88a98637299ff47b9236/pyarrow-24.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bec9373df11544592b0ba7ec2af0e35059e5f0e7647c6183a854dedd193298f1", size = 51911424, upload-time = "2026-04-21T10:46:25.347Z" }, - { url = "https://files.pythonhosted.org/packages/3a/ba/464cc70761c2a525d97ebd84e21c31ebd47f3ef4bdcee117009f51c46f24/pyarrow-24.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:c42ab9439498270139cc63e18847a02afe5c8b3ed9c931266533cfe378bd3591", size = 27251730, upload-time = "2026-04-21T10:46:30.913Z" }, - { url = "https://files.pythonhosted.org/packages/62/c9/a47ab7ece0d86cbe6678418a0fbd1ac4bb493b9184a3891dfa0e7f287ae0/pyarrow-24.0.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:b0e131f880cda8d04e076cee175a46fc0e8bc8b65c99c6c09dff6669335fde74", size = 35068898, upload-time = "2026-04-21T10:46:36.599Z" }, - { url = "https://files.pythonhosted.org/packages/d1/bc/8db86617a9a58008acf8913d6fed68ea2a46acb6de928db28d724c891a68/pyarrow-24.0.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:1b2fe7f9a5566401a0ef2571f197eb92358925c1f0c8dba305d6e43ea0871bb3", size = 36679915, upload-time = "2026-04-21T10:46:42.602Z" }, - { url = "https://files.pythonhosted.org/packages/eb/8e/fb178720400ef69db251eb4a9c3ccf4af269bc1feb5055529b8fc87170d1/pyarrow-24.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:0b3537c00fb8d384f15ac1e79b6eb6db04a16514c8c1d22e59a9b95c8ba42868", size = 45697931, upload-time = "2026-04-21T10:46:48.403Z" }, - { url = "https://files.pythonhosted.org/packages/f3/27/99c42abe8e21b44f4917f62631f3aa31404882a2c41d8a4cd5c110e13d52/pyarrow-24.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:14e31a3c9e35f1ab6356c6378f6f72830e6d2d5f1791df3774a7b097d18a6a1e", size = 48837449, upload-time = "2026-04-21T10:46:55.329Z" }, - { url = "https://files.pythonhosted.org/packages/36/b6/333749e2666e9032891125bf9c691146e92901bece62030ac1430e2e7c88/pyarrow-24.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b7d9a514e73bc42711e6a35aaccf3587c520024fe0a25d830a1a8a27c15f4f57", size = 49395949, upload-time = "2026-04-21T10:47:01.869Z" }, - { url = "https://files.pythonhosted.org/packages/17/25/c5201706a2dd374e8ba6ee3fd7a8c89fb7ffc16eed5217a91fd2bd7f7626/pyarrow-24.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b196eb3f931862af3fa84c2a253514d859c08e0d8fe020e07be12e75a5a9780c", size = 51912986, upload-time = "2026-04-21T10:47:09.872Z" }, - { url = "https://files.pythonhosted.org/packages/f8/d2/4d1bbba65320b21a49678d6fbdc6ff7c649251359fdcfc03568c4136231d/pyarrow-24.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:35405aecb474e683fb36af650618fd5340ee5471fc65a21b36076a18bbc6c981", size = 27255371, upload-time = "2026-04-21T10:47:15.943Z" }, - { url = "https://files.pythonhosted.org/packages/b4/a9/9686d9f07837f91f775e8932659192e02c74f9d8920524b480b85212cc68/pyarrow-24.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:6233c9ed9ab9d1db47de57d9753256d9dcffbf42db341576099f0fd9f6bf4810", size = 34981559, upload-time = "2026-04-21T10:47:22.17Z" }, - { url = "https://files.pythonhosted.org/packages/80/b6/0ddf0e9b6ead3474ab087ae598c76b031fc45532bf6a63f3a553440fb258/pyarrow-24.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:f7616236ec1bc2b15bfdec22a71ab38851c86f8f05ff64f379e1278cf20c634a", size = 36663654, upload-time = "2026-04-21T10:47:28.315Z" }, - { url = "https://files.pythonhosted.org/packages/7c/3b/926382efe8ce27ba729071d3566ade6dfb86bdf112f366000196b2f5780a/pyarrow-24.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:1617043b99bd33e5318ae18eb2919af09c71322ef1ca46566cdafc6e6712fb66", size = 45679394, upload-time = "2026-04-21T10:47:34.821Z" }, - { url = "https://files.pythonhosted.org/packages/b3/7a/829f7d9dfd37c207206081d6dad474d81dde29952401f07f2ba507814818/pyarrow-24.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:6165461f55ef6314f026de6638d661188e3455d3ec49834556a0ebbdbace18bb", size = 48863122, upload-time = "2026-04-21T10:47:42.056Z" }, - { url = "https://files.pythonhosted.org/packages/5f/e8/f88ce625fe8babaae64e8db2d417c7653adb3019b08aae85c5ed787dc816/pyarrow-24.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3b13dedfe76a0ad2d1d859b0811b53827a4e9d93a0bcb05cf59333ab4980cc7e", size = 49376032, upload-time = "2026-04-21T10:47:48.967Z" }, - { url = "https://files.pythonhosted.org/packages/36/7a/82c363caa145fff88fb475da50d3bf52bb024f61917be5424c3392eaf878/pyarrow-24.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:25ea65d868eb04015cd18e6df2fbe98f07e5bda2abefabcb88fce39a947716f6", size = 51929490, upload-time = "2026-04-21T10:47:55.981Z" }, - { url = "https://files.pythonhosted.org/packages/66/1c/e3e72c8014ad2743ca64a701652c733cc5cbcee15c0463a32a8c55518d9e/pyarrow-24.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:295f0a7f2e242dabd513737cf076007dc5b2d59237e3eca37b05c0c6446f3826", size = 27355660, upload-time = "2026-04-21T10:48:01.718Z" }, - { url = "https://files.pythonhosted.org/packages/6f/d3/a1abf004482026ddc17f4503db227787fa3cfe41ec5091ff20e4fea55e57/pyarrow-24.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:02b001b3ed4723caa44f6cd1af2d5c86aa2cf9971dacc2ffa55b21237713dfba", size = 34976759, upload-time = "2026-04-21T10:48:07.258Z" }, - { url = "https://files.pythonhosted.org/packages/4f/4a/34f0a36d28a2dd32225301b79daad44e243dc1a2bb77d43b60749be255c4/pyarrow-24.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:04920d6a71aabd08a0417709efce97d45ea8e6fb733d9ca9ecffb13c67839f68", size = 36658471, upload-time = "2026-04-21T10:48:13.347Z" }, - { url = "https://files.pythonhosted.org/packages/1f/78/543b94712ae8bb1a6023bcc1acf1a740fbff8286747c289cd9468fced2a5/pyarrow-24.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:a964266397740257f16f7bb2e4f08a0c81454004beab8ff59dd531b73610e9f2", size = 45675981, upload-time = "2026-04-21T10:48:20.201Z" }, - { url = "https://files.pythonhosted.org/packages/84/9f/8fb7c222b100d314137fa40ec050de56cd8c6d957d1cfff685ce72f15b17/pyarrow-24.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:6f066b179d68c413374294bc1735f68475457c933258df594443bb9d88ddc2a0", size = 48859172, upload-time = "2026-04-21T10:48:27.541Z" }, - { url = "https://files.pythonhosted.org/packages/a7/d3/1ea72538e6c8b3b475ed78d1049a2c518e655761ea50fe1171fc855fcab7/pyarrow-24.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1183baeb14c5f587b1ec52831e665718ce632caab84b7cd6b85fd44f96114495", size = 49385733, upload-time = "2026-04-21T10:48:34.7Z" }, - { url = "https://files.pythonhosted.org/packages/c3/be/c3d8b06a1ba35f2260f8e1f771abbee7d5e345c0937aab90675706b1690a/pyarrow-24.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:806f24b4085453c197a5078218d1ee08783ebbba271badd153d1ae22a3ee804f", size = 51934335, upload-time = "2026-04-21T10:48:42.099Z" }, - { url = "https://files.pythonhosted.org/packages/9c/62/89e07a1e7329d2cde3e3c6994ba0839a24977a2beda8be6005ea3d860b99/pyarrow-24.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:e4505fc6583f7b05ab854934896bcac8253b04ac1171a77dfb73efef92076d91", size = 27271748, upload-time = "2026-04-21T10:49:42.532Z" }, - { url = "https://files.pythonhosted.org/packages/17/1a/cff3a59f80b5b1658549d46611b67163f65e0664431c076ad728bf9d5af4/pyarrow-24.0.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:1a4e45017efbf115032e4475ee876d525e0e36c742214fbe405332480ecd6275", size = 35238554, upload-time = "2026-04-21T10:48:48.526Z" }, - { url = "https://files.pythonhosted.org/packages/a8/99/cce0f42a327bfef2c420fb6078a3eb834826e5d6697bf3009fe11d2ad051/pyarrow-24.0.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:7986f1fa71cee060ad00758bcc79d3a93bab8559bf978fab9e53472a2e25a17b", size = 36782301, upload-time = "2026-04-21T10:48:55.181Z" }, - { url = "https://files.pythonhosted.org/packages/2a/66/8e560d5ff6793ca29aca213c53eec0dd482dd46cb93b2819e5aab52e4252/pyarrow-24.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:d3e0b61e8efb24ed38898e5cdc5fffa9124be480008d401a1f8071500494ae42", size = 45721929, upload-time = "2026-04-21T10:49:03.676Z" }, - { url = "https://files.pythonhosted.org/packages/27/0c/a26e25505d030716e078d9f16eb74973cbf0b33b672884e9f9da1c83b871/pyarrow-24.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:55a3bc1e3df3b5567b7d27ef551b2283f0c68a5e86f1cd56abc569da4f31335b", size = 48825365, upload-time = "2026-04-21T10:49:11.714Z" }, - { url = "https://files.pythonhosted.org/packages/5f/eb/771f9ecb0c65e73fe9dccdd1717901b9594f08c4515d000c7c62df573811/pyarrow-24.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:641f795b361874ac9da5294f8f443dfdbee355cf2bd9e3b8d97aaac2306b9b37", size = 49451819, upload-time = "2026-04-21T10:49:21.474Z" }, - { url = "https://files.pythonhosted.org/packages/48/da/61ae89a88732f5a785646f3ec6125dbb640fa98a540eb2b9889caa561403/pyarrow-24.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8adc8e6ce5fccf5dc707046ae4914fd537def529709cc0d285d37a7f9cd442ca", size = 51909252, upload-time = "2026-04-21T10:49:31.164Z" }, - { url = "https://files.pythonhosted.org/packages/cb/1a/8dd5cafab7b66573fa91c03d06d213356ad4edd71813aa75e08ce2b3a844/pyarrow-24.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:9b18371ad2f44044b81a8d23bc2d8a9b6a6226dca775e8e16cfee640473d6c5d", size = 27388127, upload-time = "2026-04-21T10:49:37.334Z" }, - { url = "https://files.pythonhosted.org/packages/ad/80/d022a34ff05d2cbedd8ccf841fc1f532ecfa9eb5ed1711b56d0e0ea71fc9/pyarrow-24.0.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:1cc9057f0319e26333b357e17f3c2c022f1a83739b48a88b25bfd5fa2dc18838", size = 35007997, upload-time = "2026-04-21T10:49:48.796Z" }, - { url = "https://files.pythonhosted.org/packages/1a/ff/f01485fda6f4e5d441afb8dd5e7681e4db18826c1e271852f5d3957d6a80/pyarrow-24.0.0-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:e6f1278ee4785b6db21229374a1c9e54ec7c549de5d1efc9630b6207de7e170b", size = 36678720, upload-time = "2026-04-21T10:49:55.858Z" }, - { url = "https://files.pythonhosted.org/packages/9e/c2/2d2d5fea814237923f71b36495211f20b43a1576f9a4d6da7e751a64ec6f/pyarrow-24.0.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:adbbedc55506cbdabb830890444fb856bfb0060c46c6f8026c6c2f2cf86ae795", size = 45741852, upload-time = "2026-04-21T10:50:04.624Z" }, - { url = "https://files.pythonhosted.org/packages/8e/3a/28ba9c1c1ebdbb5f1b94dfebb46f207e52e6a554b7fe4132540fde29a3a0/pyarrow-24.0.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:ae8a1145af31d903fa9bb166824d7abe9b4681a000b0159c9fb99c11bc11ad26", size = 48889852, upload-time = "2026-04-21T10:50:12.293Z" }, - { url = "https://files.pythonhosted.org/packages/df/51/4a389acfd31dca009f8fb82d7f510bb4130f2b3a8e18cf00194d0687d8ac/pyarrow-24.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d7027eba1df3b2069e2e8d80f644fa0918b68c46432af3d088ddd390d063ecde", size = 49445207, upload-time = "2026-04-21T10:50:20.677Z" }, - { url = "https://files.pythonhosted.org/packages/19/4b/0bab2b23d2ae901b1b9a03c0efd4b2d070256f8ce3fc43f6e58c167b2081/pyarrow-24.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e56a1ffe9bf7b727432b89104cc0849c21582949dd7bdcb34f17b2001a351a76", size = 51954117, upload-time = "2026-04-21T10:50:29.14Z" }, - { url = "https://files.pythonhosted.org/packages/29/88/f4e9145da0417b3d2c12035a8492b35ff4a3dbc653e614fcfb51d9dedb38/pyarrow-24.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:38be1808cdd068605b787e6ca9119b27eb275a0234e50212c3492331680c3b1e", size = 28001155, upload-time = "2026-04-21T10:51:22.337Z" }, - { url = "https://files.pythonhosted.org/packages/79/4f/46a49a63f43526da895b1a45bbb51d5baf8e4d77159f8528fc3e5490007f/pyarrow-24.0.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:418e48ce50a45a6a6c73c454677203a9c75c966cb1e92ca3370959185f197a05", size = 35250387, upload-time = "2026-04-21T10:50:35.552Z" }, - { url = "https://files.pythonhosted.org/packages/a0/da/d5e0cd5ef00796922404806d5f00325cdadc3441ce2c13fe7115f2df9a64/pyarrow-24.0.0-cp314-cp314t-macosx_12_0_x86_64.whl", hash = "sha256:2f16197705a230a78270cdd4ea8a1d57e86b2fdcbc34a1f6aebc72e65c986f9a", size = 36797102, upload-time = "2026-04-21T10:50:42.417Z" }, - { url = "https://files.pythonhosted.org/packages/34/c7/5904145b0a593a05236c882933d439b5720f0a145381179063722fbfc123/pyarrow-24.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:fb24ac194bfc5e86839d7dcd52092ee31e5fe6733fe11f5e3b06ef0812b20072", size = 45745118, upload-time = "2026-04-21T10:50:49.324Z" }, - { url = "https://files.pythonhosted.org/packages/13/d3/cca42fe166d1c6e4d5b80e530b7949104d10e17508a90ae202dac205ce2a/pyarrow-24.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:9700ebd9a51f5895ce75ff4ac4b3c47a7d4b42bc618be8e713e5d56bacf5f931", size = 48844765, upload-time = "2026-04-21T10:50:55.579Z" }, - { url = "https://files.pythonhosted.org/packages/b0/49/942c3b79878ba928324d1e17c274ed84581db8c0a749b24bcf4cbdf15bd3/pyarrow-24.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d8ddd2768da81d3ee08cfea9b597f4abb4e8e1dc8ae7e204b608d23a0d3ab699", size = 49471890, upload-time = "2026-04-21T10:51:02.439Z" }, - { url = "https://files.pythonhosted.org/packages/76/97/ff71431000a75d84135a1ace5ca4ba11726a231a8007bbb320a4c54075d5/pyarrow-24.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:61a3d7eaa97a14768b542f3d284dc6400dd2470d9f080708b13cd46b6ae18136", size = 51932250, upload-time = "2026-04-21T10:51:10.576Z" }, - { url = "https://files.pythonhosted.org/packages/51/be/6f79d55816d5c22557cf27533543d5d70dfe692adfbee4b99f2760674f38/pyarrow-24.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:c91d00057f23b8d353039520dc3a6c09d8608164c692e9f59a175a42b2ae0c19", size = 28131282, upload-time = "2026-04-21T10:51:16.815Z" }, -] - [[package]] name = "pycparser" version = "3.0" @@ -3642,127 +2647,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/74/3a/95deec7db1eb53979973ebd156f3369a72732208d1391cd2e5d127062a32/redis-7.4.0-py3-none-any.whl", hash = "sha256:a9c74a5c893a5ef8455a5adb793a31bb70feb821c86eccb62eebef5a19c429ec", size = 409772, upload-time = "2026-03-24T09:14:35.968Z" }, ] -[[package]] -name = "regex" -version = "2026.5.9" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/dc/0e/49aee608ad09480e7fd276898c99ec6192985fa331abe4eb3a986094490b/regex-2026.5.9.tar.gz", hash = "sha256:a8234aa23ec39894bfe4a3f1b85616a7032481964a13ac6fc9f10de4f6fca270", size = 416074, upload-time = "2026-05-09T23:15:19.37Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/ed/0ad2c8edf634918eb4484365d3819fa7bd7f58daf807fe7fb21812c316e5/regex-2026.5.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a9e1328e17c84c1a5d22ec9f785ecef4a967fab9a42b6a8dc3bcbebd0a0c9e44", size = 489438, upload-time = "2026-05-09T23:11:29.374Z" }, - { url = "https://files.pythonhosted.org/packages/89/a9/4ed972ad263963b860b7c3e86e0e1bcc791def47b43b8c8efe57e710f139/regex-2026.5.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bfe1ce50cbfb569d74e1e4337da6468961f31dbea55fd85aa5de59c0947a805a", size = 291270, upload-time = "2026-05-09T23:11:33.254Z" }, - { url = "https://files.pythonhosted.org/packages/16/81/075930d9fa28c4ea1f53398dd015ee7c882f623539759113cda1257f4b82/regex-2026.5.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:15ee42209947f4ca045412eae98416317238163618ace2a8e54f99586a466733", size = 289198, upload-time = "2026-05-09T23:11:35.769Z" }, - { url = "https://files.pythonhosted.org/packages/d4/c8/5cdfbf0b5dc6599e1b6131eff43262e5275d4ec3469ce10216061659aadb/regex-2026.5.9-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4bb445ff3f725f59df8f6014edb547ee928ec7023a774f6a39a3f953038cbb2", size = 784765, upload-time = "2026-05-09T23:11:37.689Z" }, - { url = "https://files.pythonhosted.org/packages/cd/ca/ae5fd6edc59b7f84b904b31d6ec39a860cbcecd10f64bd5a062ca83a4864/regex-2026.5.9-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:446ddd671e43ab535810c4b21cff7104945c701d4a14d1e6d1cd6f4e445a8bea", size = 852115, upload-time = "2026-05-09T23:11:39.973Z" }, - { url = "https://files.pythonhosted.org/packages/f6/ce/a91cf555afb51f3b74a182e24ba073b91ea7bb64592fc4b315c111bb19fd/regex-2026.5.9-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7b92817338591505f282cf3864c145244b1edcf5381d237038df955001091538", size = 899503, upload-time = "2026-05-09T23:11:42.48Z" }, - { url = "https://files.pythonhosted.org/packages/55/7f/725a0a2b245a4cf0c4bab29d0e97c74285d94136a65d1b55a6459a583502/regex-2026.5.9-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6b8a143aca6c39b446ea8092cde25cc8fe9304d4f5fecfbc1a9dbb0282703c2", size = 794093, upload-time = "2026-05-09T23:11:44.681Z" }, - { url = "https://files.pythonhosted.org/packages/e3/2a/996efbd59ce6b5d4a09e3af6180ceb62af171f4a9a6fb557d2f0ae0d462b/regex-2026.5.9-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0f03aa6898aaaac4592479821df16e68e8d0e29e903e65d8f2dfb2f19028a989", size = 786234, upload-time = "2026-05-09T23:11:46.882Z" }, - { url = "https://files.pythonhosted.org/packages/4b/0a/8731e8b8806174c9cdd5903f80a14990331c1f42fc4209b540952e9e010d/regex-2026.5.9-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ed457d8e98ae812ed7732bef7bf78de78e834eae0372a74e23ca90ef21d910f9", size = 769895, upload-time = "2026-05-09T23:11:49.324Z" }, - { url = "https://files.pythonhosted.org/packages/9a/0b/932473194bd563f342a412ae2ffbbd6da608306a2bc4e99249a41c2b0b92/regex-2026.5.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:71b61c5bfe1c806332defc42ad6c780b3c55f661986d7f40283a3a88274b4c00", size = 774991, upload-time = "2026-05-09T23:11:51.261Z" }, - { url = "https://files.pythonhosted.org/packages/98/80/9523d196010031df25f7177ee0a467efbee436324038e5d99def17a57515/regex-2026.5.9-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:3b1e39888c5e0c7d92cea4fc777396c4a90363b05de75d02eb459a4752200808", size = 848790, upload-time = "2026-05-09T23:11:53.232Z" }, - { url = "https://files.pythonhosted.org/packages/3c/07/56987b35e89edf47e4a38cf2845aeee476bfa688a6bdbd3e820cda461dc1/regex-2026.5.9-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:6ba42b2e7e7f46cf68cc6a5ca36fa07959f9bbd9c6bdcc47b6ee76549a590248", size = 757679, upload-time = "2026-05-09T23:11:55.82Z" }, - { url = "https://files.pythonhosted.org/packages/04/2a/ff713fff0c566507c06a4ce2dc0ae8e7eeebc88811a95fc81cf1e7d534dd/regex-2026.5.9-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:c010eb8caca74bdb40c07498d7ece26b4428fd3f04aa8a72c9ac6f79e8faaac6", size = 837116, upload-time = "2026-05-09T23:11:57.934Z" }, - { url = "https://files.pythonhosted.org/packages/77/90/df6d982b03e3614785c6937ba51b57f6733d97d2ee1c9bc7531dbfab3a54/regex-2026.5.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a6a563446a41adc451393dc6b8e6ad87979efaee3c8738690a8d1b08ebead1b4", size = 782081, upload-time = "2026-05-09T23:11:59.607Z" }, - { url = "https://files.pythonhosted.org/packages/c7/8a/4e88a5f7c3e98489aac4dd23142723d907b2a595b4a6abcbacabefeded09/regex-2026.5.9-cp310-cp310-win32.whl", hash = "sha256:954cc214c04663ee6d266fc61739cad83054683048de65c5bd1d640ad28098ac", size = 266247, upload-time = "2026-05-09T23:12:01.116Z" }, - { url = "https://files.pythonhosted.org/packages/6a/40/4b224cb0582b2dca1786726e6cdabe26abbf757d7f6718332f186da155d2/regex-2026.5.9-cp310-cp310-win_amd64.whl", hash = "sha256:b310768746dd314ea6e2ff4cc89ef215426813396ff4e94ee8e6f7096c8b6e03", size = 278416, upload-time = "2026-05-09T23:12:03.2Z" }, - { url = "https://files.pythonhosted.org/packages/12/4d/014fbe803204cab0947ee428f09f658a29632053dde1d3c6176bb4f0fd4c/regex-2026.5.9-cp310-cp310-win_arm64.whl", hash = "sha256:19c16ceb4a267a8789e25733e583983eeab9f0f8664e66b0bd1c5d21f14c2d4b", size = 270413, upload-time = "2026-05-09T23:12:04.649Z" }, - { url = "https://files.pythonhosted.org/packages/c2/dc/c1f2df4027e82fc54b5a473e4b250f5139faca49a0fbe29a48668d228f34/regex-2026.5.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ccf5249114cc3e772ecdd88a98a86eca0fd74c61ce32a94743758c083fc05d48", size = 489445, upload-time = "2026-05-09T23:12:06.111Z" }, - { url = "https://files.pythonhosted.org/packages/03/d2/59f01110660081cce9c0bc30ebd0b5ee250dacf658e3248ed92f01e0e8ee/regex-2026.5.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46f1326ca6e65b0879d23ca302c0f2415aad42ff0309b9c818e7949fe19a41d8", size = 291271, upload-time = "2026-05-09T23:12:07.731Z" }, - { url = "https://files.pythonhosted.org/packages/58/b6/14b2c84ff90ddb370c81d27503f4a0fcf071496416f4855f6cc8c5d81c35/regex-2026.5.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ef31cbfe458e21c6122ba8150ff060e0c7789ed0d26eb423f25472584920b555", size = 289212, upload-time = "2026-05-09T23:12:09.266Z" }, - { url = "https://files.pythonhosted.org/packages/03/d0/4db86529117320de0c84afd90e70bb47434625875e34fcef9d8c127c5b16/regex-2026.5.9-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:992604d02e6d9c6d786c24a706a71ecffe1020fc1ef264044474cd81fa2c3919", size = 792310, upload-time = "2026-05-09T23:12:11.416Z" }, - { url = "https://files.pythonhosted.org/packages/07/78/fe4800cd322f862ecffd2d553409b20d80650e5ed71b9d178f853d020b82/regex-2026.5.9-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c9411dd64ca95477225734a93dfc8583b51916b8d5942f99d6cac21e09965451", size = 861721, upload-time = "2026-05-09T23:12:13.681Z" }, - { url = "https://files.pythonhosted.org/packages/b5/d0/b3618a895dd8feb897c61bb2954edd265e1767d82a01d53065d5871127a3/regex-2026.5.9-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3dd4a3ff360dfb836fecdb93a4598f9d6e2ac81e3e397125145c6221bf58cf4c", size = 906460, upload-time = "2026-05-09T23:12:15.443Z" }, - { url = "https://files.pythonhosted.org/packages/33/6f/1481597e859ef19508b345eec4afd1416ed6e6b459c75a64026ef193aecf/regex-2026.5.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2a661a7d270a61f7cf460caee8b9fa2d5ef9e5c681234bcb9e0fe14f488e7dfc", size = 799843, upload-time = "2026-05-09T23:12:16.892Z" }, - { url = "https://files.pythonhosted.org/packages/73/59/955734c803f59108deccba3597ae440c76b62a652733c0006e6243758420/regex-2026.5.9-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f079e50a0d3cc3cd5091fa9ff45869a2e6b2cd35895731edafb0327901a8d86d", size = 773610, upload-time = "2026-05-09T23:12:19.127Z" }, - { url = "https://files.pythonhosted.org/packages/68/8f/70c04a236d651c81881dac42ef8538bddda6121434509d0a22d9e601503b/regex-2026.5.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4ebe8f0b5ec5a5024dc4a4c59f444c4e9afc5f2abdbb8962065b75d27fb971f9", size = 781645, upload-time = "2026-05-09T23:12:20.806Z" }, - { url = "https://files.pythonhosted.org/packages/1d/96/05c7434d88185e5d27fe54aeb74df86bd77cd79f52f0b4eae54faa8fea70/regex-2026.5.9-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:97cf3bc1b7d7d2306772ec07366c80d9df00ff79e79cea32898883a646d2fae2", size = 854473, upload-time = "2026-05-09T23:12:22.465Z" }, - { url = "https://files.pythonhosted.org/packages/4e/c1/6e3d8202d981f3117004bf341ee74893ba4ba8a9fbaf4b94615846550a08/regex-2026.5.9-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0f9eede6a5cbdc02d4978090186390936e1776a7d1359b21e41014c609880bcf", size = 763311, upload-time = "2026-05-09T23:12:24.351Z" }, - { url = "https://files.pythonhosted.org/packages/93/c7/e7737f1526b3fb32bd4c337fd6c71c3ebb5c8296fc34d11197e0955d2e35/regex-2026.5.9-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:01f0f5f55f4b64dacec85dc116d3c05fd23ad3ff037bbc73a2085775953c2611", size = 844593, upload-time = "2026-05-09T23:12:26.341Z" }, - { url = "https://files.pythonhosted.org/packages/a5/27/0daffb1a535bb39f422c3d200f4ab023c71110ad66a32b366bee708baba0/regex-2026.5.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1268eddd8486dc561d08eee1156e40aa3a8fe10f4bdec8fa653b455fcbffd12c", size = 789167, upload-time = "2026-05-09T23:12:27.975Z" }, - { url = "https://files.pythonhosted.org/packages/ce/fc/294fe4fac4f2ed67207b17471815870c1c45b3a489e08e0ac96daea16ef6/regex-2026.5.9-cp311-cp311-win32.whl", hash = "sha256:8676474c07469d6f33dd1085ca2cd45f65785f32518f2b20e36d9953ca07f994", size = 266249, upload-time = "2026-05-09T23:12:30.141Z" }, - { url = "https://files.pythonhosted.org/packages/d0/b0/8dce459f6245bcf8f6e9f23ac9569f1a0f15c131cc0745e82b43226204cf/regex-2026.5.9-cp311-cp311-win_amd64.whl", hash = "sha256:246de9d60aa3f8538b519834dd95cbf276ea263d6a7bd5a3666dc3fa0230505b", size = 278423, upload-time = "2026-05-09T23:12:31.676Z" }, - { url = "https://files.pythonhosted.org/packages/db/8d/f9aeff6ad63a3ef720386f2907e6d34a35a510a6e498ebad28b0fb3f6ab6/regex-2026.5.9-cp311-cp311-win_arm64.whl", hash = "sha256:d726ca3f0d76969bf1e8e477d160d3d666bbf999f6860bd314889e5345782046", size = 270420, upload-time = "2026-05-09T23:12:33.194Z" }, - { url = "https://files.pythonhosted.org/packages/50/9b/6550044bc44e17c84d312c031c2ec42fbdb6a4ec4e29093be3a172d08772/regex-2026.5.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:57eeeb05db7979413dec5438f2db21d7ecbba787cde7a711df1a6f6df672aa06", size = 490451, upload-time = "2026-05-09T23:12:34.72Z" }, - { url = "https://files.pythonhosted.org/packages/1e/95/fc7ba4303b5a0f92446a12ee6778ef2c6c799233f5060042a31bf390cfe9/regex-2026.5.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:398c521292f4c7fb807001dcd54694d3a1fcafc179a36ad9cc56f98df85930b6", size = 292112, upload-time = "2026-05-09T23:12:36.285Z" }, - { url = "https://files.pythonhosted.org/packages/54/4b/ee27938d1b2c443e89a9a10e00d2d19aa5ee300cd3d61140644e93bb083e/regex-2026.5.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f7a7c26137296beba7784de6eba69c6a93a63ccebc385e4962fe67e267a91225", size = 289599, upload-time = "2026-05-09T23:12:38.089Z" }, - { url = "https://files.pythonhosted.org/packages/d8/dd/ba103dc19614e25f3880800ca67ce093d6e21b325d72b8383c7bf906e9fa/regex-2026.5.9-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6441cc660d76107934a09c22167200839a0e89604a6297f78a974e66e931d2c0", size = 796732, upload-time = "2026-05-09T23:12:40.062Z" }, - { url = "https://files.pythonhosted.org/packages/cf/e7/f035b4fd858b050b0080bf302968dc0f59ba34e391872d54936758e6844e/regex-2026.5.9-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:91328f1c23d47595ca3ef0a7557fa129c5a23404b775c770697d2f35b33e0107", size = 865440, upload-time = "2026-05-09T23:12:42.059Z" }, - { url = "https://files.pythonhosted.org/packages/0a/51/8cd301ecc899aea28124357f729f4272f44de7806fc7ca02490bfbe253e8/regex-2026.5.9-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:93a7860539414dddaefba2b40f8771765ae17949d4c7182b876ce429e11a8309", size = 912329, upload-time = "2026-05-09T23:12:44.373Z" }, - { url = "https://files.pythonhosted.org/packages/cc/1e/3fbe2fa1e8cebd62f3bb7d3321cff1640aca2e240b51d9bd624aad949260/regex-2026.5.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd2810d22146b6d838acc5ec15602cb6b47920aa4e33015df3868eedfd20bab8", size = 801239, upload-time = "2026-05-09T23:12:46.268Z" }, - { url = "https://files.pythonhosted.org/packages/17/2f/6f6008682bf2cf98040a0d3153a8e557b6ab728d7713d045cee4ce544ab8/regex-2026.5.9-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:daff2bdbaf1d23e52fdff7c0b7bc2048b68f978df6a4d107ac981f94caef2e66", size = 777054, upload-time = "2026-05-09T23:12:48.051Z" }, - { url = "https://files.pythonhosted.org/packages/19/2b/eee0d20a6842ba04df4b8847a920b57ef56853f14ef85405473e586b605a/regex-2026.5.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4eeb011098fcb77af513dcef521a3dbecbf8849b1e38940759d293b7a93f5026", size = 785098, upload-time = "2026-05-09T23:12:49.851Z" }, - { url = "https://files.pythonhosted.org/packages/4a/98/6fc1e6410feefb92159edaed5041992bfe390e8d26c721865434acbca558/regex-2026.5.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ea9c8ecfa1b73c73b626534d6626e5340d429630943672b8480724f44e84b962", size = 860095, upload-time = "2026-05-09T23:12:51.666Z" }, - { url = "https://files.pythonhosted.org/packages/18/a3/bd855e0f2cb1a978ecf6fa6bb69632dd9c3f6ea3b81cde62fde14c9daec7/regex-2026.5.9-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:cd2846168eb9ee3c513902bc8225409cb1caab31d04728b145171fa1625d9621", size = 765762, upload-time = "2026-05-09T23:12:53.413Z" }, - { url = "https://files.pythonhosted.org/packages/dc/66/0ae8c092e60b14c79d24f8e0b7f0aea5bfbffdcab00b5483d13404d3c3a5/regex-2026.5.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:39617fb0cde9c0e6306dc70e3bfc096f3da793219879f7ae7aa341a69fbdcf6d", size = 852100, upload-time = "2026-05-09T23:12:55.256Z" }, - { url = "https://files.pythonhosted.org/packages/21/de/8dfde60fc1b21c946a893ba273403b72617edb261370cb1087099a83f088/regex-2026.5.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fd03c4f0e33280d15cae17159b899245d6b7c53d21def19b263b39655061f5ce", size = 789479, upload-time = "2026-05-09T23:12:57.573Z" }, - { url = "https://files.pythonhosted.org/packages/c3/1c/bdcc98f9a4af4fdd166c74941174619ccff4726d3ce32faa8e9a2ecd38dd/regex-2026.5.9-cp312-cp312-win32.whl", hash = "sha256:164eba9b755ea6f244b0d881196fbc1fac09714e9782c9e2732b813142033c8e", size = 266699, upload-time = "2026-05-09T23:12:59.14Z" }, - { url = "https://files.pythonhosted.org/packages/78/87/240d36864f9e48ace85f72e79ced97ceb7f27ce87739a947dcb834b4e6bc/regex-2026.5.9-cp312-cp312-win_amd64.whl", hash = "sha256:86f40a5d6444db30a125c9c9177e6b25dad981cbc37451fd838f145e6edac92e", size = 277783, upload-time = "2026-05-09T23:13:00.789Z" }, - { url = "https://files.pythonhosted.org/packages/4f/b5/7b30f312b0669dff5beebe5b0989dc2d1a312b1a44fab852199c387a5b96/regex-2026.5.9-cp312-cp312-win_arm64.whl", hash = "sha256:96f5f58b54a063d7ea9dca08e1cf57bfe10499c4d579ee672da284f57f5f0070", size = 270513, upload-time = "2026-05-09T23:13:02.426Z" }, - { url = "https://files.pythonhosted.org/packages/aa/da/797e91ecec6f84135da778ddce78c20e0af5d2a15c26f87a81bc3eadb6db/regex-2026.5.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d626b84406444b165fc0ba981604edea39f0588ff1f92baa23fe50799ea9afdb", size = 490303, upload-time = "2026-05-09T23:13:04.382Z" }, - { url = "https://files.pythonhosted.org/packages/44/da/bf30abaaa737b58f4a4b8c4a03659e02fd92092c822e0197ed9e0daab917/regex-2026.5.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d7bdc0ab8f3dd7e1b4f9ab88634e13374669db86bb3c72e8292f07ae313f539f", size = 292019, upload-time = "2026-05-09T23:13:06.022Z" }, - { url = "https://files.pythonhosted.org/packages/2d/e7/d0eaf5713828417b9e5648cf81fa9bacd4961f6ab98c380c2034f8716e35/regex-2026.5.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a8820737949116ffff55fe18f9fc644530063ba6ebfcb8314239416e78f1347c", size = 289468, upload-time = "2026-05-09T23:13:08.214Z" }, - { url = "https://files.pythonhosted.org/packages/d3/9b/b3fdd62b003baa1a9b593cd8c8699c9651c2e80cc21a5c715707983c42d7/regex-2026.5.9-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa0fbdbac82cb3e4450d0ccde7d7a35607f4cb2dd9fba4b8b69bfaf8c9fa6aed", size = 796749, upload-time = "2026-05-09T23:13:10.573Z" }, - { url = "https://files.pythonhosted.org/packages/d4/30/66ab84588765f5b4b271a9ca09ef7ce2b87caa95176ec3d2ad65d7bc4902/regex-2026.5.9-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:57e8915c7986aa33d25e4d3629cef711cd2863f2961b10409f0c04cb8b7d9020", size = 865445, upload-time = "2026-05-09T23:13:12.523Z" }, - { url = "https://files.pythonhosted.org/packages/1a/89/f05169e8588aac365f35ffc7f3bc3184f095ef4cfded7cfaa3c7fd5dbd89/regex-2026.5.9-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:508f56a89ba9cb26e4168cbc37dbd60a28d82430a9e18ad1d25fe0883c314ca2", size = 912322, upload-time = "2026-05-09T23:13:14.281Z" }, - { url = "https://files.pythonhosted.org/packages/30/e1/c93444052cf41581f3c884ab3fb5823daf0992f11cd4388d4275ca610558/regex-2026.5.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6d189041f15691cfa2b6c4290448ec221244d225b3f5fe9e7771b34ffcdf6e2", size = 801269, upload-time = "2026-05-09T23:13:16.569Z" }, - { url = "https://files.pythonhosted.org/packages/50/fe/0cf96b882f540e62e8b9956599798203d599c44cf4c77917ca27400ff69b/regex-2026.5.9-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e82db382b44d0111b22601c509c89f64434816c9e0eef9d1989cda8cc6ff1c04", size = 777085, upload-time = "2026-05-09T23:13:18.675Z" }, - { url = "https://files.pythonhosted.org/packages/23/5c/d78d4924e7fc875557b9e9b768423925fdfaac5549d06da7810019a9bd26/regex-2026.5.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2acfb48634f64996b57f90f39afa692ff362162722581921fe92239a59960f3c", size = 785153, upload-time = "2026-05-09T23:13:20.525Z" }, - { url = "https://files.pythonhosted.org/packages/bf/e0/5214774090e7b4524dcea3e3c4aa74141d43043f8beb49c1599db1c8b53a/regex-2026.5.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d29eebfc9525db68cad3c97eedd7f754fa265aa5cd0cf4f863b2421e1b48fc9f", size = 860164, upload-time = "2026-05-09T23:13:22.263Z" }, - { url = "https://files.pythonhosted.org/packages/6e/e1/4a57a83350319b1271f0d7a249b8672513ed928b237a741631270de6caea/regex-2026.5.9-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:debb893095e944091c16e641a6e33c1b0f4cb61ab945ec5afbf53ce7068834d8", size = 765731, upload-time = "2026-05-09T23:13:24.277Z" }, - { url = "https://files.pythonhosted.org/packages/12/f4/499e74a20c156fc75836ee04a72a38d1a063978f600937f9760467beb1b0/regex-2026.5.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d659eee77986549c9ea45b861c7567e44d6287c3dc9a4565478853f7b9fe2ff6", size = 852062, upload-time = "2026-05-09T23:13:26.125Z" }, - { url = "https://files.pythonhosted.org/packages/5b/92/7eebc0d0a01e78629695f342ba17e0deaff8fb45e79cc0d7b98287da6e3e/regex-2026.5.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2efa205e6d98b24d1f3ab395c11aa15cdf10935bca283d0285e0499c284fba21", size = 789577, upload-time = "2026-05-09T23:13:27.814Z" }, - { url = "https://files.pythonhosted.org/packages/05/a4/018e71f7d2ad48c1ebe6d3ae0026f9b7cb4802fd15c7cc02fdf724355102/regex-2026.5.9-cp313-cp313-win32.whl", hash = "sha256:f3844f134e834076677dd369976e9f5068679fcb8e50102fdf6b7ac96a3ec127", size = 266691, upload-time = "2026-05-09T23:13:29.549Z" }, - { url = "https://files.pythonhosted.org/packages/e6/1d/861a93719fb9ee7dbfc3761b3797b7a3e112a5d42c6129459d2d741be9b5/regex-2026.5.9-cp313-cp313-win_amd64.whl", hash = "sha256:3527bb4942d2c14552155406cdedd906567456821848aed1cb4933a391bf5eca", size = 277747, upload-time = "2026-05-09T23:13:31.859Z" }, - { url = "https://files.pythonhosted.org/packages/d9/c6/0a2436ae4da1ba76e51cb98943c6838a9a721faa40ebe2dce07694ae34e3/regex-2026.5.9-cp313-cp313-win_arm64.whl", hash = "sha256:56a33f191f17d8c417f99945ebdc1e691d3af9605d86ec68c7e54a57e3e17af6", size = 270500, upload-time = "2026-05-09T23:13:33.525Z" }, - { url = "https://files.pythonhosted.org/packages/e8/e9/d21346f7b60ed58789371358ed66b09d00f832e1bd7c06e55d9da5679882/regex-2026.5.9-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:01f28d868834624c934b8d2e0aa1c8341337e37831f4a012f18a5afcba4cbaf3", size = 494172, upload-time = "2026-05-09T23:13:35.935Z" }, - { url = "https://files.pythonhosted.org/packages/c4/43/fd1177a2032037c681baecdb3422ee4e1424aec4e4f470ef47793d325274/regex-2026.5.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:48036f6374aaa79eb3b754ec29c61d1c6b1606749d705a13f8854fa2539671f6", size = 293952, upload-time = "2026-05-09T23:13:38.307Z" }, - { url = "https://files.pythonhosted.org/packages/f2/7d/9fbf919768368d3f8a4f6c692cf2aa61e482b2b81ec6a298ace4cbf02480/regex-2026.5.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b96350aa424e79d4fd6b567b344dcbe2b2d6bfc48dfe7717587e1fa6d43da6ff", size = 292314, upload-time = "2026-05-09T23:13:40.353Z" }, - { url = "https://files.pythonhosted.org/packages/e2/6c/e41bfeecb589716843e7c4df09ba46ff2a42961457afece19059d85caeef/regex-2026.5.9-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f3af7a4903c5c04a11a196a5aa75cdd7dd3f8508132f9fb3259d9f5908e3b88", size = 811681, upload-time = "2026-05-09T23:13:42.543Z" }, - { url = "https://files.pythonhosted.org/packages/87/83/a5c1c525fba0aa656e88ad0face0b1829788ef4c2fb6b26df58aa1151b84/regex-2026.5.9-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7e87577720152d2caae19fe2baaf1f8d5ca12091e9e229f03915c37d1e4b9178", size = 871135, upload-time = "2026-05-09T23:13:44.326Z" }, - { url = "https://files.pythonhosted.org/packages/18/d4/80882e799e440dd878b0979cbebf8fa4d54624a332c83037c7a701649e3f/regex-2026.5.9-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c8b9b9d294cfea3cd19c718ade7cc93492b2c4991abd9a68d0b3477ae6d8e100", size = 917265, upload-time = "2026-05-09T23:13:47.295Z" }, - { url = "https://files.pythonhosted.org/packages/ae/ff/8db60211e2286e396aad7dc7725356c502bff0901ea05bd6cdc2e1a042b9/regex-2026.5.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:728d8bfd28a8845c8b6bc5dc7ce010453d206396786c0765c2740cb65f37791e", size = 816311, upload-time = "2026-05-09T23:13:49.885Z" }, - { url = "https://files.pythonhosted.org/packages/4c/47/742ef579c61730f8d268e5cf1f9ce0e37e2ea041ad0f5644724f2378e463/regex-2026.5.9-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7e30b874d341fac767d7df5a0870540541c2c054b80cfaac116e8d367a8a7ff2", size = 785498, upload-time = "2026-05-09T23:13:52.25Z" }, - { url = "https://files.pythonhosted.org/packages/7f/ab/cb0999802dcb0fb95b1ab005e8d4163d8afdd67efc2cb6b6630ac13f8cb1/regex-2026.5.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fd190e88a895a8901325fad284a3f74ea52b1da8525b76cc811fa9b1edf0ce2b", size = 801348, upload-time = "2026-05-09T23:13:54.127Z" }, - { url = "https://files.pythonhosted.org/packages/7d/62/8ca59a24c55bc34d166eefaf3717bd77772f329fdbf984d86581e0a3571c/regex-2026.5.9-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:8e76e8161ad00694cfce6767d5dea860c6391ac5b83e5c3a39661e696f11fc7e", size = 866493, upload-time = "2026-05-09T23:13:56.067Z" }, - { url = "https://files.pythonhosted.org/packages/8d/3d/30f2ae62cef3278bb5bb821f467277a55fb73f01032cf85997e15e8289a8/regex-2026.5.9-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ddda5340e6c01a293027dd46232fa79eaff1b48058ce7a98f572b6445b088041", size = 772811, upload-time = "2026-05-09T23:13:57.867Z" }, - { url = "https://files.pythonhosted.org/packages/d8/ae/7d2089bcd78ad0c0161bc684339df50032acb438a7bd3305e7ddb1193cec/regex-2026.5.9-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:205109e96b3cf5adf8f4cd62bedde9487feb282b9497a3535451e5a24cd706a0", size = 856584, upload-time = "2026-05-09T23:13:59.679Z" }, - { url = "https://files.pythonhosted.org/packages/a9/29/92ff47f75990131ea4f24ba17819e5a9d141e10819807e09addd73409af6/regex-2026.5.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dfbe4579b9f08036aa7d101d1835437a20783574ac66327e6b29b4018a138081", size = 803453, upload-time = "2026-05-09T23:14:01.978Z" }, - { url = "https://files.pythonhosted.org/packages/04/99/eff29f1037dcab36702c9ee5d6858cf1ce2336ea8ea2987f64245b99ea5e/regex-2026.5.9-cp313-cp313t-win32.whl", hash = "sha256:ed2c9e8068b614c574d8d30e543d617cf5379b0535d46f97ef00e904745a08b5", size = 269951, upload-time = "2026-05-09T23:14:03.661Z" }, - { url = "https://files.pythonhosted.org/packages/0e/9d/8870b8981d27b22cda77bb26a5ac7ebfa9c7d9e0dea195a834a82380e748/regex-2026.5.9-cp313-cp313t-win_amd64.whl", hash = "sha256:b46b0f094dc1d3b90356c85a0bd2c9bafc4a6a190b9d6f8ddd5a033b6e088ed4", size = 281240, upload-time = "2026-05-09T23:14:05.56Z" }, - { url = "https://files.pythonhosted.org/packages/72/b1/3379415e8f135c13ac551353397cc4fe97b4978f3cac73c5fcbcded548b8/regex-2026.5.9-cp313-cp313t-win_arm64.whl", hash = "sha256:872acc074bd29ffc9913ecdfedf6ea77502312ca44a4aa0d3779089c6069d8de", size = 272383, upload-time = "2026-05-09T23:14:07.843Z" }, - { url = "https://files.pythonhosted.org/packages/13/3e/9c3cd292d8808b3645a2ce517e200179b6d0e903f176300bd8b542e14de5/regex-2026.5.9-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:1bd7587a2948b4085195d5a3374eaf4a425dc3e55784c038175355ecf3bbbf8a", size = 490376, upload-time = "2026-05-09T23:14:09.64Z" }, - { url = "https://files.pythonhosted.org/packages/60/70/d43ee8a2ca0a8b68d167f21658b85520ac0574617c7f320367c5047f7556/regex-2026.5.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:dea2e88e1cce4522496cce630e11e67b98b7076620bc4336c3f674bc21a375f4", size = 291964, upload-time = "2026-05-09T23:14:11.424Z" }, - { url = "https://files.pythonhosted.org/packages/21/91/9d50b433828d8e74196904e168a43abf1e6e88b2a15d47ed742456720c37/regex-2026.5.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2099f7e7ff7b6aa3192312650a56e91cc091e49d50b04e4f6f8b6e28b3b27f1c", size = 289682, upload-time = "2026-05-09T23:14:13.123Z" }, - { url = "https://files.pythonhosted.org/packages/3e/d2/b835e3cafbb9d977736912436259ff551d60919f7d7b3d37d46659c63564/regex-2026.5.9-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecd353045824e4477562a2ac718c25799cdaaa41f7aa925a806a8a3e6848a5b9", size = 796996, upload-time = "2026-05-09T23:14:14.923Z" }, - { url = "https://files.pythonhosted.org/packages/2c/a6/9f992d00019166b9de01c546dd4549bc679f2a68df11b877740b0760b7c2/regex-2026.5.9-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:65c8c8c37377794bd5b2f3ebe51919042bf17aec802e23c833d89782ed0c78af", size = 866089, upload-time = "2026-05-09T23:14:17.757Z" }, - { url = "https://files.pythonhosted.org/packages/e0/08/4d32af657e049b19cb62b02e46e38fe1518797bfb2203ee93a510b21b0dc/regex-2026.5.9-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5b73ab8afcf66c622db143d1c6fda4e58e4d537ee4f125229ad47b1ab80f34c0", size = 911530, upload-time = "2026-05-09T23:14:20.353Z" }, - { url = "https://files.pythonhosted.org/packages/d9/27/2af43dd1dc201d1fecefda64a45f4ad0995855b92724f795a777b402ee69/regex-2026.5.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0de5cf193997384ed2ca6f1cd4f78055b255d93d82d5a8cd6ba0d11c10b167e4", size = 800643, upload-time = "2026-05-09T23:14:22.265Z" }, - { url = "https://files.pythonhosted.org/packages/a4/dd/23a249047013b5321d4a60c4d2437462086f601b061776a525e5fba2a59f/regex-2026.5.9-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d641a8c9a61618047796d572a39a79b26167b0411d2c3031937b2fe2d081e2cf", size = 777223, upload-time = "2026-05-09T23:14:24.179Z" }, - { url = "https://files.pythonhosted.org/packages/94/6a/e85ed9538cd19586d0465076a4578a12e093ce776d15f3f8ce92733a8dd6/regex-2026.5.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:24b2355ef5cc9aa5b8f07d17704face1c166fdcc2290fa7bd6e6c925655a8346", size = 785760, upload-time = "2026-05-09T23:14:26.065Z" }, - { url = "https://files.pythonhosted.org/packages/2a/c4/f25473209438638e947c55f9156fd8f236f74169229028cc99116380868e/regex-2026.5.9-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:a24852d3c29ad9e47593593d8a247c44ccc3d0548ef12c822d6ed0810affe676", size = 860891, upload-time = "2026-05-09T23:14:28.17Z" }, - { url = "https://files.pythonhosted.org/packages/f9/f7/f4f86e3c74419c37370e91f150ae0c2ef7d34b2e0e4cdd5da046a02e4022/regex-2026.5.9-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:916714069da19329ef7de197dcbc77bb3104145c7c2c864dbfbe318f46b88b14", size = 765891, upload-time = "2026-05-09T23:14:30.06Z" }, - { url = "https://files.pythonhosted.org/packages/26/70/704d8e13765939146b1cd0ef4e2feb71d7929727d2290f026eed10095955/regex-2026.5.9-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:fa411799ca8da32a8d38d020a88faa5b6f91657d284761352940ecf9f7c3bbdd", size = 851380, upload-time = "2026-05-09T23:14:32.123Z" }, - { url = "https://files.pythonhosted.org/packages/26/29/1a13582a8460038edc38e49f64ceb0dd7c60f5caba77571f4bf6601965d9/regex-2026.5.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1e6da47d679b7010ef27556b6e0f99771b744936db1792a10ceac6547ae1503e", size = 789350, upload-time = "2026-05-09T23:14:34.799Z" }, - { url = "https://files.pythonhosted.org/packages/73/56/3dcafe34fc72e271d62ad9a291801e88a1457bb251c132f15fcc2e5aad1a/regex-2026.5.9-cp314-cp314-win32.whl", hash = "sha256:98bd73080e8756255137e1bd3f3f00295bbc5aa383c0e0f973920e9134d7c4ad", size = 272130, upload-time = "2026-05-09T23:14:36.729Z" }, - { url = "https://files.pythonhosted.org/packages/d0/9c/02eebf0be95efe416c664db7fb8b6b05b7a0b06a7544f2884f2558b0526f/regex-2026.5.9-cp314-cp314-win_amd64.whl", hash = "sha256:ff8d372ac2acdc048d1c19916f27ee61bc5722728458ba6ca5052f2c72d51763", size = 280999, upload-time = "2026-05-09T23:14:39.126Z" }, - { url = "https://files.pythonhosted.org/packages/70/5a/1dd1abee76cb7a846a0bcf42fdc87e5720c3c33c24f3e37814310a513d9f/regex-2026.5.9-cp314-cp314-win_arm64.whl", hash = "sha256:e1d93bf647916292e8edcec150c07ddf3dc50179ccaf770c04a7f9e452155372", size = 273500, upload-time = "2026-05-09T23:14:41.059Z" }, - { url = "https://files.pythonhosted.org/packages/86/c1/c5f619b0057a7965cb78ec559c1d7a45ce8c99a35bea95483d64959a93d9/regex-2026.5.9-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:83d0ee4a57d1c87cb549e195ec300b8f0ec3a82eba66d835e4e2ed8634fe4499", size = 494269, upload-time = "2026-05-09T23:14:42.869Z" }, - { url = "https://files.pythonhosted.org/packages/05/2c/5d01f1aee33de4bbe60c8452945bfc8477ca7c5ae4450f6bfe711036cb36/regex-2026.5.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d3d7eb5c9a7f6df82ed3cfac9beb93882a5cbcb5b8b157b56cb2b3b276574ac1", size = 293954, upload-time = "2026-05-09T23:14:44.822Z" }, - { url = "https://files.pythonhosted.org/packages/7a/fe/e8988b2ae2108c6ef71bd4aa8d87fbe257976dd0810e826cd75f701c68b6/regex-2026.5.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:075160bf16658e16d35233300b8453aac25de4cbea808d22348b6979668e924d", size = 292405, upload-time = "2026-05-09T23:14:47.211Z" }, - { url = "https://files.pythonhosted.org/packages/79/34/d2b0937faa7859263f7f0a3c6b103a1296306be6952dc173d0154e9a2f49/regex-2026.5.9-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45375819235558a4ff1c4971dc32881f022613abdb180128f5cb4768c1765a1c", size = 811855, upload-time = "2026-05-09T23:14:49.21Z" }, - { url = "https://files.pythonhosted.org/packages/80/fe/daf53a47457a8486db66c66c01ceb9c2303eecee3f87197f1e77eb1a736d/regex-2026.5.9-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ead4b163ac30a29574510cd4b3e2e985ac5290c05fc7095557d6a5f403fc31b5", size = 871189, upload-time = "2026-05-09T23:14:51.555Z" }, - { url = "https://files.pythonhosted.org/packages/1c/75/058fc4470cbfbf57d800aff1a0022b929a3f9fa553ee10a0cdf2070eb31f/regex-2026.5.9-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8c6e4218fbdfbcd4f6c19efca40930d24a621bf4b48cb76bc6640543bd28ef20", size = 917485, upload-time = "2026-05-09T23:14:53.633Z" }, - { url = "https://files.pythonhosted.org/packages/88/e7/179cfda3a28bc843b5c6cfe7f79f23489c791ed95f151083803660878432/regex-2026.5.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6351571c8a42b505eb555c0dc47d740d0fb66977dc142919eea6f4325b7c56a0", size = 816369, upload-time = "2026-05-09T23:14:56.198Z" }, - { url = "https://files.pythonhosted.org/packages/41/90/6f0cc422071688266d344fca8462d787cba0a2c144acb25721f9a61ec265/regex-2026.5.9-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:002205cafd2a9e78c6290c7d1df277bf3277b3b7a30e0b4bb0dac2e2e3f7cb2d", size = 785869, upload-time = "2026-05-09T23:14:58.602Z" }, - { url = "https://files.pythonhosted.org/packages/02/67/a31f1760f09c27b251ef39e9beb541f462cf977381d067faa764c2c0e393/regex-2026.5.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8abd33fef90b2a9efac5557d6033ca82d1195ed3a15fea5af15ba7b463c6a63b", size = 801427, upload-time = "2026-05-09T23:15:00.642Z" }, - { url = "https://files.pythonhosted.org/packages/e3/c4/1a80654597b6bc1e1ea0494824c31200e8a956abe290afae9b19a166a148/regex-2026.5.9-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:31037c82eccb44b7ea2e9e221d7c01429430e989a1f4b91ea5a855f6017b509a", size = 866482, upload-time = "2026-05-09T23:15:03.384Z" }, - { url = "https://files.pythonhosted.org/packages/d1/11/960724e06482c08466ff5611e242e86f80062949cdf6b4b9cc317b9dd93d/regex-2026.5.9-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:5604dfd046dc37eca90250fc3be938b076c8059fa772ac0ed6f499b0f0fb0415", size = 773022, upload-time = "2026-05-09T23:15:05.625Z" }, - { url = "https://files.pythonhosted.org/packages/50/a8/a9979c3e7918280e93159ebcab5ef1a65116dd4f3bd6091be0eae4a126e8/regex-2026.5.9-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0e1b1b4e496afbb24f4a62aba855ee4f88f25578927697b340702e48c9ee6bc2", size = 856642, upload-time = "2026-05-09T23:15:07.966Z" }, - { url = "https://files.pythonhosted.org/packages/fe/d4/a9b732f2f0072c0ab12227483abb24fffcb9f73f8a2b203df0a6d0434735/regex-2026.5.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:be3372b9df6ddecff6486d37e19095a7b4973137caf5512407a89f4455361f41", size = 803552, upload-time = "2026-05-09T23:15:10.215Z" }, - { url = "https://files.pythonhosted.org/packages/d5/fe/1b3113817447a1d4155e4ac76d2e072f42c0bcba2f43fa8a0e756ea2cd91/regex-2026.5.9-cp314-cp314t-win32.whl", hash = "sha256:3ddd90103f9e5c471c49c7852ecc1fe27c7e45eb99e977aefe7caa4e779f4f58", size = 275746, upload-time = "2026-05-09T23:15:12.609Z" }, - { url = "https://files.pythonhosted.org/packages/92/73/93d42045302636c91f2e5ef588b65b84b01428f28ec77de256b1dfdfbe5c/regex-2026.5.9-cp314-cp314t-win_amd64.whl", hash = "sha256:ca518ed29c46eecba6010b15f1b9a479314d2de409536e71b6a13aa04e3b8a77", size = 285685, upload-time = "2026-05-09T23:15:15.086Z" }, - { url = "https://files.pythonhosted.org/packages/da/80/35b4c33c804a165a7f55289afda3ea9e3eb6d15800341a2d66455c0f1f30/regex-2026.5.9-cp314-cp314t-win_arm64.whl", hash = "sha256:5e41809d2683fcde7d5a8c87a6567ba1fb1ce0de9f31bff578de00a4b2d76daa", size = 275713, upload-time = "2026-05-09T23:15:16.98Z" }, -] - [[package]] name = "requests" version = "2.34.1" @@ -3790,410 +2674,6 @@ wheels = [ { url = "https://files.pythonhosted.org/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 = "rich" -version = "15.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markdown-it-py" }, - { name = "pygments" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" }, -] - -[[package]] -name = "robust-downloader" -version = "0.0.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorlog" }, - { name = "requests" }, - { name = "tqdm" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/63/20/8d28efa080f58fa06f6378875ac482ee511c076369e5293a2e65128cf9a0/robust-downloader-0.0.2.tar.gz", hash = "sha256:08c938b96e317abe6b037e34230a91bda9b5d613f009bca4a47664997c61de90", size = 15785, upload-time = "2023-11-13T03:00:20.637Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/56/a1/779e9d0ebbdc704411ce30915a1105eb01aeaa9e402d7e446613ff8fb121/robust_downloader-0.0.2-py3-none-any.whl", hash = "sha256:8fe08bfb64d714fd1a048a7df6eb7b413eb4e624309a49db2c16fbb80a62869d", size = 15534, upload-time = "2023-11-13T03:00:18.957Z" }, -] - -[[package]] -name = "s3transfer" -version = "0.17.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "botocore" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9b/ec/7c692cde9125b77e84b307354d4fb705f98b8ccad59a036d5957ca75bfc3/s3transfer-0.17.0.tar.gz", hash = "sha256:9edeb6d1c3c2f89d6050348548834ad8289610d886e5bf7b7207728bd43ce33a", size = 155337, upload-time = "2026-04-29T22:07:36.33Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/87/72/c6c32d2b657fa3dad1de340254e14390b1e334ce38268b7ad51abda3c8c2/s3transfer-0.17.0-py3-none-any.whl", hash = "sha256:ce3801712acf4ad3e89fb9990df97b4972e93f4b3b0004d214be5bce12814c20", size = 86811, upload-time = "2026-04-29T22:07:34.966Z" }, -] - -[[package]] -name = "safetensors" -version = "0.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/29/9c/6e74567782559a63bd040a236edca26fd71bc7ba88de2ef35d75df3bca5e/safetensors-0.7.0.tar.gz", hash = "sha256:07663963b67e8bd9f0b8ad15bb9163606cd27cc5a1b96235a50d8369803b96b0", size = 200878, upload-time = "2025-11-19T15:18:43.199Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/47/aef6c06649039accf914afef490268e1067ed82be62bcfa5b7e886ad15e8/safetensors-0.7.0-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:c82f4d474cf725255d9e6acf17252991c3c8aac038d6ef363a4bf8be2f6db517", size = 467781, upload-time = "2025-11-19T15:18:35.84Z" }, - { url = "https://files.pythonhosted.org/packages/e8/00/374c0c068e30cd31f1e1b46b4b5738168ec79e7689ca82ee93ddfea05109/safetensors-0.7.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:94fd4858284736bb67a897a41608b5b0c2496c9bdb3bf2af1fa3409127f20d57", size = 447058, upload-time = "2025-11-19T15:18:34.416Z" }, - { url = "https://files.pythonhosted.org/packages/f1/06/578ffed52c2296f93d7fd2d844cabfa92be51a587c38c8afbb8ae449ca89/safetensors-0.7.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e07d91d0c92a31200f25351f4acb2bc6aff7f48094e13ebb1d0fb995b54b6542", size = 491748, upload-time = "2025-11-19T15:18:09.79Z" }, - { url = "https://files.pythonhosted.org/packages/ae/33/1debbbb70e4791dde185edb9413d1fe01619255abb64b300157d7f15dddd/safetensors-0.7.0-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8469155f4cb518bafb4acf4865e8bb9d6804110d2d9bdcaa78564b9fd841e104", size = 503881, upload-time = "2025-11-19T15:18:16.145Z" }, - { url = "https://files.pythonhosted.org/packages/8e/1c/40c2ca924d60792c3be509833df711b553c60effbd91da6f5284a83f7122/safetensors-0.7.0-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:54bef08bf00a2bff599982f6b08e8770e09cc012d7bba00783fc7ea38f1fb37d", size = 623463, upload-time = "2025-11-19T15:18:21.11Z" }, - { url = "https://files.pythonhosted.org/packages/9b/3a/13784a9364bd43b0d61eef4bea2845039bc2030458b16594a1bd787ae26e/safetensors-0.7.0-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:42cb091236206bb2016d245c377ed383aa7f78691748f3bb6ee1bfa51ae2ce6a", size = 532855, upload-time = "2025-11-19T15:18:25.719Z" }, - { url = "https://files.pythonhosted.org/packages/a0/60/429e9b1cb3fc651937727befe258ea24122d9663e4d5709a48c9cbfceecb/safetensors-0.7.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac7252938f0696ddea46f5e855dd3138444e82236e3be475f54929f0c510d48", size = 507152, upload-time = "2025-11-19T15:18:33.023Z" }, - { url = "https://files.pythonhosted.org/packages/3c/a8/4b45e4e059270d17af60359713ffd83f97900d45a6afa73aaa0d737d48b6/safetensors-0.7.0-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1d060c70284127fa805085d8f10fbd0962792aed71879d00864acda69dbab981", size = 541856, upload-time = "2025-11-19T15:18:31.075Z" }, - { url = "https://files.pythonhosted.org/packages/06/87/d26d8407c44175d8ae164a95b5a62707fcc445f3c0c56108e37d98070a3d/safetensors-0.7.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:cdab83a366799fa730f90a4ebb563e494f28e9e92c4819e556152ad55e43591b", size = 674060, upload-time = "2025-11-19T15:18:37.211Z" }, - { url = "https://files.pythonhosted.org/packages/11/f5/57644a2ff08dc6325816ba7217e5095f17269dada2554b658442c66aed51/safetensors-0.7.0-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:672132907fcad9f2aedcb705b2d7b3b93354a2aec1b2f706c4db852abe338f85", size = 771715, upload-time = "2025-11-19T15:18:38.689Z" }, - { url = "https://files.pythonhosted.org/packages/86/31/17883e13a814bd278ae6e266b13282a01049b0c81341da7fd0e3e71a80a3/safetensors-0.7.0-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:5d72abdb8a4d56d4020713724ba81dac065fedb7f3667151c4a637f1d3fb26c0", size = 714377, upload-time = "2025-11-19T15:18:40.162Z" }, - { url = "https://files.pythonhosted.org/packages/4a/d8/0c8a7dc9b41dcac53c4cbf9df2b9c83e0e0097203de8b37a712b345c0be5/safetensors-0.7.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b0f6d66c1c538d5a94a73aa9ddca8ccc4227e6c9ff555322ea40bdd142391dd4", size = 677368, upload-time = "2025-11-19T15:18:41.627Z" }, - { url = "https://files.pythonhosted.org/packages/05/e5/cb4b713c8a93469e3c5be7c3f8d77d307e65fe89673e731f5c2bfd0a9237/safetensors-0.7.0-cp38-abi3-win32.whl", hash = "sha256:c74af94bf3ac15ac4d0f2a7c7b4663a15f8c2ab15ed0fc7531ca61d0835eccba", size = 326423, upload-time = "2025-11-19T15:18:45.74Z" }, - { url = "https://files.pythonhosted.org/packages/5d/e6/ec8471c8072382cb91233ba7267fd931219753bb43814cbc71757bfd4dab/safetensors-0.7.0-cp38-abi3-win_amd64.whl", hash = "sha256:d1239932053f56f3456f32eb9625590cc7582e905021f94636202a864d470755", size = 341380, upload-time = "2025-11-19T15:18:44.427Z" }, - { url = "https://files.pythonhosted.org/packages/a7/6a/4d08d89a6fcbe905c5ae68b8b34f0791850882fc19782d0d02c65abbdf3b/safetensors-0.7.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4729811a6640d019a4b7ba8638ee2fd21fa5ca8c7e7bdf0fed62068fcaac737", size = 492430, upload-time = "2025-11-19T15:18:11.884Z" }, - { url = "https://files.pythonhosted.org/packages/dd/29/59ed8152b30f72c42d00d241e58eaca558ae9dbfa5695206e2e0f54c7063/safetensors-0.7.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12f49080303fa6bb424b362149a12949dfbbf1e06811a88f2307276b0c131afd", size = 503977, upload-time = "2025-11-19T15:18:17.523Z" }, - { url = "https://files.pythonhosted.org/packages/d3/0b/4811bfec67fa260e791369b16dab105e4bae82686120554cc484064e22b4/safetensors-0.7.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0071bffba4150c2f46cae1432d31995d77acfd9f8db598b5d1a2ce67e8440ad2", size = 623890, upload-time = "2025-11-19T15:18:22.666Z" }, - { url = "https://files.pythonhosted.org/packages/58/5b/632a58724221ef03d78ab65062e82a1010e1bef8e8e0b9d7c6d7b8044841/safetensors-0.7.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:473b32699f4200e69801bf5abf93f1a4ecd432a70984df164fc22ccf39c4a6f3", size = 531885, upload-time = "2025-11-19T15:18:27.146Z" }, -] - -[[package]] -name = "scikit-learn" -version = "1.7.2" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.11' and sys_platform == 'win32'", - "python_full_version < '3.11' and sys_platform == 'linux'", - "python_full_version < '3.11' and sys_platform == 'darwin'", - "python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux' and sys_platform != 'win32'", -] -dependencies = [ - { name = "joblib", marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "threadpoolctl", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/98/c2/a7855e41c9d285dfe86dc50b250978105dce513d6e459ea66a6aeb0e1e0c/scikit_learn-1.7.2.tar.gz", hash = "sha256:20e9e49ecd130598f1ca38a1d85090e1a600147b9c02fa6f15d69cb53d968fda", size = 7193136, upload-time = "2025-09-09T08:21:29.075Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ba/3e/daed796fd69cce768b8788401cc464ea90b306fb196ae1ffed0b98182859/scikit_learn-1.7.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b33579c10a3081d076ab403df4a4190da4f4432d443521674637677dc91e61f", size = 9336221, upload-time = "2025-09-09T08:20:19.328Z" }, - { url = "https://files.pythonhosted.org/packages/1c/ce/af9d99533b24c55ff4e18d9b7b4d9919bbc6cd8f22fe7a7be01519a347d5/scikit_learn-1.7.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:36749fb62b3d961b1ce4fedf08fa57a1986cd409eff2d783bca5d4b9b5fce51c", size = 8653834, upload-time = "2025-09-09T08:20:22.073Z" }, - { url = "https://files.pythonhosted.org/packages/58/0e/8c2a03d518fb6bd0b6b0d4b114c63d5f1db01ff0f9925d8eb10960d01c01/scikit_learn-1.7.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7a58814265dfc52b3295b1900cfb5701589d30a8bb026c7540f1e9d3499d5ec8", size = 9660938, upload-time = "2025-09-09T08:20:24.327Z" }, - { url = "https://files.pythonhosted.org/packages/2b/75/4311605069b5d220e7cf5adabb38535bd96f0079313cdbb04b291479b22a/scikit_learn-1.7.2-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a847fea807e278f821a0406ca01e387f97653e284ecbd9750e3ee7c90347f18", size = 9477818, upload-time = "2025-09-09T08:20:26.845Z" }, - { url = "https://files.pythonhosted.org/packages/7f/9b/87961813c34adbca21a6b3f6b2bea344c43b30217a6d24cc437c6147f3e8/scikit_learn-1.7.2-cp310-cp310-win_amd64.whl", hash = "sha256:ca250e6836d10e6f402436d6463d6c0e4d8e0234cfb6a9a47835bd392b852ce5", size = 8886969, upload-time = "2025-09-09T08:20:29.329Z" }, - { url = "https://files.pythonhosted.org/packages/43/83/564e141eef908a5863a54da8ca342a137f45a0bfb71d1d79704c9894c9d1/scikit_learn-1.7.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7509693451651cd7361d30ce4e86a1347493554f172b1c72a39300fa2aea79e", size = 9331967, upload-time = "2025-09-09T08:20:32.421Z" }, - { url = "https://files.pythonhosted.org/packages/18/d6/ba863a4171ac9d7314c4d3fc251f015704a2caeee41ced89f321c049ed83/scikit_learn-1.7.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:0486c8f827c2e7b64837c731c8feff72c0bd2b998067a8a9cbc10643c31f0fe1", size = 8648645, upload-time = "2025-09-09T08:20:34.436Z" }, - { url = "https://files.pythonhosted.org/packages/ef/0e/97dbca66347b8cf0ea8b529e6bb9367e337ba2e8be0ef5c1a545232abfde/scikit_learn-1.7.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:89877e19a80c7b11a2891a27c21c4894fb18e2c2e077815bcade10d34287b20d", size = 9715424, upload-time = "2025-09-09T08:20:36.776Z" }, - { url = "https://files.pythonhosted.org/packages/f7/32/1f3b22e3207e1d2c883a7e09abb956362e7d1bd2f14458c7de258a26ac15/scikit_learn-1.7.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8da8bf89d4d79aaec192d2bda62f9b56ae4e5b4ef93b6a56b5de4977e375c1f1", size = 9509234, upload-time = "2025-09-09T08:20:38.957Z" }, - { url = "https://files.pythonhosted.org/packages/9f/71/34ddbd21f1da67c7a768146968b4d0220ee6831e4bcbad3e03dd3eae88b6/scikit_learn-1.7.2-cp311-cp311-win_amd64.whl", hash = "sha256:9b7ed8d58725030568523e937c43e56bc01cadb478fc43c042a9aca1dacb3ba1", size = 8894244, upload-time = "2025-09-09T08:20:41.166Z" }, - { url = "https://files.pythonhosted.org/packages/a7/aa/3996e2196075689afb9fce0410ebdb4a09099d7964d061d7213700204409/scikit_learn-1.7.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8d91a97fa2b706943822398ab943cde71858a50245e31bc71dba62aab1d60a96", size = 9259818, upload-time = "2025-09-09T08:20:43.19Z" }, - { url = "https://files.pythonhosted.org/packages/43/5d/779320063e88af9c4a7c2cf463ff11c21ac9c8bd730c4a294b0000b666c9/scikit_learn-1.7.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:acbc0f5fd2edd3432a22c69bed78e837c70cf896cd7993d71d51ba6708507476", size = 8636997, upload-time = "2025-09-09T08:20:45.468Z" }, - { url = "https://files.pythonhosted.org/packages/5c/d0/0c577d9325b05594fdd33aa970bf53fb673f051a45496842caee13cfd7fe/scikit_learn-1.7.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e5bf3d930aee75a65478df91ac1225ff89cd28e9ac7bd1196853a9229b6adb0b", size = 9478381, upload-time = "2025-09-09T08:20:47.982Z" }, - { url = "https://files.pythonhosted.org/packages/82/70/8bf44b933837ba8494ca0fc9a9ab60f1c13b062ad0197f60a56e2fc4c43e/scikit_learn-1.7.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4d6e9deed1a47aca9fe2f267ab8e8fe82ee20b4526b2c0cd9e135cea10feb44", size = 9300296, upload-time = "2025-09-09T08:20:50.366Z" }, - { url = "https://files.pythonhosted.org/packages/c6/99/ed35197a158f1fdc2fe7c3680e9c70d0128f662e1fee4ed495f4b5e13db0/scikit_learn-1.7.2-cp312-cp312-win_amd64.whl", hash = "sha256:6088aa475f0785e01bcf8529f55280a3d7d298679f50c0bb70a2364a82d0b290", size = 8731256, upload-time = "2025-09-09T08:20:52.627Z" }, - { url = "https://files.pythonhosted.org/packages/ae/93/a3038cb0293037fd335f77f31fe053b89c72f17b1c8908c576c29d953e84/scikit_learn-1.7.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0b7dacaa05e5d76759fb071558a8b5130f4845166d88654a0f9bdf3eb57851b7", size = 9212382, upload-time = "2025-09-09T08:20:54.731Z" }, - { url = "https://files.pythonhosted.org/packages/40/dd/9a88879b0c1104259136146e4742026b52df8540c39fec21a6383f8292c7/scikit_learn-1.7.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:abebbd61ad9e1deed54cca45caea8ad5f79e1b93173dece40bb8e0c658dbe6fe", size = 8592042, upload-time = "2025-09-09T08:20:57.313Z" }, - { url = "https://files.pythonhosted.org/packages/46/af/c5e286471b7d10871b811b72ae794ac5fe2989c0a2df07f0ec723030f5f5/scikit_learn-1.7.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:502c18e39849c0ea1a5d681af1dbcf15f6cce601aebb657aabbfe84133c1907f", size = 9434180, upload-time = "2025-09-09T08:20:59.671Z" }, - { url = "https://files.pythonhosted.org/packages/f1/fd/df59faa53312d585023b2da27e866524ffb8faf87a68516c23896c718320/scikit_learn-1.7.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a4c328a71785382fe3fe676a9ecf2c86189249beff90bf85e22bdb7efaf9ae0", size = 9283660, upload-time = "2025-09-09T08:21:01.71Z" }, - { url = "https://files.pythonhosted.org/packages/a7/c7/03000262759d7b6f38c836ff9d512f438a70d8a8ddae68ee80de72dcfb63/scikit_learn-1.7.2-cp313-cp313-win_amd64.whl", hash = "sha256:63a9afd6f7b229aad94618c01c252ce9e6fa97918c5ca19c9a17a087d819440c", size = 8702057, upload-time = "2025-09-09T08:21:04.234Z" }, - { url = "https://files.pythonhosted.org/packages/55/87/ef5eb1f267084532c8e4aef98a28b6ffe7425acbfd64b5e2f2e066bc29b3/scikit_learn-1.7.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9acb6c5e867447b4e1390930e3944a005e2cb115922e693c08a323421a6966e8", size = 9558731, upload-time = "2025-09-09T08:21:06.381Z" }, - { url = "https://files.pythonhosted.org/packages/93/f8/6c1e3fc14b10118068d7938878a9f3f4e6d7b74a8ddb1e5bed65159ccda8/scikit_learn-1.7.2-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:2a41e2a0ef45063e654152ec9d8bcfc39f7afce35b08902bfe290c2498a67a6a", size = 9038852, upload-time = "2025-09-09T08:21:08.628Z" }, - { url = "https://files.pythonhosted.org/packages/83/87/066cafc896ee540c34becf95d30375fe5cbe93c3b75a0ee9aa852cd60021/scikit_learn-1.7.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98335fb98509b73385b3ab2bd0639b1f610541d3988ee675c670371d6a87aa7c", size = 9527094, upload-time = "2025-09-09T08:21:11.486Z" }, - { url = "https://files.pythonhosted.org/packages/9c/2b/4903e1ccafa1f6453b1ab78413938c8800633988c838aa0be386cbb33072/scikit_learn-1.7.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:191e5550980d45449126e23ed1d5e9e24b2c68329ee1f691a3987476e115e09c", size = 9367436, upload-time = "2025-09-09T08:21:13.602Z" }, - { url = "https://files.pythonhosted.org/packages/b5/aa/8444be3cfb10451617ff9d177b3c190288f4563e6c50ff02728be67ad094/scikit_learn-1.7.2-cp313-cp313t-win_amd64.whl", hash = "sha256:57dc4deb1d3762c75d685507fbd0bc17160144b2f2ba4ccea5dc285ab0d0e973", size = 9275749, upload-time = "2025-09-09T08:21:15.96Z" }, - { url = "https://files.pythonhosted.org/packages/d9/82/dee5acf66837852e8e68df6d8d3a6cb22d3df997b733b032f513d95205b7/scikit_learn-1.7.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fa8f63940e29c82d1e67a45d5297bdebbcb585f5a5a50c4914cc2e852ab77f33", size = 9208906, upload-time = "2025-09-09T08:21:18.557Z" }, - { url = "https://files.pythonhosted.org/packages/3c/30/9029e54e17b87cb7d50d51a5926429c683d5b4c1732f0507a6c3bed9bf65/scikit_learn-1.7.2-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:f95dc55b7902b91331fa4e5845dd5bde0580c9cd9612b1b2791b7e80c3d32615", size = 8627836, upload-time = "2025-09-09T08:21:20.695Z" }, - { url = "https://files.pythonhosted.org/packages/60/18/4a52c635c71b536879f4b971c2cedf32c35ee78f48367885ed8025d1f7ee/scikit_learn-1.7.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9656e4a53e54578ad10a434dc1f993330568cfee176dff07112b8785fb413106", size = 9426236, upload-time = "2025-09-09T08:21:22.645Z" }, - { url = "https://files.pythonhosted.org/packages/99/7e/290362f6ab582128c53445458a5befd471ed1ea37953d5bcf80604619250/scikit_learn-1.7.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96dc05a854add0e50d3f47a1ef21a10a595016da5b007c7d9cd9d0bffd1fcc61", size = 9312593, upload-time = "2025-09-09T08:21:24.65Z" }, - { url = "https://files.pythonhosted.org/packages/8e/87/24f541b6d62b1794939ae6422f8023703bbf6900378b2b34e0b4384dfefd/scikit_learn-1.7.2-cp314-cp314-win_amd64.whl", hash = "sha256:bb24510ed3f9f61476181e4db51ce801e2ba37541def12dc9333b946fc7a9cf8", size = 8820007, upload-time = "2025-09-09T08:21:26.713Z" }, -] - -[[package]] -name = "scikit-learn" -version = "1.8.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform == 'linux'", - "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'linux'", - "python_full_version >= '3.14' and sys_platform == 'darwin'", - "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'darwin'", - "python_full_version >= '3.14' and sys_platform != 'darwin' and sys_platform != 'linux' and sys_platform != 'win32'", - "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'darwin' and sys_platform != 'linux' and sys_platform != 'win32'", -] -dependencies = [ - { name = "joblib", marker = "python_full_version >= '3.11'" }, - { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "threadpoolctl", marker = "python_full_version >= '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0e/d4/40988bf3b8e34feec1d0e6a051446b1f66225f8529b9309becaeef62b6c4/scikit_learn-1.8.0.tar.gz", hash = "sha256:9bccbb3b40e3de10351f8f5068e105d0f4083b1a65fa07b6634fbc401a6287fd", size = 7335585, upload-time = "2025-12-10T07:08:53.618Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c9/92/53ea2181da8ac6bf27170191028aee7251f8f841f8d3edbfdcaf2008fde9/scikit_learn-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:146b4d36f800c013d267b29168813f7a03a43ecd2895d04861f1240b564421da", size = 8595835, upload-time = "2025-12-10T07:07:39.385Z" }, - { url = "https://files.pythonhosted.org/packages/01/18/d154dc1638803adf987910cdd07097d9c526663a55666a97c124d09fb96a/scikit_learn-1.8.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:f984ca4b14914e6b4094c5d52a32ea16b49832c03bd17a110f004db3c223e8e1", size = 8080381, upload-time = "2025-12-10T07:07:41.93Z" }, - { url = "https://files.pythonhosted.org/packages/8a/44/226142fcb7b7101e64fdee5f49dbe6288d4c7af8abf593237b70fca080a4/scikit_learn-1.8.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e30adb87f0cc81c7690a84f7932dd66be5bac57cfe16b91cb9151683a4a2d3b", size = 8799632, upload-time = "2025-12-10T07:07:43.899Z" }, - { url = "https://files.pythonhosted.org/packages/36/4d/4a67f30778a45d542bbea5db2dbfa1e9e100bf9ba64aefe34215ba9f11f6/scikit_learn-1.8.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ada8121bcb4dac28d930febc791a69f7cb1673c8495e5eee274190b73a4559c1", size = 9103788, upload-time = "2025-12-10T07:07:45.982Z" }, - { url = "https://files.pythonhosted.org/packages/89/3c/45c352094cfa60050bcbb967b1faf246b22e93cb459f2f907b600f2ceda5/scikit_learn-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:c57b1b610bd1f40ba43970e11ce62821c2e6569e4d74023db19c6b26f246cb3b", size = 8081706, upload-time = "2025-12-10T07:07:48.111Z" }, - { url = "https://files.pythonhosted.org/packages/3d/46/5416595bb395757f754feb20c3d776553a386b661658fb21b7c814e89efe/scikit_learn-1.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:2838551e011a64e3053ad7618dda9310175f7515f1742fa2d756f7c874c05961", size = 7688451, upload-time = "2025-12-10T07:07:49.873Z" }, - { url = "https://files.pythonhosted.org/packages/90/74/e6a7cc4b820e95cc38cf36cd74d5aa2b42e8ffc2d21fe5a9a9c45c1c7630/scikit_learn-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5fb63362b5a7ddab88e52b6dbb47dac3fd7dafeee740dc6c8d8a446ddedade8e", size = 8548242, upload-time = "2025-12-10T07:07:51.568Z" }, - { url = "https://files.pythonhosted.org/packages/49/d8/9be608c6024d021041c7f0b3928d4749a706f4e2c3832bbede4fb4f58c95/scikit_learn-1.8.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:5025ce924beccb28298246e589c691fe1b8c1c96507e6d27d12c5fadd85bfd76", size = 8079075, upload-time = "2025-12-10T07:07:53.697Z" }, - { url = "https://files.pythonhosted.org/packages/dd/47/f187b4636ff80cc63f21cd40b7b2d177134acaa10f6bb73746130ee8c2e5/scikit_learn-1.8.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4496bb2cf7a43ce1a2d7524a79e40bc5da45cf598dbf9545b7e8316ccba47bb4", size = 8660492, upload-time = "2025-12-10T07:07:55.574Z" }, - { url = "https://files.pythonhosted.org/packages/97/74/b7a304feb2b49df9fafa9382d4d09061a96ee9a9449a7cbea7988dda0828/scikit_learn-1.8.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0bcfe4d0d14aec44921545fd2af2338c7471de9cb701f1da4c9d85906ab847a", size = 8931904, upload-time = "2025-12-10T07:07:57.666Z" }, - { url = "https://files.pythonhosted.org/packages/9f/c4/0ab22726a04ede56f689476b760f98f8f46607caecff993017ac1b64aa5d/scikit_learn-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:35c007dedb2ffe38fe3ee7d201ebac4a2deccd2408e8621d53067733e3c74809", size = 8019359, upload-time = "2025-12-10T07:07:59.838Z" }, - { url = "https://files.pythonhosted.org/packages/24/90/344a67811cfd561d7335c1b96ca21455e7e472d281c3c279c4d3f2300236/scikit_learn-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:8c497fff237d7b4e07e9ef1a640887fa4fb765647f86fbe00f969ff6280ce2bb", size = 7641898, upload-time = "2025-12-10T07:08:01.36Z" }, - { url = "https://files.pythonhosted.org/packages/03/aa/e22e0768512ce9255eba34775be2e85c2048da73da1193e841707f8f039c/scikit_learn-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0d6ae97234d5d7079dc0040990a6f7aeb97cb7fa7e8945f1999a429b23569e0a", size = 8513770, upload-time = "2025-12-10T07:08:03.251Z" }, - { url = "https://files.pythonhosted.org/packages/58/37/31b83b2594105f61a381fc74ca19e8780ee923be2d496fcd8d2e1147bd99/scikit_learn-1.8.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:edec98c5e7c128328124a029bceb09eda2d526997780fef8d65e9a69eead963e", size = 8044458, upload-time = "2025-12-10T07:08:05.336Z" }, - { url = "https://files.pythonhosted.org/packages/2d/5a/3f1caed8765f33eabb723596666da4ebbf43d11e96550fb18bdec42b467b/scikit_learn-1.8.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:74b66d8689d52ed04c271e1329f0c61635bcaf5b926db9b12d58914cdc01fe57", size = 8610341, upload-time = "2025-12-10T07:08:07.732Z" }, - { url = "https://files.pythonhosted.org/packages/38/cf/06896db3f71c75902a8e9943b444a56e727418f6b4b4a90c98c934f51ed4/scikit_learn-1.8.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8fdf95767f989b0cfedb85f7ed8ca215d4be728031f56ff5a519ee1e3276dc2e", size = 8900022, upload-time = "2025-12-10T07:08:09.862Z" }, - { url = "https://files.pythonhosted.org/packages/1c/f9/9b7563caf3ec8873e17a31401858efab6b39a882daf6c1bfa88879c0aa11/scikit_learn-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:2de443b9373b3b615aec1bb57f9baa6bb3a9bd093f1269ba95c17d870422b271", size = 7989409, upload-time = "2025-12-10T07:08:12.028Z" }, - { url = "https://files.pythonhosted.org/packages/49/bd/1f4001503650e72c4f6009ac0c4413cb17d2d601cef6f71c0453da2732fc/scikit_learn-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:eddde82a035681427cbedded4e6eff5e57fa59216c2e3e90b10b19ab1d0a65c3", size = 7619760, upload-time = "2025-12-10T07:08:13.688Z" }, - { url = "https://files.pythonhosted.org/packages/d2/7d/a630359fc9dcc95496588c8d8e3245cc8fd81980251079bc09c70d41d951/scikit_learn-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7cc267b6108f0a1499a734167282c00c4ebf61328566b55ef262d48e9849c735", size = 8826045, upload-time = "2025-12-10T07:08:15.215Z" }, - { url = "https://files.pythonhosted.org/packages/cc/56/a0c86f6930cfcd1c7054a2bc417e26960bb88d32444fe7f71d5c2cfae891/scikit_learn-1.8.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:fe1c011a640a9f0791146011dfd3c7d9669785f9fed2b2a5f9e207536cf5c2fd", size = 8420324, upload-time = "2025-12-10T07:08:17.561Z" }, - { url = "https://files.pythonhosted.org/packages/46/1e/05962ea1cebc1cf3876667ecb14c283ef755bf409993c5946ade3b77e303/scikit_learn-1.8.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72358cce49465d140cc4e7792015bb1f0296a9742d5622c67e31399b75468b9e", size = 8680651, upload-time = "2025-12-10T07:08:19.952Z" }, - { url = "https://files.pythonhosted.org/packages/fe/56/a85473cd75f200c9759e3a5f0bcab2d116c92a8a02ee08ccd73b870f8bb4/scikit_learn-1.8.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:80832434a6cc114f5219211eec13dcbc16c2bac0e31ef64c6d346cde3cf054cb", size = 8925045, upload-time = "2025-12-10T07:08:22.11Z" }, - { url = "https://files.pythonhosted.org/packages/cc/b7/64d8cfa896c64435ae57f4917a548d7ac7a44762ff9802f75a79b77cb633/scikit_learn-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ee787491dbfe082d9c3013f01f5991658b0f38aa8177e4cd4bf434c58f551702", size = 8507994, upload-time = "2025-12-10T07:08:23.943Z" }, - { url = "https://files.pythonhosted.org/packages/5e/37/e192ea709551799379958b4c4771ec507347027bb7c942662c7fbeba31cb/scikit_learn-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf97c10a3f5a7543f9b88cbf488d33d175e9146115a451ae34568597ba33dcde", size = 7869518, upload-time = "2025-12-10T07:08:25.71Z" }, - { url = "https://files.pythonhosted.org/packages/24/05/1af2c186174cc92dcab2233f327336058c077d38f6fe2aceb08e6ab4d509/scikit_learn-1.8.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c22a2da7a198c28dd1a6e1136f19c830beab7fdca5b3e5c8bba8394f8a5c45b3", size = 8528667, upload-time = "2025-12-10T07:08:27.541Z" }, - { url = "https://files.pythonhosted.org/packages/a8/25/01c0af38fe969473fb292bba9dc2b8f9b451f3112ff242c647fee3d0dfe7/scikit_learn-1.8.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:6b595b07a03069a2b1740dc08c2299993850ea81cce4fe19b2421e0c970de6b7", size = 8066524, upload-time = "2025-12-10T07:08:29.822Z" }, - { url = "https://files.pythonhosted.org/packages/be/ce/a0623350aa0b68647333940ee46fe45086c6060ec604874e38e9ab7d8e6c/scikit_learn-1.8.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:29ffc74089f3d5e87dfca4c2c8450f88bdc61b0fc6ed5d267f3988f19a1309f6", size = 8657133, upload-time = "2025-12-10T07:08:31.865Z" }, - { url = "https://files.pythonhosted.org/packages/b8/cb/861b41341d6f1245e6ca80b1c1a8c4dfce43255b03df034429089ca2a2c5/scikit_learn-1.8.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fb65db5d7531bccf3a4f6bec3462223bea71384e2cda41da0f10b7c292b9e7c4", size = 8923223, upload-time = "2025-12-10T07:08:34.166Z" }, - { url = "https://files.pythonhosted.org/packages/76/18/a8def8f91b18cd1ba6e05dbe02540168cb24d47e8dcf69e8d00b7da42a08/scikit_learn-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:56079a99c20d230e873ea40753102102734c5953366972a71d5cb39a32bc40c6", size = 8096518, upload-time = "2025-12-10T07:08:36.339Z" }, - { url = "https://files.pythonhosted.org/packages/d1/77/482076a678458307f0deb44e29891d6022617b2a64c840c725495bee343f/scikit_learn-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:3bad7565bc9cf37ce19a7c0d107742b320c1285df7aab1a6e2d28780df167242", size = 7754546, upload-time = "2025-12-10T07:08:38.128Z" }, - { url = "https://files.pythonhosted.org/packages/2d/d1/ef294ca754826daa043b2a104e59960abfab4cf653891037d19dd5b6f3cf/scikit_learn-1.8.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:4511be56637e46c25721e83d1a9cea9614e7badc7040c4d573d75fbe257d6fd7", size = 8848305, upload-time = "2025-12-10T07:08:41.013Z" }, - { url = "https://files.pythonhosted.org/packages/5b/e2/b1f8b05138ee813b8e1a4149f2f0d289547e60851fd1bb268886915adbda/scikit_learn-1.8.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:a69525355a641bf8ef136a7fa447672fb54fe8d60cab5538d9eb7c6438543fb9", size = 8432257, upload-time = "2025-12-10T07:08:42.873Z" }, - { url = "https://files.pythonhosted.org/packages/26/11/c32b2138a85dcb0c99f6afd13a70a951bfdff8a6ab42d8160522542fb647/scikit_learn-1.8.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c2656924ec73e5939c76ac4c8b026fc203b83d8900362eb2599d8aee80e4880f", size = 8678673, upload-time = "2025-12-10T07:08:45.362Z" }, - { url = "https://files.pythonhosted.org/packages/c7/57/51f2384575bdec454f4fe4e7a919d696c9ebce914590abf3e52d47607ab8/scikit_learn-1.8.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15fc3b5d19cc2be65404786857f2e13c70c83dd4782676dd6814e3b89dc8f5b9", size = 8922467, upload-time = "2025-12-10T07:08:47.408Z" }, - { url = "https://files.pythonhosted.org/packages/35/4d/748c9e2872637a57981a04adc038dacaa16ba8ca887b23e34953f0b3f742/scikit_learn-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:00d6f1d66fbcf4eba6e356e1420d33cc06c70a45bb1363cd6f6a8e4ebbbdece2", size = 8774395, upload-time = "2025-12-10T07:08:49.337Z" }, - { url = "https://files.pythonhosted.org/packages/60/22/d7b2ebe4704a5e50790ba089d5c2ae308ab6bb852719e6c3bd4f04c3a363/scikit_learn-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:f28dd15c6bb0b66ba09728cf09fd8736c304be29409bd8445a080c1280619e8c", size = 8002647, upload-time = "2025-12-10T07:08:51.601Z" }, -] - -[[package]] -name = "scipy" -version = "1.15.3" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.11' and sys_platform == 'win32'", - "python_full_version < '3.11' and sys_platform == 'linux'", - "python_full_version < '3.11' and sys_platform == 'darwin'", - "python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux' and sys_platform != 'win32'", -] -dependencies = [ - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0f/37/6964b830433e654ec7485e45a00fc9a27cf868d622838f6b6d9c5ec0d532/scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf", size = 59419214, upload-time = "2025-05-08T16:13:05.955Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/78/2f/4966032c5f8cc7e6a60f1b2e0ad686293b9474b65246b0c642e3ef3badd0/scipy-1.15.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:a345928c86d535060c9c2b25e71e87c39ab2f22fc96e9636bd74d1dbf9de448c", size = 38702770, upload-time = "2025-05-08T16:04:20.849Z" }, - { url = "https://files.pythonhosted.org/packages/a0/6e/0c3bf90fae0e910c274db43304ebe25a6b391327f3f10b5dcc638c090795/scipy-1.15.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:ad3432cb0f9ed87477a8d97f03b763fd1d57709f1bbde3c9369b1dff5503b253", size = 30094511, upload-time = "2025-05-08T16:04:27.103Z" }, - { url = "https://files.pythonhosted.org/packages/ea/b1/4deb37252311c1acff7f101f6453f0440794f51b6eacb1aad4459a134081/scipy-1.15.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:aef683a9ae6eb00728a542b796f52a5477b78252edede72b8327a886ab63293f", size = 22368151, upload-time = "2025-05-08T16:04:31.731Z" }, - { url = "https://files.pythonhosted.org/packages/38/7d/f457626e3cd3c29b3a49ca115a304cebb8cc6f31b04678f03b216899d3c6/scipy-1.15.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:1c832e1bd78dea67d5c16f786681b28dd695a8cb1fb90af2e27580d3d0967e92", size = 25121732, upload-time = "2025-05-08T16:04:36.596Z" }, - { url = "https://files.pythonhosted.org/packages/db/0a/92b1de4a7adc7a15dcf5bddc6e191f6f29ee663b30511ce20467ef9b82e4/scipy-1.15.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:263961f658ce2165bbd7b99fa5135195c3a12d9bef045345016b8b50c315cb82", size = 35547617, upload-time = "2025-05-08T16:04:43.546Z" }, - { url = "https://files.pythonhosted.org/packages/8e/6d/41991e503e51fc1134502694c5fa7a1671501a17ffa12716a4a9151af3df/scipy-1.15.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e2abc762b0811e09a0d3258abee2d98e0c703eee49464ce0069590846f31d40", size = 37662964, upload-time = "2025-05-08T16:04:49.431Z" }, - { url = "https://files.pythonhosted.org/packages/25/e1/3df8f83cb15f3500478c889be8fb18700813b95e9e087328230b98d547ff/scipy-1.15.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ed7284b21a7a0c8f1b6e5977ac05396c0d008b89e05498c8b7e8f4a1423bba0e", size = 37238749, upload-time = "2025-05-08T16:04:55.215Z" }, - { url = "https://files.pythonhosted.org/packages/93/3e/b3257cf446f2a3533ed7809757039016b74cd6f38271de91682aa844cfc5/scipy-1.15.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5380741e53df2c566f4d234b100a484b420af85deb39ea35a1cc1be84ff53a5c", size = 40022383, upload-time = "2025-05-08T16:05:01.914Z" }, - { url = "https://files.pythonhosted.org/packages/d1/84/55bc4881973d3f79b479a5a2e2df61c8c9a04fcb986a213ac9c02cfb659b/scipy-1.15.3-cp310-cp310-win_amd64.whl", hash = "sha256:9d61e97b186a57350f6d6fd72640f9e99d5a4a2b8fbf4b9ee9a841eab327dc13", size = 41259201, upload-time = "2025-05-08T16:05:08.166Z" }, - { url = "https://files.pythonhosted.org/packages/96/ab/5cc9f80f28f6a7dff646c5756e559823614a42b1939d86dd0ed550470210/scipy-1.15.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:993439ce220d25e3696d1b23b233dd010169b62f6456488567e830654ee37a6b", size = 38714255, upload-time = "2025-05-08T16:05:14.596Z" }, - { url = "https://files.pythonhosted.org/packages/4a/4a/66ba30abe5ad1a3ad15bfb0b59d22174012e8056ff448cb1644deccbfed2/scipy-1.15.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:34716e281f181a02341ddeaad584205bd2fd3c242063bd3423d61ac259ca7eba", size = 30111035, upload-time = "2025-05-08T16:05:20.152Z" }, - { url = "https://files.pythonhosted.org/packages/4b/fa/a7e5b95afd80d24313307f03624acc65801846fa75599034f8ceb9e2cbf6/scipy-1.15.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3b0334816afb8b91dab859281b1b9786934392aa3d527cd847e41bb6f45bee65", size = 22384499, upload-time = "2025-05-08T16:05:24.494Z" }, - { url = "https://files.pythonhosted.org/packages/17/99/f3aaddccf3588bb4aea70ba35328c204cadd89517a1612ecfda5b2dd9d7a/scipy-1.15.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:6db907c7368e3092e24919b5e31c76998b0ce1684d51a90943cb0ed1b4ffd6c1", size = 25152602, upload-time = "2025-05-08T16:05:29.313Z" }, - { url = "https://files.pythonhosted.org/packages/56/c5/1032cdb565f146109212153339f9cb8b993701e9fe56b1c97699eee12586/scipy-1.15.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:721d6b4ef5dc82ca8968c25b111e307083d7ca9091bc38163fb89243e85e3889", size = 35503415, upload-time = "2025-05-08T16:05:34.699Z" }, - { url = "https://files.pythonhosted.org/packages/bd/37/89f19c8c05505d0601ed5650156e50eb881ae3918786c8fd7262b4ee66d3/scipy-1.15.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39cb9c62e471b1bb3750066ecc3a3f3052b37751c7c3dfd0fd7e48900ed52982", size = 37652622, upload-time = "2025-05-08T16:05:40.762Z" }, - { url = "https://files.pythonhosted.org/packages/7e/31/be59513aa9695519b18e1851bb9e487de66f2d31f835201f1b42f5d4d475/scipy-1.15.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:795c46999bae845966368a3c013e0e00947932d68e235702b5c3f6ea799aa8c9", size = 37244796, upload-time = "2025-05-08T16:05:48.119Z" }, - { url = "https://files.pythonhosted.org/packages/10/c0/4f5f3eeccc235632aab79b27a74a9130c6c35df358129f7ac8b29f562ac7/scipy-1.15.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:18aaacb735ab38b38db42cb01f6b92a2d0d4b6aabefeb07f02849e47f8fb3594", size = 40047684, upload-time = "2025-05-08T16:05:54.22Z" }, - { url = "https://files.pythonhosted.org/packages/ab/a7/0ddaf514ce8a8714f6ed243a2b391b41dbb65251affe21ee3077ec45ea9a/scipy-1.15.3-cp311-cp311-win_amd64.whl", hash = "sha256:ae48a786a28412d744c62fd7816a4118ef97e5be0bee968ce8f0a2fba7acf3bb", size = 41246504, upload-time = "2025-05-08T16:06:00.437Z" }, - { url = "https://files.pythonhosted.org/packages/37/4b/683aa044c4162e10ed7a7ea30527f2cbd92e6999c10a8ed8edb253836e9c/scipy-1.15.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6ac6310fdbfb7aa6612408bd2f07295bcbd3fda00d2d702178434751fe48e019", size = 38766735, upload-time = "2025-05-08T16:06:06.471Z" }, - { url = "https://files.pythonhosted.org/packages/7b/7e/f30be3d03de07f25dc0ec926d1681fed5c732d759ac8f51079708c79e680/scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:185cd3d6d05ca4b44a8f1595af87f9c372bb6acf9c808e99aa3e9aa03bd98cf6", size = 30173284, upload-time = "2025-05-08T16:06:11.686Z" }, - { url = "https://files.pythonhosted.org/packages/07/9c/0ddb0d0abdabe0d181c1793db51f02cd59e4901da6f9f7848e1f96759f0d/scipy-1.15.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:05dc6abcd105e1a29f95eada46d4a3f251743cfd7d3ae8ddb4088047f24ea477", size = 22446958, upload-time = "2025-05-08T16:06:15.97Z" }, - { url = "https://files.pythonhosted.org/packages/af/43/0bce905a965f36c58ff80d8bea33f1f9351b05fad4beaad4eae34699b7a1/scipy-1.15.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:06efcba926324df1696931a57a176c80848ccd67ce6ad020c810736bfd58eb1c", size = 25242454, upload-time = "2025-05-08T16:06:20.394Z" }, - { url = "https://files.pythonhosted.org/packages/56/30/a6f08f84ee5b7b28b4c597aca4cbe545535c39fe911845a96414700b64ba/scipy-1.15.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05045d8b9bfd807ee1b9f38761993297b10b245f012b11b13b91ba8945f7e45", size = 35210199, upload-time = "2025-05-08T16:06:26.159Z" }, - { url = "https://files.pythonhosted.org/packages/0b/1f/03f52c282437a168ee2c7c14a1a0d0781a9a4a8962d84ac05c06b4c5b555/scipy-1.15.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271e3713e645149ea5ea3e97b57fdab61ce61333f97cfae392c28ba786f9bb49", size = 37309455, upload-time = "2025-05-08T16:06:32.778Z" }, - { url = "https://files.pythonhosted.org/packages/89/b1/fbb53137f42c4bf630b1ffdfc2151a62d1d1b903b249f030d2b1c0280af8/scipy-1.15.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6cfd56fc1a8e53f6e89ba3a7a7251f7396412d655bca2aa5611c8ec9a6784a1e", size = 36885140, upload-time = "2025-05-08T16:06:39.249Z" }, - { url = "https://files.pythonhosted.org/packages/2e/2e/025e39e339f5090df1ff266d021892694dbb7e63568edcfe43f892fa381d/scipy-1.15.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ff17c0bb1cb32952c09217d8d1eed9b53d1463e5f1dd6052c7857f83127d539", size = 39710549, upload-time = "2025-05-08T16:06:45.729Z" }, - { url = "https://files.pythonhosted.org/packages/e6/eb/3bf6ea8ab7f1503dca3a10df2e4b9c3f6b3316df07f6c0ded94b281c7101/scipy-1.15.3-cp312-cp312-win_amd64.whl", hash = "sha256:52092bc0472cfd17df49ff17e70624345efece4e1a12b23783a1ac59a1b728ed", size = 40966184, upload-time = "2025-05-08T16:06:52.623Z" }, - { url = "https://files.pythonhosted.org/packages/73/18/ec27848c9baae6e0d6573eda6e01a602e5649ee72c27c3a8aad673ebecfd/scipy-1.15.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c620736bcc334782e24d173c0fdbb7590a0a436d2fdf39310a8902505008759", size = 38728256, upload-time = "2025-05-08T16:06:58.696Z" }, - { url = "https://files.pythonhosted.org/packages/74/cd/1aef2184948728b4b6e21267d53b3339762c285a46a274ebb7863c9e4742/scipy-1.15.3-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:7e11270a000969409d37ed399585ee530b9ef6aa99d50c019de4cb01e8e54e62", size = 30109540, upload-time = "2025-05-08T16:07:04.209Z" }, - { url = "https://files.pythonhosted.org/packages/5b/d8/59e452c0a255ec352bd0a833537a3bc1bfb679944c4938ab375b0a6b3a3e/scipy-1.15.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8c9ed3ba2c8a2ce098163a9bdb26f891746d02136995df25227a20e71c396ebb", size = 22383115, upload-time = "2025-05-08T16:07:08.998Z" }, - { url = "https://files.pythonhosted.org/packages/08/f5/456f56bbbfccf696263b47095291040655e3cbaf05d063bdc7c7517f32ac/scipy-1.15.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0bdd905264c0c9cfa74a4772cdb2070171790381a5c4d312c973382fc6eaf730", size = 25163884, upload-time = "2025-05-08T16:07:14.091Z" }, - { url = "https://files.pythonhosted.org/packages/a2/66/a9618b6a435a0f0c0b8a6d0a2efb32d4ec5a85f023c2b79d39512040355b/scipy-1.15.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79167bba085c31f38603e11a267d862957cbb3ce018d8b38f79ac043bc92d825", size = 35174018, upload-time = "2025-05-08T16:07:19.427Z" }, - { url = "https://files.pythonhosted.org/packages/b5/09/c5b6734a50ad4882432b6bb7c02baf757f5b2f256041da5df242e2d7e6b6/scipy-1.15.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9deabd6d547aee2c9a81dee6cc96c6d7e9a9b1953f74850c179f91fdc729cb7", size = 37269716, upload-time = "2025-05-08T16:07:25.712Z" }, - { url = "https://files.pythonhosted.org/packages/77/0a/eac00ff741f23bcabd352731ed9b8995a0a60ef57f5fd788d611d43d69a1/scipy-1.15.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dde4fc32993071ac0c7dd2d82569e544f0bdaff66269cb475e0f369adad13f11", size = 36872342, upload-time = "2025-05-08T16:07:31.468Z" }, - { url = "https://files.pythonhosted.org/packages/fe/54/4379be86dd74b6ad81551689107360d9a3e18f24d20767a2d5b9253a3f0a/scipy-1.15.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f77f853d584e72e874d87357ad70f44b437331507d1c311457bed8ed2b956126", size = 39670869, upload-time = "2025-05-08T16:07:38.002Z" }, - { url = "https://files.pythonhosted.org/packages/87/2e/892ad2862ba54f084ffe8cc4a22667eaf9c2bcec6d2bff1d15713c6c0703/scipy-1.15.3-cp313-cp313-win_amd64.whl", hash = "sha256:b90ab29d0c37ec9bf55424c064312930ca5f4bde15ee8619ee44e69319aab163", size = 40988851, upload-time = "2025-05-08T16:08:33.671Z" }, - { url = "https://files.pythonhosted.org/packages/1b/e9/7a879c137f7e55b30d75d90ce3eb468197646bc7b443ac036ae3fe109055/scipy-1.15.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3ac07623267feb3ae308487c260ac684b32ea35fd81e12845039952f558047b8", size = 38863011, upload-time = "2025-05-08T16:07:44.039Z" }, - { url = "https://files.pythonhosted.org/packages/51/d1/226a806bbd69f62ce5ef5f3ffadc35286e9fbc802f606a07eb83bf2359de/scipy-1.15.3-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6487aa99c2a3d509a5227d9a5e889ff05830a06b2ce08ec30df6d79db5fcd5c5", size = 30266407, upload-time = "2025-05-08T16:07:49.891Z" }, - { url = "https://files.pythonhosted.org/packages/e5/9b/f32d1d6093ab9eeabbd839b0f7619c62e46cc4b7b6dbf05b6e615bbd4400/scipy-1.15.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:50f9e62461c95d933d5c5ef4a1f2ebf9a2b4e83b0db374cb3f1de104d935922e", size = 22540030, upload-time = "2025-05-08T16:07:54.121Z" }, - { url = "https://files.pythonhosted.org/packages/e7/29/c278f699b095c1a884f29fda126340fcc201461ee8bfea5c8bdb1c7c958b/scipy-1.15.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:14ed70039d182f411ffc74789a16df3835e05dc469b898233a245cdfd7f162cb", size = 25218709, upload-time = "2025-05-08T16:07:58.506Z" }, - { url = "https://files.pythonhosted.org/packages/24/18/9e5374b617aba742a990581373cd6b68a2945d65cc588482749ef2e64467/scipy-1.15.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a769105537aa07a69468a0eefcd121be52006db61cdd8cac8a0e68980bbb723", size = 34809045, upload-time = "2025-05-08T16:08:03.929Z" }, - { url = "https://files.pythonhosted.org/packages/e1/fe/9c4361e7ba2927074360856db6135ef4904d505e9b3afbbcb073c4008328/scipy-1.15.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9db984639887e3dffb3928d118145ffe40eff2fa40cb241a306ec57c219ebbbb", size = 36703062, upload-time = "2025-05-08T16:08:09.558Z" }, - { url = "https://files.pythonhosted.org/packages/b7/8e/038ccfe29d272b30086b25a4960f757f97122cb2ec42e62b460d02fe98e9/scipy-1.15.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:40e54d5c7e7ebf1aa596c374c49fa3135f04648a0caabcb66c52884b943f02b4", size = 36393132, upload-time = "2025-05-08T16:08:15.34Z" }, - { url = "https://files.pythonhosted.org/packages/10/7e/5c12285452970be5bdbe8352c619250b97ebf7917d7a9a9e96b8a8140f17/scipy-1.15.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5e721fed53187e71d0ccf382b6bf977644c533e506c4d33c3fb24de89f5c3ed5", size = 38979503, upload-time = "2025-05-08T16:08:21.513Z" }, - { url = "https://files.pythonhosted.org/packages/81/06/0a5e5349474e1cbc5757975b21bd4fad0e72ebf138c5592f191646154e06/scipy-1.15.3-cp313-cp313t-win_amd64.whl", hash = "sha256:76ad1fb5f8752eabf0fa02e4cc0336b4e8f021e2d5f061ed37d6d264db35e3ca", size = 40308097, upload-time = "2025-05-08T16:08:27.627Z" }, -] - -[[package]] -name = "scipy" -version = "1.17.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform == 'linux'", - "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'linux'", - "python_full_version >= '3.14' and sys_platform == 'darwin'", - "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'darwin'", - "python_full_version >= '3.14' and sys_platform != 'darwin' and sys_platform != 'linux' and sys_platform != 'win32'", - "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'darwin' and sys_platform != 'linux' and sys_platform != 'win32'", -] -dependencies = [ - { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/df/75/b4ce781849931fef6fd529afa6b63711d5a733065722d0c3e2724af9e40a/scipy-1.17.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:1f95b894f13729334fb990162e911c9e5dc1ab390c58aa6cbecb389c5b5e28ec", size = 31613675, upload-time = "2026-02-23T00:16:00.13Z" }, - { url = "https://files.pythonhosted.org/packages/f7/58/bccc2861b305abdd1b8663d6130c0b3d7cc22e8d86663edbc8401bfd40d4/scipy-1.17.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:e18f12c6b0bc5a592ed23d3f7b891f68fd7f8241d69b7883769eb5d5dfb52696", size = 28162057, upload-time = "2026-02-23T00:16:09.456Z" }, - { url = "https://files.pythonhosted.org/packages/6d/ee/18146b7757ed4976276b9c9819108adbc73c5aad636e5353e20746b73069/scipy-1.17.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a3472cfbca0a54177d0faa68f697d8ba4c80bbdc19908c3465556d9f7efce9ee", size = 20334032, upload-time = "2026-02-23T00:16:17.358Z" }, - { url = "https://files.pythonhosted.org/packages/ec/e6/cef1cf3557f0c54954198554a10016b6a03b2ec9e22a4e1df734936bd99c/scipy-1.17.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:766e0dc5a616d026a3a1cffa379af959671729083882f50307e18175797b3dfd", size = 22709533, upload-time = "2026-02-23T00:16:25.791Z" }, - { url = "https://files.pythonhosted.org/packages/4d/60/8804678875fc59362b0fb759ab3ecce1f09c10a735680318ac30da8cd76b/scipy-1.17.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:744b2bf3640d907b79f3fd7874efe432d1cf171ee721243e350f55234b4cec4c", size = 33062057, upload-time = "2026-02-23T00:16:36.931Z" }, - { url = "https://files.pythonhosted.org/packages/09/7d/af933f0f6e0767995b4e2d705a0665e454d1c19402aa7e895de3951ebb04/scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43af8d1f3bea642559019edfe64e9b11192a8978efbd1539d7bc2aaa23d92de4", size = 35349300, upload-time = "2026-02-23T00:16:49.108Z" }, - { url = "https://files.pythonhosted.org/packages/b4/3d/7ccbbdcbb54c8fdc20d3b6930137c782a163fa626f0aef920349873421ba/scipy-1.17.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd96a1898c0a47be4520327e01f874acfd61fb48a9420f8aa9f6483412ffa444", size = 35127333, upload-time = "2026-02-23T00:17:01.293Z" }, - { url = "https://files.pythonhosted.org/packages/e8/19/f926cb11c42b15ba08e3a71e376d816ac08614f769b4f47e06c3580c836a/scipy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4eb6c25dd62ee8d5edf68a8e1c171dd71c292fdae95d8aeb3dd7d7de4c364082", size = 37741314, upload-time = "2026-02-23T00:17:12.576Z" }, - { url = "https://files.pythonhosted.org/packages/95/da/0d1df507cf574b3f224ccc3d45244c9a1d732c81dcb26b1e8a766ae271a8/scipy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:d30e57c72013c2a4fe441c2fcb8e77b14e152ad48b5464858e07e2ad9fbfceff", size = 36607512, upload-time = "2026-02-23T00:17:23.424Z" }, - { url = "https://files.pythonhosted.org/packages/68/7f/bdd79ceaad24b671543ffe0ef61ed8e659440eb683b66f033454dcee90eb/scipy-1.17.1-cp311-cp311-win_arm64.whl", hash = "sha256:9ecb4efb1cd6e8c4afea0daa91a87fbddbce1b99d2895d151596716c0b2e859d", size = 24599248, upload-time = "2026-02-23T00:17:34.561Z" }, - { url = "https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:35c3a56d2ef83efc372eaec584314bd0ef2e2f0d2adb21c55e6ad5b344c0dcb8", size = 31610954, upload-time = "2026-02-23T00:17:49.855Z" }, - { url = "https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fcb310ddb270a06114bb64bbe53c94926b943f5b7f0842194d585c65eb4edd76", size = 28172662, upload-time = "2026-02-23T00:18:01.64Z" }, - { url = "https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cc90d2e9c7e5c7f1a482c9875007c095c3194b1cfedca3c2f3291cdc2bc7c086", size = 20344366, upload-time = "2026-02-23T00:18:12.015Z" }, - { url = "https://files.pythonhosted.org/packages/35/f5/906eda513271c8deb5af284e5ef0206d17a96239af79f9fa0aebfe0e36b4/scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:c80be5ede8f3f8eded4eff73cc99a25c388ce98e555b17d31da05287015ffa5b", size = 22704017, upload-time = "2026-02-23T00:18:21.502Z" }, - { url = "https://files.pythonhosted.org/packages/da/34/16f10e3042d2f1d6b66e0428308ab52224b6a23049cb2f5c1756f713815f/scipy-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e19ebea31758fac5893a2ac360fedd00116cbb7628e650842a6691ba7ca28a21", size = 32927842, upload-time = "2026-02-23T00:18:35.367Z" }, - { url = "https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02ae3b274fde71c5e92ac4d54bc06c42d80e399fec704383dcd99b301df37458", size = 35235890, upload-time = "2026-02-23T00:18:49.188Z" }, - { url = "https://files.pythonhosted.org/packages/c5/5c/9d7f4c88bea6e0d5a4f1bc0506a53a00e9fcb198de372bfe4d3652cef482/scipy-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a604bae87c6195d8b1045eddece0514d041604b14f2727bbc2b3020172045eb", size = 35003557, upload-time = "2026-02-23T00:18:54.74Z" }, - { url = "https://files.pythonhosted.org/packages/65/94/7698add8f276dbab7a9de9fb6b0e02fc13ee61d51c7c3f85ac28b65e1239/scipy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f590cd684941912d10becc07325a3eeb77886fe981415660d9265c4c418d0bea", size = 37625856, upload-time = "2026-02-23T00:19:00.307Z" }, - { url = "https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87", size = 36549682, upload-time = "2026-02-23T00:19:07.67Z" }, - { url = "https://files.pythonhosted.org/packages/bc/98/fe9ae9ffb3b54b62559f52dedaebe204b408db8109a8c66fdd04869e6424/scipy-1.17.1-cp312-cp312-win_arm64.whl", hash = "sha256:f4115102802df98b2b0db3cce5cb9b92572633a1197c77b7553e5203f284a5b3", size = 24547340, upload-time = "2026-02-23T00:19:12.024Z" }, - { url = "https://files.pythonhosted.org/packages/76/27/07ee1b57b65e92645f219b37148a7e7928b82e2b5dbeccecb4dff7c64f0b/scipy-1.17.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5e3c5c011904115f88a39308379c17f91546f77c1667cea98739fe0fccea804c", size = 31590199, upload-time = "2026-02-23T00:19:17.192Z" }, - { url = "https://files.pythonhosted.org/packages/ec/ae/db19f8ab842e9b724bf5dbb7db29302a91f1e55bc4d04b1025d6d605a2c5/scipy-1.17.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:6fac755ca3d2c3edcb22f479fceaa241704111414831ddd3bc6056e18516892f", size = 28154001, upload-time = "2026-02-23T00:19:22.241Z" }, - { url = "https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:7ff200bf9d24f2e4d5dc6ee8c3ac64d739d3a89e2326ba68aaf6c4a2b838fd7d", size = 20325719, upload-time = "2026-02-23T00:19:26.329Z" }, - { url = "https://files.pythonhosted.org/packages/b2/83/15087d945e0e4d48ce2377498abf5ad171ae013232ae31d06f336e64c999/scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4b400bdc6f79fa02a4d86640310dde87a21fba0c979efff5248908c6f15fad1b", size = 22683595, upload-time = "2026-02-23T00:19:30.304Z" }, - { url = "https://files.pythonhosted.org/packages/b4/e0/e58fbde4a1a594c8be8114eb4aac1a55bcd6587047efc18a61eb1f5c0d30/scipy-1.17.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b64ca7d4aee0102a97f3ba22124052b4bd2152522355073580bf4845e2550b6", size = 32896429, upload-time = "2026-02-23T00:19:35.536Z" }, - { url = "https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:581b2264fc0aa555f3f435a5944da7504ea3a065d7029ad60e7c3d1ae09c5464", size = 35203952, upload-time = "2026-02-23T00:19:42.259Z" }, - { url = "https://files.pythonhosted.org/packages/8d/a5/9afd17de24f657fdfe4df9a3f1ea049b39aef7c06000c13db1530d81ccca/scipy-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:beeda3d4ae615106d7094f7e7cef6218392e4465cc95d25f900bebabfded0950", size = 34979063, upload-time = "2026-02-23T00:19:47.547Z" }, - { url = "https://files.pythonhosted.org/packages/8b/13/88b1d2384b424bf7c924f2038c1c409f8d88bb2a8d49d097861dd64a57b2/scipy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6609bc224e9568f65064cfa72edc0f24ee6655b47575954ec6339534b2798369", size = 37598449, upload-time = "2026-02-23T00:19:53.238Z" }, - { url = "https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:37425bc9175607b0268f493d79a292c39f9d001a357bebb6b88fdfaff13f6448", size = 36510943, upload-time = "2026-02-23T00:20:50.89Z" }, - { url = "https://files.pythonhosted.org/packages/2a/fd/3be73c564e2a01e690e19cc618811540ba5354c67c8680dce3281123fb79/scipy-1.17.1-cp313-cp313-win_arm64.whl", hash = "sha256:5cf36e801231b6a2059bf354720274b7558746f3b1a4efb43fcf557ccd484a87", size = 24545621, upload-time = "2026-02-23T00:20:55.871Z" }, - { url = "https://files.pythonhosted.org/packages/6f/6b/17787db8b8114933a66f9dcc479a8272e4b4da75fe03b0c282f7b0ade8cd/scipy-1.17.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:d59c30000a16d8edc7e64152e30220bfbd724c9bbb08368c054e24c651314f0a", size = 31936708, upload-time = "2026-02-23T00:19:58.694Z" }, - { url = "https://files.pythonhosted.org/packages/38/2e/524405c2b6392765ab1e2b722a41d5da33dc5c7b7278184a8ad29b6cb206/scipy-1.17.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:010f4333c96c9bb1a4516269e33cb5917b08ef2166d5556ca2fd9f082a9e6ea0", size = 28570135, upload-time = "2026-02-23T00:20:03.934Z" }, - { url = "https://files.pythonhosted.org/packages/fd/c3/5bd7199f4ea8556c0c8e39f04ccb014ac37d1468e6cfa6a95c6b3562b76e/scipy-1.17.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2ceb2d3e01c5f1d83c4189737a42d9cb2fc38a6eeed225e7515eef71ad301dce", size = 20741977, upload-time = "2026-02-23T00:20:07.935Z" }, - { url = "https://files.pythonhosted.org/packages/d9/b8/8ccd9b766ad14c78386599708eb745f6b44f08400a5fd0ade7cf89b6fc93/scipy-1.17.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:844e165636711ef41f80b4103ed234181646b98a53c8f05da12ca5ca289134f6", size = 23029601, upload-time = "2026-02-23T00:20:12.161Z" }, - { url = "https://files.pythonhosted.org/packages/6d/a0/3cb6f4d2fb3e17428ad2880333cac878909ad1a89f678527b5328b93c1d4/scipy-1.17.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:158dd96d2207e21c966063e1635b1063cd7787b627b6f07305315dd73d9c679e", size = 33019667, upload-time = "2026-02-23T00:20:17.208Z" }, - { url = "https://files.pythonhosted.org/packages/f3/c3/2d834a5ac7bf3a0c806ad1508efc02dda3c8c61472a56132d7894c312dea/scipy-1.17.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74cbb80d93260fe2ffa334efa24cb8f2f0f622a9b9febf8b483c0b865bfb3475", size = 35264159, upload-time = "2026-02-23T00:20:23.087Z" }, - { url = "https://files.pythonhosted.org/packages/4d/77/d3ed4becfdbd217c52062fafe35a72388d1bd82c2d0ba5ca19d6fcc93e11/scipy-1.17.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dbc12c9f3d185f5c737d801da555fb74b3dcfa1a50b66a1a93e09190f41fab50", size = 35102771, upload-time = "2026-02-23T00:20:28.636Z" }, - { url = "https://files.pythonhosted.org/packages/bd/12/d19da97efde68ca1ee5538bb261d5d2c062f0c055575128f11a2730e3ac1/scipy-1.17.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94055a11dfebe37c656e70317e1996dc197e1a15bbcc351bcdd4610e128fe1ca", size = 37665910, upload-time = "2026-02-23T00:20:34.743Z" }, - { url = "https://files.pythonhosted.org/packages/06/1c/1172a88d507a4baaf72c5a09bb6c018fe2ae0ab622e5830b703a46cc9e44/scipy-1.17.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e30bdeaa5deed6bc27b4cc490823cd0347d7dae09119b8803ae576ea0ce52e4c", size = 36562980, upload-time = "2026-02-23T00:20:40.575Z" }, - { url = "https://files.pythonhosted.org/packages/70/b0/eb757336e5a76dfa7911f63252e3b7d1de00935d7705cf772db5b45ec238/scipy-1.17.1-cp313-cp313t-win_arm64.whl", hash = "sha256:a720477885a9d2411f94a93d16f9d89bad0f28ca23c3f8daa521e2dcc3f44d49", size = 24856543, upload-time = "2026-02-23T00:20:45.313Z" }, - { url = "https://files.pythonhosted.org/packages/cf/83/333afb452af6f0fd70414dc04f898647ee1423979ce02efa75c3b0f2c28e/scipy-1.17.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:a48a72c77a310327f6a3a920092fa2b8fd03d7deaa60f093038f22d98e096717", size = 31584510, upload-time = "2026-02-23T00:21:01.015Z" }, - { url = "https://files.pythonhosted.org/packages/ed/a6/d05a85fd51daeb2e4ea71d102f15b34fedca8e931af02594193ae4fd25f7/scipy-1.17.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:45abad819184f07240d8a696117a7aacd39787af9e0b719d00285549ed19a1e9", size = 28170131, upload-time = "2026-02-23T00:21:05.888Z" }, - { url = "https://files.pythonhosted.org/packages/db/7b/8624a203326675d7746a254083a187398090a179335b2e4a20e2ddc46e83/scipy-1.17.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3fd1fcdab3ea951b610dc4cef356d416d5802991e7e32b5254828d342f7b7e0b", size = 20342032, upload-time = "2026-02-23T00:21:09.904Z" }, - { url = "https://files.pythonhosted.org/packages/c9/35/2c342897c00775d688d8ff3987aced3426858fd89d5a0e26e020b660b301/scipy-1.17.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7bdf2da170b67fdf10bca777614b1c7d96ae3ca5794fd9587dce41eb2966e866", size = 22678766, upload-time = "2026-02-23T00:21:14.313Z" }, - { url = "https://files.pythonhosted.org/packages/ef/f2/7cdb8eb308a1a6ae1e19f945913c82c23c0c442a462a46480ce487fdc0ac/scipy-1.17.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:adb2642e060a6549c343603a3851ba76ef0b74cc8c079a9a58121c7ec9fe2350", size = 32957007, upload-time = "2026-02-23T00:21:19.663Z" }, - { url = "https://files.pythonhosted.org/packages/0b/2e/7eea398450457ecb54e18e9d10110993fa65561c4f3add5e8eccd2b9cd41/scipy-1.17.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eee2cfda04c00a857206a4330f0c5e3e56535494e30ca445eb19ec624ae75118", size = 35221333, upload-time = "2026-02-23T00:21:25.278Z" }, - { url = "https://files.pythonhosted.org/packages/d9/77/5b8509d03b77f093a0d52e606d3c4f79e8b06d1d38c441dacb1e26cacf46/scipy-1.17.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d2650c1fb97e184d12d8ba010493ee7b322864f7d3d00d3f9bb97d9c21de4068", size = 35042066, upload-time = "2026-02-23T00:21:31.358Z" }, - { url = "https://files.pythonhosted.org/packages/f9/df/18f80fb99df40b4070328d5ae5c596f2f00fffb50167e31439e932f29e7d/scipy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:08b900519463543aa604a06bec02461558a6e1cef8fdbb8098f77a48a83c8118", size = 37612763, upload-time = "2026-02-23T00:21:37.247Z" }, - { url = "https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:3877ac408e14da24a6196de0ddcace62092bfc12a83823e92e49e40747e52c19", size = 37290984, upload-time = "2026-02-23T00:22:35.023Z" }, - { url = "https://files.pythonhosted.org/packages/7c/56/fe201e3b0f93d1a8bcf75d3379affd228a63d7e2d80ab45467a74b494947/scipy-1.17.1-cp314-cp314-win_arm64.whl", hash = "sha256:f8885db0bc2bffa59d5c1b72fad7a6a92d3e80e7257f967dd81abb553a90d293", size = 25192877, upload-time = "2026-02-23T00:22:39.798Z" }, - { url = "https://files.pythonhosted.org/packages/96/ad/f8c414e121f82e02d76f310f16db9899c4fcde36710329502a6b2a3c0392/scipy-1.17.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:1cc682cea2ae55524432f3cdff9e9a3be743d52a7443d0cba9017c23c87ae2f6", size = 31949750, upload-time = "2026-02-23T00:21:42.289Z" }, - { url = "https://files.pythonhosted.org/packages/7c/b0/c741e8865d61b67c81e255f4f0a832846c064e426636cd7de84e74d209be/scipy-1.17.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:2040ad4d1795a0ae89bfc7e8429677f365d45aa9fd5e4587cf1ea737f927b4a1", size = 28585858, upload-time = "2026-02-23T00:21:47.706Z" }, - { url = "https://files.pythonhosted.org/packages/ed/1b/3985219c6177866628fa7c2595bfd23f193ceebbe472c98a08824b9466ff/scipy-1.17.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:131f5aaea57602008f9822e2115029b55d4b5f7c070287699fe45c661d051e39", size = 20757723, upload-time = "2026-02-23T00:21:52.039Z" }, - { url = "https://files.pythonhosted.org/packages/c0/19/2a04aa25050d656d6f7b9e7b685cc83d6957fb101665bfd9369ca6534563/scipy-1.17.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9cdc1a2fcfd5c52cfb3045feb399f7b3ce822abdde3a193a6b9a60b3cb5854ca", size = 23043098, upload-time = "2026-02-23T00:21:56.185Z" }, - { url = "https://files.pythonhosted.org/packages/86/f1/3383beb9b5d0dbddd030335bf8a8b32d4317185efe495374f134d8be6cce/scipy-1.17.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e3dcd57ab780c741fde8dc68619de988b966db759a3c3152e8e9142c26295ad", size = 33030397, upload-time = "2026-02-23T00:22:01.404Z" }, - { url = "https://files.pythonhosted.org/packages/41/68/8f21e8a65a5a03f25a79165ec9d2b28c00e66dc80546cf5eb803aeeff35b/scipy-1.17.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9956e4d4f4a301ebf6cde39850333a6b6110799d470dbbb1e25326ac447f52a", size = 35281163, upload-time = "2026-02-23T00:22:07.024Z" }, - { url = "https://files.pythonhosted.org/packages/84/8d/c8a5e19479554007a5632ed7529e665c315ae7492b4f946b0deb39870e39/scipy-1.17.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a4328d245944d09fd639771de275701ccadf5f781ba0ff092ad141e017eccda4", size = 35116291, upload-time = "2026-02-23T00:22:12.585Z" }, - { url = "https://files.pythonhosted.org/packages/52/52/e57eceff0e342a1f50e274264ed47497b59e6a4e3118808ee58ddda7b74a/scipy-1.17.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a77cbd07b940d326d39a1d1b37817e2ee4d79cb30e7338f3d0cddffae70fcaa2", size = 37682317, upload-time = "2026-02-23T00:22:18.513Z" }, - { url = "https://files.pythonhosted.org/packages/11/2f/b29eafe4a3fbc3d6de9662b36e028d5f039e72d345e05c250e121a230dd4/scipy-1.17.1-cp314-cp314t-win_amd64.whl", hash = "sha256:eb092099205ef62cd1782b006658db09e2fed75bffcae7cc0d44052d8aa0f484", size = 37345327, upload-time = "2026-02-23T00:22:24.442Z" }, - { url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165, upload-time = "2026-02-23T00:22:29.563Z" }, -] - -[[package]] -name = "sentence-transformers" -version = "5.5.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "huggingface-hub" }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "scikit-learn", version = "1.7.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "scikit-learn", version = "1.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "torch" }, - { name = "tqdm" }, - { name = "transformers" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/2c/27/16d127a61303e05847d878b23687f3371868c76e738557fa80b4373a8c2b/sentence_transformers-5.5.0.tar.gz", hash = "sha256:9cec675e68bfe09d07466d1f13ab06d1d79d60a0f45b154baf433bde6ae159cb", size = 444908, upload-time = "2026-05-12T14:05:42.383Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/55/20/18416624bcbae866ec0b111979766cebabe8e5ff7563ab953ecbaf3ff9e7/sentence_transformers-5.5.0-py3-none-any.whl", hash = "sha256:75313fdcc2397ec4b58297c25d6187fcca5a6b2aeb09570a72eff5a3223d8d58", size = 588665, upload-time = "2026-05-12T14:05:40.899Z" }, -] - -[[package]] -name = "sentencepiece" -version = "0.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/15/15/2e7a025fc62d764b151ae6d0f2a92f8081755ebe8d4a64099accc6f77ba6/sentencepiece-0.2.1.tar.gz", hash = "sha256:8138cec27c2f2282f4a34d9a016e3374cd40e5c6e9cb335063db66a0a3b71fad", size = 3228515, upload-time = "2025-08-12T07:00:51.718Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/af/31/5b7cccb307b485db1a2372d6d2980b0a65d067f8be5ca943a103b4acd5b3/sentencepiece-0.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e10fa50bdbaa5e2445dbd387979980d391760faf0ec99a09bd7780ff37eaec44", size = 1942557, upload-time = "2025-08-12T06:59:12.379Z" }, - { url = "https://files.pythonhosted.org/packages/1f/41/0ac923a8e685ad290c5afc8ae55c5844977b8d75076fcc04302b9a324274/sentencepiece-0.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f27ae6deea72efdb6f361750c92f6c21fd0ad087445082770cc34015213c526", size = 1325384, upload-time = "2025-08-12T06:59:14.334Z" }, - { url = "https://files.pythonhosted.org/packages/fc/ef/3751555d67daf9003384978f169d31c775cb5c7baf28633caaf1eb2b2b4d/sentencepiece-0.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:60937c959e6f44159fdd9f56fbdd302501f96114a5ba436829496d5f32d8de3f", size = 1253317, upload-time = "2025-08-12T06:59:16.247Z" }, - { url = "https://files.pythonhosted.org/packages/46/a5/742c69b7bd144eb32b6e5fd50dbd8abbbc7a95fce2fe16e50156fa400e3b/sentencepiece-0.2.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8b1d91545578852f128650b8cce4ec20f93d39b378ff554ebe66290f2dabb92", size = 1316379, upload-time = "2025-08-12T06:59:17.825Z" }, - { url = "https://files.pythonhosted.org/packages/c8/89/8deeafbba2871e8fa10f20f17447786f4ac38085925335728d360eaf4cae/sentencepiece-0.2.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:27e38eee653abc3d387862e67bc5c8b6f428cd604e688b85d29170b7e725c26c", size = 1387926, upload-time = "2025-08-12T06:59:19.395Z" }, - { url = "https://files.pythonhosted.org/packages/c3/ca/67fe73005f0ab617c6a970b199754e28e524b6873aa7025224fad3cda252/sentencepiece-0.2.1-cp310-cp310-win32.whl", hash = "sha256:251874d720ac7f28024a168501f3c7bb15d1802245f6e66de565f18bbb9b5eaa", size = 999550, upload-time = "2025-08-12T06:59:20.844Z" }, - { url = "https://files.pythonhosted.org/packages/6d/33/dc5b54042050d2dda4229c3ce1f862541c99966390b6aa20f54d520d2dc2/sentencepiece-0.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:e52144670738b4b477fade6c2a9b6af71a8d0094514c9853ac9f6fc1fcfabae7", size = 1054613, upload-time = "2025-08-12T06:59:22.255Z" }, - { url = "https://files.pythonhosted.org/packages/fa/19/1ea47f46ff97fe04422b78997da1a37cd632f414aae042d27a9009c5b733/sentencepiece-0.2.1-cp310-cp310-win_arm64.whl", hash = "sha256:9076430ac25dfa7147d9d05751dbc66a04bc1aaac371c07f84952979ea59f0d0", size = 1033884, upload-time = "2025-08-12T06:59:24.194Z" }, - { url = "https://files.pythonhosted.org/packages/d8/15/46afbab00733d81788b64be430ca1b93011bb9388527958e26cc31832de5/sentencepiece-0.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6356d0986b8b8dc351b943150fcd81a1c6e6e4d439772e8584c64230e58ca987", size = 1942560, upload-time = "2025-08-12T06:59:25.82Z" }, - { url = "https://files.pythonhosted.org/packages/fa/79/7c01b8ef98a0567e9d84a4e7a910f8e7074fcbf398a5cd76f93f4b9316f9/sentencepiece-0.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8f8ba89a3acb3dc1ae90f65ec1894b0b9596fdb98ab003ff38e058f898b39bc7", size = 1325385, upload-time = "2025-08-12T06:59:27.722Z" }, - { url = "https://files.pythonhosted.org/packages/bb/88/2b41e07bd24f33dcf2f18ec3b74247aa4af3526bad8907b8727ea3caba03/sentencepiece-0.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:02593eca45440ef39247cee8c47322a34bdcc1d8ae83ad28ba5a899a2cf8d79a", size = 1253319, upload-time = "2025-08-12T06:59:29.306Z" }, - { url = "https://files.pythonhosted.org/packages/a0/54/38a1af0c6210a3c6f95aa46d23d6640636d020fba7135cd0d9a84ada05a7/sentencepiece-0.2.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a0d15781a171d188b661ae4bde1d998c303f6bd8621498c50c671bd45a4798e", size = 1316162, upload-time = "2025-08-12T06:59:30.914Z" }, - { url = "https://files.pythonhosted.org/packages/ef/66/fb191403ade791ad2c3c1e72fe8413e63781b08cfa3aa4c9dfc536d6e795/sentencepiece-0.2.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f5a3e0d9f445ed9d66c0fec47d4b23d12cfc858b407a03c194c1b26c2ac2a63", size = 1387785, upload-time = "2025-08-12T06:59:32.491Z" }, - { url = "https://files.pythonhosted.org/packages/a9/2d/3bd9b08e70067b2124518b308db6a84a4f8901cc8a4317e2e4288cdd9b4d/sentencepiece-0.2.1-cp311-cp311-win32.whl", hash = "sha256:6d297a1748d429ba8534eebe5535448d78b8acc32d00a29b49acf28102eeb094", size = 999555, upload-time = "2025-08-12T06:59:34.475Z" }, - { url = "https://files.pythonhosted.org/packages/32/b8/f709977f5fda195ae1ea24f24e7c581163b6f142b1005bc3d0bbfe4d7082/sentencepiece-0.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:82d9ead6591015f009cb1be1cb1c015d5e6f04046dbb8c9588b931e869a29728", size = 1054617, upload-time = "2025-08-12T06:59:36.461Z" }, - { url = "https://files.pythonhosted.org/packages/7a/40/a1fc23be23067da0f703709797b464e8a30a1c78cc8a687120cd58d4d509/sentencepiece-0.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:39f8651bd10974eafb9834ce30d9bcf5b73e1fc798a7f7d2528f9820ca86e119", size = 1033877, upload-time = "2025-08-12T06:59:38.391Z" }, - { url = "https://files.pythonhosted.org/packages/4a/be/32ce495aa1d0e0c323dcb1ba87096037358edee539cac5baf8755a6bd396/sentencepiece-0.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:57cae326c8727de58c85977b175af132a7138d84c764635d7e71bbee7e774133", size = 1943152, upload-time = "2025-08-12T06:59:40.048Z" }, - { url = "https://files.pythonhosted.org/packages/88/7e/ff23008899a58678e98c6ff592bf4d368eee5a71af96d0df6b38a039dd4f/sentencepiece-0.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:56dd39a3c4d6493db3cdca7e8cc68c6b633f0d4195495cbadfcf5af8a22d05a6", size = 1325651, upload-time = "2025-08-12T06:59:41.536Z" }, - { url = "https://files.pythonhosted.org/packages/19/84/42eb3ce4796777a1b5d3699dfd4dca85113e68b637f194a6c8d786f16a04/sentencepiece-0.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d9381351182ff9888cc80e41c632e7e274b106f450de33d67a9e8f6043da6f76", size = 1253645, upload-time = "2025-08-12T06:59:42.903Z" }, - { url = "https://files.pythonhosted.org/packages/89/fa/d3d5ebcba3cb9e6d3775a096251860c41a6bc53a1b9461151df83fe93255/sentencepiece-0.2.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:99f955df238021bf11f0fc37cdb54fd5e5b5f7fd30ecc3d93fb48b6815437167", size = 1316273, upload-time = "2025-08-12T06:59:44.476Z" }, - { url = "https://files.pythonhosted.org/packages/04/88/14f2f4a2b922d8b39be45bf63d79e6cd3a9b2f248b2fcb98a69b12af12f5/sentencepiece-0.2.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0cdfecef430d985f1c2bcbfff3defd1d95dae876fbd0173376012d2d7d24044b", size = 1387881, upload-time = "2025-08-12T06:59:46.09Z" }, - { url = "https://files.pythonhosted.org/packages/fd/b8/903e5ccb77b4ef140605d5d71b4f9e0ad95d456d6184688073ed11712809/sentencepiece-0.2.1-cp312-cp312-win32.whl", hash = "sha256:a483fd29a34c3e34c39ac5556b0a90942bec253d260235729e50976f5dba1068", size = 999540, upload-time = "2025-08-12T06:59:48.023Z" }, - { url = "https://files.pythonhosted.org/packages/2d/81/92df5673c067148c2545b1bfe49adfd775bcc3a169a047f5a0e6575ddaca/sentencepiece-0.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:4cdc7c36234fda305e85c32949c5211faaf8dd886096c7cea289ddc12a2d02de", size = 1054671, upload-time = "2025-08-12T06:59:49.895Z" }, - { url = "https://files.pythonhosted.org/packages/fe/02/c5e3bc518655d714622bec87d83db9cdba1cd0619a4a04e2109751c4f47f/sentencepiece-0.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:daeb5e9e9fcad012324807856113708614d534f596d5008638eb9b40112cd9e4", size = 1033923, upload-time = "2025-08-12T06:59:51.952Z" }, - { url = "https://files.pythonhosted.org/packages/ba/4a/85fbe1706d4d04a7e826b53f327c4b80f849cf1c7b7c5e31a20a97d8f28b/sentencepiece-0.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dcd8161eee7b41aae57ded06272905dbd680a0a04b91edd0f64790c796b2f706", size = 1943150, upload-time = "2025-08-12T06:59:53.588Z" }, - { url = "https://files.pythonhosted.org/packages/c2/83/4cfb393e287509fc2155480b9d184706ef8d9fa8cbf5505d02a5792bf220/sentencepiece-0.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c6c8f42949f419ff8c7e9960dbadcfbc982d7b5efc2f6748210d3dd53a7de062", size = 1325651, upload-time = "2025-08-12T06:59:55.073Z" }, - { url = "https://files.pythonhosted.org/packages/8d/de/5a007fb53b1ab0aafc69d11a5a3dd72a289d5a3e78dcf2c3a3d9b14ffe93/sentencepiece-0.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:097f3394e99456e9e4efba1737c3749d7e23563dd1588ce71a3d007f25475fff", size = 1253641, upload-time = "2025-08-12T06:59:56.562Z" }, - { url = "https://files.pythonhosted.org/packages/2c/d2/f552be5928105588f4f4d66ee37dd4c61460d8097e62d0e2e0eec41bc61d/sentencepiece-0.2.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d7b670879c370d350557edabadbad1f6561a9e6968126e6debca4029e5547820", size = 1316271, upload-time = "2025-08-12T06:59:58.109Z" }, - { url = "https://files.pythonhosted.org/packages/96/df/0cfe748ace5485be740fed9476dee7877f109da32ed0d280312c94ec259f/sentencepiece-0.2.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7f0fd2f2693309e6628aeeb2e2faf6edd221134dfccac3308ca0de01f8dab47", size = 1387882, upload-time = "2025-08-12T07:00:00.701Z" }, - { url = "https://files.pythonhosted.org/packages/ac/dd/f7774d42a881ced8e1739f393ab1e82ece39fc9abd4779e28050c2e975b5/sentencepiece-0.2.1-cp313-cp313-win32.whl", hash = "sha256:92b3816aa2339355fda2c8c4e021a5de92180b00aaccaf5e2808972e77a4b22f", size = 999541, upload-time = "2025-08-12T07:00:02.709Z" }, - { url = "https://files.pythonhosted.org/packages/dd/e9/932b9eae6fd7019548321eee1ab8d5e3b3d1294df9d9a0c9ac517c7b636d/sentencepiece-0.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:10ed3dab2044c47f7a2e7b4969b0c430420cdd45735d78c8f853191fa0e3148b", size = 1054669, upload-time = "2025-08-12T07:00:04.915Z" }, - { url = "https://files.pythonhosted.org/packages/c9/3a/76488a00ea7d6931689cda28726a1447d66bf1a4837943489314593d5596/sentencepiece-0.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:ac650534e2251083c5f75dde4ff28896ce7c8904133dc8fef42780f4d5588fcd", size = 1033922, upload-time = "2025-08-12T07:00:06.496Z" }, - { url = "https://files.pythonhosted.org/packages/4a/b6/08fe2ce819e02ccb0296f4843e3f195764ce9829cbda61b7513f29b95718/sentencepiece-0.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:8dd4b477a7b069648d19363aad0cab9bad2f4e83b2d179be668efa672500dc94", size = 1946052, upload-time = "2025-08-12T07:00:08.136Z" }, - { url = "https://files.pythonhosted.org/packages/ab/d9/1ea0e740591ff4c6fc2b6eb1d7510d02f3fb885093f19b2f3abd1363b402/sentencepiece-0.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0c0f672da370cc490e4c59d89e12289778310a0e71d176c541e4834759e1ae07", size = 1327408, upload-time = "2025-08-12T07:00:09.572Z" }, - { url = "https://files.pythonhosted.org/packages/99/7e/1fb26e8a21613f6200e1ab88824d5d203714162cf2883248b517deb500b7/sentencepiece-0.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ad8493bea8432dae8d6830365352350f3b4144415a1d09c4c8cb8d30cf3b6c3c", size = 1254857, upload-time = "2025-08-12T07:00:11.021Z" }, - { url = "https://files.pythonhosted.org/packages/bc/85/c72fd1f3c7a6010544d6ae07f8ddb38b5e2a7e33bd4318f87266c0bbafbf/sentencepiece-0.2.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b81a24733726e3678d2db63619acc5a8dccd074f7aa7a54ecd5ca33ca6d2d596", size = 1315722, upload-time = "2025-08-12T07:00:12.989Z" }, - { url = "https://files.pythonhosted.org/packages/4a/e8/661e5bd82a8aa641fd6c1020bd0e890ef73230a2b7215ddf9c8cd8e941c2/sentencepiece-0.2.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0a81799d0a68d618e89063fb423c3001a034c893069135ffe51fee439ae474d6", size = 1387452, upload-time = "2025-08-12T07:00:15.088Z" }, - { url = "https://files.pythonhosted.org/packages/99/5e/ae66c361023a470afcbc1fbb8da722c72ea678a2fcd9a18f1a12598c7501/sentencepiece-0.2.1-cp313-cp313t-win32.whl", hash = "sha256:89a3ea015517c42c0341d0d962f3e6aaf2cf10d71b1932d475c44ba48d00aa2b", size = 1002501, upload-time = "2025-08-12T07:00:16.966Z" }, - { url = "https://files.pythonhosted.org/packages/c1/03/d332828c4ff764e16c1b56c2c8f9a33488bbe796b53fb6b9c4205ddbf167/sentencepiece-0.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:33f068c9382dc2e7c228eedfd8163b52baa86bb92f50d0488bf2b7da7032e484", size = 1057555, upload-time = "2025-08-12T07:00:18.573Z" }, - { url = "https://files.pythonhosted.org/packages/88/14/5aee0bf0864df9bd82bd59e7711362908e4935e3f9cdc1f57246b5d5c9b9/sentencepiece-0.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:b3616ad246f360e52c85781e47682d31abfb6554c779e42b65333d4b5f44ecc0", size = 1036042, upload-time = "2025-08-12T07:00:20.209Z" }, - { url = "https://files.pythonhosted.org/packages/24/9c/89eb8b2052f720a612478baf11c8227dcf1dc28cd4ea4c0c19506b5af2a2/sentencepiece-0.2.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:5d0350b686c320068702116276cfb26c066dc7e65cfef173980b11bb4d606719", size = 1943147, upload-time = "2025-08-12T07:00:21.809Z" }, - { url = "https://files.pythonhosted.org/packages/82/0b/a1432bc87f97c2ace36386ca23e8bd3b91fb40581b5e6148d24b24186419/sentencepiece-0.2.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c7f54a31cde6fa5cb030370566f68152a742f433f8d2be458463d06c208aef33", size = 1325624, upload-time = "2025-08-12T07:00:23.289Z" }, - { url = "https://files.pythonhosted.org/packages/ea/99/bbe054ebb5a5039457c590e0a4156ed073fb0fe9ce4f7523404dd5b37463/sentencepiece-0.2.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c83b85ab2d6576607f31df77ff86f28182be4a8de6d175d2c33ca609925f5da1", size = 1253670, upload-time = "2025-08-12T07:00:24.69Z" }, - { url = "https://files.pythonhosted.org/packages/19/ad/d5c7075f701bd97971d7c2ac2904f227566f51ef0838dfbdfdccb58cd212/sentencepiece-0.2.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1855f57db07b51fb51ed6c9c452f570624d2b169b36f0f79ef71a6e6c618cd8b", size = 1316247, upload-time = "2025-08-12T07:00:26.435Z" }, - { url = "https://files.pythonhosted.org/packages/fb/03/35fbe5f3d9a7435eebd0b473e09584bd3cc354ce118b960445b060d33781/sentencepiece-0.2.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01e6912125cb45d3792f530a4d38f8e21bf884d6b4d4ade1b2de5cf7a8d2a52b", size = 1387894, upload-time = "2025-08-12T07:00:28.339Z" }, - { url = "https://files.pythonhosted.org/packages/dc/aa/956ef729aafb6c8f9c443104c9636489093bb5c61d6b90fc27aa1a865574/sentencepiece-0.2.1-cp314-cp314-win32.whl", hash = "sha256:c415c9de1447e0a74ae3fdb2e52f967cb544113a3a5ce3a194df185cbc1f962f", size = 1096698, upload-time = "2025-08-12T07:00:29.764Z" }, - { url = "https://files.pythonhosted.org/packages/b8/cb/fe400d8836952cc535c81a0ce47dc6875160e5fedb71d2d9ff0e9894c2a6/sentencepiece-0.2.1-cp314-cp314-win_amd64.whl", hash = "sha256:881b2e44b14fc19feade3cbed314be37de639fc415375cefaa5bc81a4be137fd", size = 1155115, upload-time = "2025-08-12T07:00:32.865Z" }, - { url = "https://files.pythonhosted.org/packages/32/89/047921cf70f36c7b6b6390876b2399b3633ab73b8d0cb857e5a964238941/sentencepiece-0.2.1-cp314-cp314-win_arm64.whl", hash = "sha256:2005242a16d2dc3ac5fe18aa7667549134d37854823df4c4db244752453b78a8", size = 1133890, upload-time = "2025-08-12T07:00:34.763Z" }, - { url = "https://files.pythonhosted.org/packages/a1/11/5b414b9fae6255b5fb1e22e2ed3dc3a72d3a694e5703910e640ac78346bb/sentencepiece-0.2.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:a19adcec27c524cb7069a1c741060add95f942d1cbf7ad0d104dffa0a7d28a2b", size = 1946081, upload-time = "2025-08-12T07:00:36.97Z" }, - { url = "https://files.pythonhosted.org/packages/77/eb/7a5682bb25824db8545f8e5662e7f3e32d72a508fdce086029d89695106b/sentencepiece-0.2.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:e37e4b4c4a11662b5db521def4e44d4d30ae69a1743241412a93ae40fdcab4bb", size = 1327406, upload-time = "2025-08-12T07:00:38.669Z" }, - { url = "https://files.pythonhosted.org/packages/03/b0/811dae8fb9f2784e138785d481469788f2e0d0c109c5737372454415f55f/sentencepiece-0.2.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:477c81505db072b3ab627e7eab972ea1025331bd3a92bacbf798df2b75ea86ec", size = 1254846, upload-time = "2025-08-12T07:00:40.611Z" }, - { url = "https://files.pythonhosted.org/packages/ef/23/195b2e7ec85ebb6a547969f60b723c7aca5a75800ece6cc3f41da872d14e/sentencepiece-0.2.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:010f025a544ef770bb395091d57cb94deb9652d8972e0d09f71d85d5a0816c8c", size = 1315721, upload-time = "2025-08-12T07:00:42.914Z" }, - { url = "https://files.pythonhosted.org/packages/7e/aa/553dbe4178b5f23eb28e59393dddd64186178b56b81d9b8d5c3ff1c28395/sentencepiece-0.2.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:733e59ff1794d26db706cd41fc2d7ca5f6c64a820709cb801dc0ea31780d64ab", size = 1387458, upload-time = "2025-08-12T07:00:44.56Z" }, - { url = "https://files.pythonhosted.org/packages/66/7c/08ff0012507297a4dd74a5420fdc0eb9e3e80f4e88cab1538d7f28db303d/sentencepiece-0.2.1-cp314-cp314t-win32.whl", hash = "sha256:d3233770f78e637dc8b1fda2cd7c3b99ec77e7505041934188a4e7fe751de3b0", size = 1099765, upload-time = "2025-08-12T07:00:46.058Z" }, - { url = "https://files.pythonhosted.org/packages/91/d5/2a69e1ce15881beb9ddfc7e3f998322f5cedcd5e4d244cb74dade9441663/sentencepiece-0.2.1-cp314-cp314t-win_amd64.whl", hash = "sha256:5e4366c97b68218fd30ea72d70c525e6e78a6c0a88650f57ac4c43c63b234a9d", size = 1157807, upload-time = "2025-08-12T07:00:47.673Z" }, - { url = "https://files.pythonhosted.org/packages/f3/16/54f611fcfc2d1c46cbe3ec4169780b2cfa7cf63708ef2b71611136db7513/sentencepiece-0.2.1-cp314-cp314t-win_arm64.whl", hash = "sha256:105e36e75cbac1292642045458e8da677b2342dcd33df503e640f0b457cb6751", size = 1136264, upload-time = "2025-08-12T07:00:49.485Z" }, -] - [[package]] name = "setuptools" version = "81.0.0" @@ -4203,15 +2683,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e1/e3/c164c88b2e5ce7b24d667b9bd83589cf4f3520d97cad01534cd3c4f55fdb/setuptools-81.0.0-py3-none-any.whl", hash = "sha256:fdd925d5c5d9f62e4b74b30d6dd7828ce236fd6ed998a08d81de62ce5a6310d6", size = 1062021, upload-time = "2026-02-06T21:10:37.175Z" }, ] -[[package]] -name = "shellingham" -version = "1.5.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, -] - [[package]] name = "six" version = "1.17.0" @@ -4221,15 +2692,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, ] -[[package]] -name = "soupsieve" -version = "2.8.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7b/ae/2d9c981590ed9999a0d91755b47fc74f74de286b0f5cee14c9269041e6c4/soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349", size = 118627, upload-time = "2026-01-20T04:27:02.457Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016, upload-time = "2026-01-20T04:27:01.012Z" }, -] - [[package]] name = "starlette" version = "1.0.0" @@ -4243,18 +2705,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0b/c9/584bc9651441b4ba60cc4d557d8a547b5aff901af35bda3a4ee30c819b82/starlette-1.0.0-py3-none-any.whl", hash = "sha256:d3ec55e0bb321692d275455ddfd3df75fff145d009685eb40dc91fc66b03d38b", size = 72651, upload-time = "2026-03-22T18:29:45.111Z" }, ] -[[package]] -name = "sympy" -version = "1.14.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mpmath" }, -] -sdist = { url = "https://files.pythonhosted.org/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://files.pythonhosted.org/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 = "tenacity" version = "9.1.4" @@ -4264,45 +2714,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d7/c1/eb8f9debc45d3b7918a32ab756658a0904732f75e555402972246b0b8e71/tenacity-9.1.4-py3-none-any.whl", hash = "sha256:6095a360c919085f28c6527de529e76a06ad89b23659fa881ae0649b867a9d55", size = 28926, upload-time = "2026-02-07T10:45:32.24Z" }, ] -[[package]] -name = "threadpoolctl" -version = "3.6.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/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://files.pythonhosted.org/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 = "tokenizers" -version = "0.22.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "huggingface-hub" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/73/6f/f80cfef4a312e1fb34baf7d85c72d4411afde10978d4657f8cdd811d3ccc/tokenizers-0.22.2.tar.gz", hash = "sha256:473b83b915e547aa366d1eee11806deaf419e17be16310ac0a14077f1e28f917", size = 372115, upload-time = "2026-01-05T10:45:15.988Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/92/97/5dbfabf04c7e348e655e907ed27913e03db0923abb5dfdd120d7b25630e1/tokenizers-0.22.2-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:544dd704ae7238755d790de45ba8da072e9af3eea688f698b137915ae959281c", size = 3100275, upload-time = "2026-01-05T10:41:02.158Z" }, - { url = "https://files.pythonhosted.org/packages/2e/47/174dca0502ef88b28f1c9e06b73ce33500eedfac7a7692108aec220464e7/tokenizers-0.22.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:1e418a55456beedca4621dbab65a318981467a2b188e982a23e117f115ce5001", size = 2981472, upload-time = "2026-01-05T10:41:00.276Z" }, - { url = "https://files.pythonhosted.org/packages/d6/84/7990e799f1309a8b87af6b948f31edaa12a3ed22d11b352eaf4f4b2e5753/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2249487018adec45d6e3554c71d46eb39fa8ea67156c640f7513eb26f318cec7", size = 3290736, upload-time = "2026-01-05T10:40:32.165Z" }, - { url = "https://files.pythonhosted.org/packages/78/59/09d0d9ba94dcd5f4f1368d4858d24546b4bdc0231c2354aa31d6199f0399/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25b85325d0815e86e0bac263506dd114578953b7b53d7de09a6485e4a160a7dd", size = 3168835, upload-time = "2026-01-05T10:40:38.847Z" }, - { url = "https://files.pythonhosted.org/packages/47/50/b3ebb4243e7160bda8d34b731e54dd8ab8b133e50775872e7a434e524c28/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfb88f22a209ff7b40a576d5324bf8286b519d7358663db21d6246fb17eea2d5", size = 3521673, upload-time = "2026-01-05T10:40:56.614Z" }, - { url = "https://files.pythonhosted.org/packages/e0/fa/89f4cb9e08df770b57adb96f8cbb7e22695a4cb6c2bd5f0c4f0ebcf33b66/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c774b1276f71e1ef716e5486f21e76333464f47bece56bbd554485982a9e03e", size = 3724818, upload-time = "2026-01-05T10:40:44.507Z" }, - { url = "https://files.pythonhosted.org/packages/64/04/ca2363f0bfbe3b3d36e95bf67e56a4c88c8e3362b658e616d1ac185d47f2/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df6c4265b289083bf710dff49bc51ef252f9d5be33a45ee2bed151114a56207b", size = 3379195, upload-time = "2026-01-05T10:40:51.139Z" }, - { url = "https://files.pythonhosted.org/packages/2e/76/932be4b50ef6ccedf9d3c6639b056a967a86258c6d9200643f01269211ca/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:369cc9fc8cc10cb24143873a0d95438bb8ee257bb80c71989e3ee290e8d72c67", size = 3274982, upload-time = "2026-01-05T10:40:58.331Z" }, - { url = "https://files.pythonhosted.org/packages/1d/28/5f9f5a4cc211b69e89420980e483831bcc29dade307955cc9dc858a40f01/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:29c30b83d8dcd061078b05ae0cb94d3c710555fbb44861139f9f83dcca3dc3e4", size = 9478245, upload-time = "2026-01-05T10:41:04.053Z" }, - { url = "https://files.pythonhosted.org/packages/6c/fb/66e2da4704d6aadebf8cb39f1d6d1957df667ab24cff2326b77cda0dcb85/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:37ae80a28c1d3265bb1f22464c856bd23c02a05bb211e56d0c5301a435be6c1a", size = 9560069, upload-time = "2026-01-05T10:45:10.673Z" }, - { url = "https://files.pythonhosted.org/packages/16/04/fed398b05caa87ce9b1a1bb5166645e38196081b225059a6edaff6440fac/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:791135ee325f2336f498590eb2f11dc5c295232f288e75c99a36c5dbce63088a", size = 9899263, upload-time = "2026-01-05T10:45:12.559Z" }, - { url = "https://files.pythonhosted.org/packages/05/a1/d62dfe7376beaaf1394917e0f8e93ee5f67fea8fcf4107501db35996586b/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38337540fbbddff8e999d59970f3c6f35a82de10053206a7562f1ea02d046fa5", size = 10033429, upload-time = "2026-01-05T10:45:14.333Z" }, - { url = "https://files.pythonhosted.org/packages/fd/18/a545c4ea42af3df6effd7d13d250ba77a0a86fb20393143bbb9a92e434d4/tokenizers-0.22.2-cp39-abi3-win32.whl", hash = "sha256:a6bf3f88c554a2b653af81f3204491c818ae2ac6fbc09e76ef4773351292bc92", size = 2502363, upload-time = "2026-01-05T10:45:20.593Z" }, - { url = "https://files.pythonhosted.org/packages/65/71/0670843133a43d43070abeb1949abfdef12a86d490bea9cd9e18e37c5ff7/tokenizers-0.22.2-cp39-abi3-win_amd64.whl", hash = "sha256:c9ea31edff2968b44a88f97d784c2f16dc0729b8b143ed004699ebca91f05c48", size = 2747786, upload-time = "2026-01-05T10:45:18.411Z" }, - { url = "https://files.pythonhosted.org/packages/72/f4/0de46cfa12cdcbcd464cc59fde36912af405696f687e53a091fb432f694c/tokenizers-0.22.2-cp39-abi3-win_arm64.whl", hash = "sha256:9ce725d22864a1e965217204946f830c37876eee3b2ba6fc6255e8e903d5fcbc", size = 2612133, upload-time = "2026-01-05T10:45:17.232Z" }, - { url = "https://files.pythonhosted.org/packages/84/04/655b79dbcc9b3ac5f1479f18e931a344af67e5b7d3b251d2dcdcd7558592/tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:753d47ebd4542742ef9261d9da92cd545b2cacbb48349a1225466745bb866ec4", size = 3282301, upload-time = "2026-01-05T10:40:34.858Z" }, - { url = "https://files.pythonhosted.org/packages/46/cd/e4851401f3d8f6f45d8480262ab6a5c8cb9c4302a790a35aa14eeed6d2fd/tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e10bf9113d209be7cd046d40fbabbaf3278ff6d18eb4da4c500443185dc1896c", size = 3161308, upload-time = "2026-01-05T10:40:40.737Z" }, - { url = "https://files.pythonhosted.org/packages/6f/6e/55553992a89982cd12d4a66dddb5e02126c58677ea3931efcbe601d419db/tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:64d94e84f6660764e64e7e0b22baa72f6cd942279fdbb21d46abd70d179f0195", size = 3718964, upload-time = "2026-01-05T10:40:46.56Z" }, - { url = "https://files.pythonhosted.org/packages/59/8c/b1c87148aa15e099243ec9f0cf9d0e970cc2234c3257d558c25a2c5304e6/tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f01a9c019878532f98927d2bacb79bbb404b43d3437455522a00a30718cdedb5", size = 3373542, upload-time = "2026-01-05T10:40:52.803Z" }, -] - [[package]] name = "tomli" version = "2.4.1" @@ -4357,142 +2768,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, ] -[[package]] -name = "torch" -version = "2.12.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cuda-bindings", marker = "sys_platform == 'linux'" }, - { name = "cuda-toolkit", extra = ["cudart", "cufft", "cufile", "cupti", "curand", "cusolver", "cusparse", "nvjitlink", "nvrtc", "nvtx"], marker = "sys_platform == 'linux'" }, - { name = "filelock" }, - { name = "fsspec" }, - { name = "jinja2" }, - { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "networkx", version = "3.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "nvidia-cublas", marker = "sys_platform == 'linux'" }, - { name = "nvidia-cudnn-cu13", marker = "sys_platform == 'linux'" }, - { name = "nvidia-cusparselt-cu13", marker = "sys_platform == 'linux'" }, - { name = "nvidia-nccl-cu13", marker = "sys_platform == 'linux'" }, - { name = "nvidia-nvshmem-cu13", marker = "sys_platform == 'linux'" }, - { name = "setuptools" }, - { name = "sympy" }, - { name = "triton", marker = "sys_platform == 'linux'" }, - { name = "typing-extensions" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/b7/53fe0436586716ab7aecff41e26b9302d57c85ded481fd83a2cd741e6b4e/torch-2.12.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:1834bd984f8a2f4f16bdfbeecca9146184b220aa46276bf5756735b5dae12812", size = 87981887, upload-time = "2026-05-13T14:55:53.234Z" }, - { url = "https://files.pythonhosted.org/packages/34/60/d930eac44c30de06ed16f6d1ba4e785e1632532b50d8f0bf9bf699a4d0c7/torch-2.12.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:d4d029801cb7b6df858804a2a21b00cc2aa0bf0ee5d2ab18d343c9e9e5681f35", size = 426355000, upload-time = "2026-05-13T14:54:31.944Z" }, - { url = "https://files.pythonhosted.org/packages/8e/0c/c76b6a087820bab55705b94dfc074e520de9ae91f5ef90da2ecbf2a3ef12/torch-2.12.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:d47e7dee68ac4cd7a068b26bcd6b989935427709fae1c8f7bd0019978f829e15", size = 532144998, upload-time = "2026-05-13T14:56:05.523Z" }, - { url = "https://files.pythonhosted.org/packages/4a/64/8a0d036e166a6aa85ee09bef072f3655d1ba5d5486a68d1b03b6813c01b3/torch-2.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:cf9839790285dd472e7a16aafcb4a4e6bf58ec1b494045044b0eefb0eb4bd1f2", size = 122949877, upload-time = "2026-05-13T14:55:46.841Z" }, - { url = "https://files.pythonhosted.org/packages/18/62/131124fb95df03811b8260d1d43dcc5ee85ea1a344b964613d7efe77fb08/torch-2.12.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:10802fd383bbfed646212e765a72c37d2185205d4f26eb197a254e8ac7ddcb25", size = 87990344, upload-time = "2026-05-13T14:55:42.154Z" }, - { url = "https://files.pythonhosted.org/packages/12/9c/dda0dbd547dc549839824135f223792fd0e725f28ed0715dda366b7acaa2/torch-2.12.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:c12592630aef72feaf18bd3f197ef587bbfa21131b31c38b23ab2e55fce92e36", size = 426362932, upload-time = "2026-05-13T14:54:15.295Z" }, - { url = "https://files.pythonhosted.org/packages/e2/d2/a7dd5a3f9bdaa7842124e8e2359202b317c48d47d2fc5816fafdf2049adb/torch-2.12.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:415c1b8d0412f67551c8e89a2daca0fb3e56694af0281ba155eaa9da481f58b4", size = 532170085, upload-time = "2026-05-13T14:55:20.788Z" }, - { url = "https://files.pythonhosted.org/packages/12/1b/a61ce2004f9ab0ea8964a6e6168133a127795667639e2ff4f8f2bdb16a65/torch-2.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:dd37188ea325042cb1f6cafa56822b11ada2520c04791a52629b0af25bdfbfd9", size = 122953128, upload-time = "2026-05-13T14:54:52.744Z" }, - { url = "https://files.pythonhosted.org/packages/ef/bb/285d643f254731294c9b595a007eac39db4600a98682d7bca688f42ca164/torch-2.12.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:b41339df93d491435e790ff8bcbae1c0ce777175889bfd1281d119862793e6a2", size = 88010197, upload-time = "2026-05-13T14:55:35.414Z" }, - { url = "https://files.pythonhosted.org/packages/79/81/76debf1db1343bd929bbb5d74c89fb437c2ed88eb144712557e7bd3eea45/torch-2.12.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:8fbef9f108a863e7722a73740998967e3b074742a834fc5be3a535a2befa7057", size = 426376751, upload-time = "2026-05-13T14:55:03.353Z" }, - { url = "https://files.pythonhosted.org/packages/de/f0/80026028b603c4650ff270fc3785bdef4bd6738765a9cc5a0f5a637d65a2/torch-2.12.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:4b4f64c2c2b11f7510d93dd6412b87025ff6eddd6bb61c3b5a3d892ea20c4756", size = 532261691, upload-time = "2026-05-13T14:52:54.453Z" }, - { url = "https://files.pythonhosted.org/packages/b9/c2/64b06cbb7830fb3cd9be13e1158b31a3f36b68e6a209105ee3c9d9480be0/torch-2.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:8b958caff4a14d3a3b0b2dfc6a378f64dda9728a9dad28c08a0db9ce4dafb549", size = 122988114, upload-time = "2026-05-13T14:54:42.153Z" }, - { url = "https://files.pythonhosted.org/packages/86/ca/01896c80ba921676aa45886b2c5b8d774912de2a1f719de48169c6f755cd/torch-2.12.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:90dd587a5f61bfe1307148b581e2084fc5bc4a06e2b90a20e9a36b81087ff16b", size = 88009511, upload-time = "2026-05-13T14:54:47.411Z" }, - { url = "https://files.pythonhosted.org/packages/a5/04/52bdaf4787eab6ac7d7f5851dff934e4def0bc8ead9c8fd2b69b3e529699/torch-2.12.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:864392c73b7654f4d2b3ae712f607937d0dbb1101c4555fbb41848106b297f39", size = 426383231, upload-time = "2026-05-13T14:53:32.129Z" }, - { url = "https://files.pythonhosted.org/packages/49/8a/94bdecd13f5aaa90d45920b89789d9fe7c6f4af8c3cdd7ce01fcb59908fc/torch-2.12.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:5d6b560dfa7d56291c07d615c3bb73e8d9943d9b6d87f76cd0d9d570c4797fa6", size = 532269288, upload-time = "2026-05-13T14:53:49.423Z" }, - { url = "https://files.pythonhosted.org/packages/3e/2f/bdbaaa267de519ef1b73054bf590d8c93c37a266c9a4e24a01bd38b6918f/torch-2.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:3fee918902090ade827643e758e98363278815de583c75d111fdd665ebffde9f", size = 122987706, upload-time = "2026-05-13T14:54:00.335Z" }, - { url = "https://files.pythonhosted.org/packages/9b/ad/e95e822f3538171e22640a7fbe839a1fdb666600bf6487025de2ff03b11a/torch-2.12.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:10ee1448a9f304d3b987eb4656f664ba6e4d7b410ca7a5a7c642199777a2cf88", size = 88319556, upload-time = "2026-05-13T14:54:05.574Z" }, - { url = "https://files.pythonhosted.org/packages/b7/07/055d06d985b445d67422d25b033c11cf55bbb81785d4c4e68e28bca5820e/torch-2.12.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:af68dbf403439cae9ceaeaaf92f8352b460787dcd27b92aa05c40dd4a19c0f1e", size = 426397656, upload-time = "2026-05-13T14:52:38.84Z" }, - { url = "https://files.pythonhosted.org/packages/43/94/b0b4fdc3014122e0a7302fb90086d352aa48f2576f0b252561ebb38c01a8/torch-2.12.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:a6a2eebb237d3b1d9ad3b378e86d9b9e0782afdea8b1e0eba6a13646b9b49c07", size = 532183124, upload-time = "2026-05-13T14:53:16.178Z" }, - { url = "https://files.pythonhosted.org/packages/d8/c8/052405e6ad05d3237bfe5a4df78f917773956f8e17813a2d44c059068b74/torch-2.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2140e373e9a51a3e22ef62e8d14366d0b470d18f0adf19fdc757368077133a34", size = 123232462, upload-time = "2026-05-13T14:52:27.26Z" }, - { url = "https://files.pythonhosted.org/packages/67/dc/ac069f8d6e8be701535921141055293b0d4819d3d7f224a4612cf157c7f9/torch-2.12.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:f7dfae4a519197dfa050e98d8e36378a0fb5899625a875c2b54445005a2e404e", size = 88027282, upload-time = "2026-05-13T14:53:05.258Z" }, - { url = "https://files.pythonhosted.org/packages/33/c3/1c1eb00e34555b536dddf792676026a988d710ed36981aa00499b36b0620/torch-2.12.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:891c769072637c74e9a5a77a3bc782894696d8ffec83b938df8536dee7f0ba78", size = 426386961, upload-time = "2026-05-13T14:51:28.406Z" }, - { url = "https://files.pythonhosted.org/packages/cd/d4/7e730dba0c7032a4154dc9056b76cf9625515e030e269cfbf8098fcfee7d/torch-2.12.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:e2ad3eb85d39c3cab62dfa93ed5a73516e6a53c6713cb97d004004fe089f0f1f", size = 532272265, upload-time = "2026-05-13T14:51:59.308Z" }, - { url = "https://files.pythonhosted.org/packages/f1/b4/92c80d1bbfee1c0036c06d1d2155a3065bd2423134c83bf8a47e65cd6b9b/torch-2.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:c66696857e987efb8bc1777a37357ec4f60ab5e8af6250b83d6034437fa2d8f3", size = 122987138, upload-time = "2026-05-13T14:51:45.942Z" }, - { url = "https://files.pythonhosted.org/packages/7b/78/2e12b37ce50a19a037d7bc62d652a5a8f27385a7b05859d6bc9204f20cfe/torch-2.12.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:b4556715c8572758625d62b6e0ae3b1f76c440221913a6fb5e100f321fb4fb02", size = 88320100, upload-time = "2026-05-13T14:51:39.955Z" }, - { url = "https://files.pythonhosted.org/packages/56/5e/83c450ec7b0bb40a7b74611c1b5440f9260e33c54c90d556fd4a1f0fd955/torch-2.12.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:a43ac605a5e13116c72b64c359644cce0229f213dde48d2ae0ae5eb5becf7feb", size = 426391871, upload-time = "2026-05-13T14:52:14.989Z" }, - { url = "https://files.pythonhosted.org/packages/c9/e9/1a0b575d98d0afedd8f157d23fa3d2759421483660448e60d0a4b10b6daa/torch-2.12.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:6a7512adfdd7f6732e40de1c620831e3c75b39b98cef60b11d0c5f0a76473ec5", size = 532192241, upload-time = "2026-05-13T14:51:07.795Z" }, - { url = "https://files.pythonhosted.org/packages/88/21/afadd25ecd81b3cea1e11c73cf1ab41a983a50271548c3ec7ec3b9efc3e9/torch-2.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:5f96b63f8287f66a005dd1b5a6abba2920f11156c5e5c4d815f3e2050fd1aa16", size = 123231092, upload-time = "2026-05-13T14:51:18.854Z" }, -] - -[[package]] -name = "tqdm" -version = "4.67.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" }, -] - -[[package]] -name = "transformers" -version = "5.8.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "huggingface-hub" }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "packaging" }, - { name = "pyyaml" }, - { name = "regex" }, - { name = "safetensors" }, - { name = "tokenizers" }, - { name = "tqdm" }, - { name = "typer" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e7/e6/4134ea2fbea322cddc7ffc94a0d8ee47fe32ce8e876b320cd37d88edfc4d/transformers-5.8.1.tar.gz", hash = "sha256:4dd5b6de4105725104d84fd6abd74b305f4debfc251b38c648ee5dd087cf543b", size = 8532019, upload-time = "2026-05-13T03:21:57.234Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/b1/8be7e7ef0b5200491312201918b6125ef9c9df9dd0f0240ccef9ac824e6b/transformers-5.8.1-py3-none-any.whl", hash = "sha256:5340fb95962162cdfdae5cc91d7f8fedd92ed75216c1154c5e1f590fcf56dd0e", size = 10632882, upload-time = "2026-05-13T03:21:52.876Z" }, -] - -[[package]] -name = "trec-car-tools" -version = "2.6" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cbor" }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/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://files.pythonhosted.org/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 = "triton" -version = "3.7.0" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3e/97/dcd1f2a0f8336691bff74abc59b2ed9c69a0c0f8f65cd77109c49e05f068/triton-3.7.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223ac302091491436c248a34ee1e6c47a1026486579103c906ffd805be50cb89", size = 188367104, upload-time = "2026-05-07T19:04:56.68Z" }, - { url = "https://files.pythonhosted.org/packages/b2/c0/c2ac4fd2d8809b7579d4a820a0f9e5de62a9bc8a757ed4b3abf4f7ee964a/triton-3.7.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c631b65668d4951213b948a413c0564184305b77bb45cc9d686d3e1ecc4701a3", size = 201313191, upload-time = "2026-05-07T18:45:58.444Z" }, - { url = "https://files.pythonhosted.org/packages/b8/c1/5d842314bb6c78442cc60437928781701c6050b8d479bc2a1aed691d37ca/triton-3.7.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a9e71fc392675fac364e0ecf4ef3f76f85b7f5433a16f4c3c5fe5f05a52c85fe", size = 188480277, upload-time = "2026-05-07T19:05:03.231Z" }, - { url = "https://files.pythonhosted.org/packages/13/31/8315ea5f8dd18e60970b3022e3a8b93fd37e0b784fbbef86e10c8e6e5ca1/triton-3.7.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22bacffce443f54593dd20f05294d5a40622e0ea9ab632816f87154504356221", size = 201415942, upload-time = "2026-05-07T18:46:06.479Z" }, - { url = "https://files.pythonhosted.org/packages/f7/13/ec05adfcd87311d532ba61e3af143e8be59fcd26675884c4682841406a20/triton-3.7.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4bf49b00a7a377a68a6da603a876e797614e6455a80e9021669c476a953ad9a", size = 188505104, upload-time = "2026-05-07T19:05:09.843Z" }, - { url = "https://files.pythonhosted.org/packages/62/7b/468a576e35beef1426e0828e28e9ba9e65f5474d496f16ee126c15646324/triton-3.7.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f111161d49bf903c0eaedde3962353a3d841c08a836839b7cc1025b8426efcf", size = 201457567, upload-time = "2026-05-07T18:46:13.505Z" }, - { url = "https://files.pythonhosted.org/packages/01/e1/a59a583de59b8f62c495d67c80ee3ea97d09e91ac80c4c6e76456ed8d8ac/triton-3.7.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:abdf6beaa89b1bcfb9a43cd990536ce66091a997841a4814b260b7bee4c88c3c", size = 188503209, upload-time = "2026-05-07T19:05:17.935Z" }, - { url = "https://files.pythonhosted.org/packages/30/b1/b7507bb9815d403927c8dd51d4158ed2e11751a92dbc118a044f247b6848/triton-3.7.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a35d7afe3f3f058e7ec49fcce09794049e0ffc5c59019ac25ec3413741b8c4e7", size = 201453566, upload-time = "2026-05-07T18:46:20.427Z" }, - { url = "https://files.pythonhosted.org/packages/a6/8f/0bea7a6a0c989315c9135a1d7fb37e41905cfb3a17cbc1f10044ebd4cc3a/triton-3.7.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc1d61c172d257db80ddf42595131fb196ad2e9bdd751e90fe2ef13531734e8b", size = 188612899, upload-time = "2026-05-07T19:05:24.955Z" }, - { url = "https://files.pythonhosted.org/packages/e1/02/d96f57828d0912aec733b9bc7e0e7dbfd2c6f079a8fa433ac25cb93d1a30/triton-3.7.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:70fb9bbdc9f400afc54bbf6eb2670af28829a6ae3996863317964783141daf56", size = 201553816, upload-time = "2026-05-07T18:46:27.49Z" }, - { url = "https://files.pythonhosted.org/packages/40/fb/82a802dac4689f2a2fb2e69302e6a138eecc3e175bbe976ba3cfc717683a/triton-3.7.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a44a8476d0d3571eac4e4d1048e1ff75aad81a09ff4602ccfc56c6dea1672e", size = 188507879, upload-time = "2026-05-07T19:05:32.209Z" }, - { url = "https://files.pythonhosted.org/packages/8f/af/9904ec6d3c93d9b24e5ec360445bbdf758b7f00bfbeedb89cb0eb64eb8bb/triton-3.7.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b9b85e72968a9d8bba5ddb24e9b64aaabaf48affb042f2755cb7cfa92b7531ce", size = 201460637, upload-time = "2026-05-07T18:46:34.749Z" }, - { url = "https://files.pythonhosted.org/packages/a1/f9/4835a8ea746b88727d8899f4e3ccce4f9cacb38abfc3bb0a638266c53111/triton-3.7.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18a160de426fd99f92b0baf509045360afbd3bfaa0b4a5171dde800ec9f09684", size = 188608706, upload-time = "2026-05-07T19:05:39.218Z" }, - { url = "https://files.pythonhosted.org/packages/c1/68/fa86e5a39608000f645535b2c124920126327ab731f8c4fafd5b07ff8d4b/triton-3.7.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce061073102714b725f3660ec6939d94a1da7984b3aa99c921417cae273672f5", size = 201546766, upload-time = "2026-05-07T18:46:42.088Z" }, -] - -[[package]] -name = "typer" -version = "0.25.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "annotated-doc" }, - { name = "click" }, - { name = "rich" }, - { name = "shellingham" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e4/51/9aed62104cea109b820bbd6c14245af756112017d309da813ef107d42e7e/typer-0.25.1.tar.gz", hash = "sha256:9616eb8853a09ffeabab1698952f33c6f29ffdbceb4eaeecf571880e8d7664cc", size = 122276, upload-time = "2026-04-30T19:32:16.964Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/f9/2b3ff4e56e5fa7debfaf9eb135d0da96f3e9a1d5b27222223c7296336e5f/typer-0.25.1-py3-none-any.whl", hash = "sha256:75caa44ed46a03fb2dab8808753ffacdbfea88495e74c85a28c5eefcf5f39c89", size = 58409, upload-time = "2026-04-30T19:32:18.271Z" }, -] - [[package]] name = "typing-extensions" version = "4.15.0" @@ -4535,15 +2810,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-py3-none-any.whl", hash = "sha256:eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d", size = 18026, upload-time = "2025-03-05T21:17:39.857Z" }, ] -[[package]] -name = "unlzw3" -version = "0.2.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/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://files.pythonhosted.org/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 = "urllib3" version = "2.7.0" @@ -4744,21 +3010,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/03/ff/7c0c86c43b3cbb927e0ccc0255cb4057ceba4799cd44ae95174ce8e8b5b2/vine-5.1.0-py3-none-any.whl", hash = "sha256:40fdf3c48b2cfe1c38a49e9ae2da6fda88e4794c810050a728bd7413811fb1dc", size = 9636, upload-time = "2023-11-05T08:46:51.205Z" }, ] -[[package]] -name = "warc3-wet" -version = "0.2.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/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://files.pythonhosted.org/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.org/simple" } -sdist = { url = "https://files.pythonhosted.org/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 = "watchfiles" version = "1.1.1" @@ -4948,12 +3199,6 @@ wheels = [ { url = "https://files.pythonhosted.org/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 = "wordninja" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/30/15/abe4af50f4be92b60c25e43c1c64d08453b51e46c32981d80b3aebec0260/wordninja-2.0.0.tar.gz", hash = "sha256:1a1cc7ec146ad19d6f71941ee82aef3d31221700f0d8bf844136cf8df79d281a", size = 541572, upload-time = "2019-08-10T02:16:54.944Z" } - [[package]] name = "xxhash" version = "3.7.0" @@ -5251,25 +3496,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/69/68/c8739671f5699c7dc470580a4f821ef37c32c4cb0b047ce223a7f115757f/yarl-1.23.0-py3-none-any.whl", hash = "sha256:a2df6afe50dea8ae15fa34c9f824a3ee958d785fd5d089063d960bae1daa0a3f", size = 48288, upload-time = "2026-03-01T22:07:51.388Z" }, ] -[[package]] -name = "zlib-state" -version = "0.1.12" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/53/f2/c1b3dfb76ff3eac8eb2a96eb1e8e05c13534c73f0d1d582dfadadb5a863a/zlib_state-0.1.12.tar.gz", hash = "sha256:ccbb06321daf165b022aa4d22d62effb7df76f55035d50bbe8b93696db416cf0", size = 9618, upload-time = "2026-04-14T19:08:30.079Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/a6/94a37b05fccfd3ff506535fb2537ea4e2a49e7eb8db761b4d826aa26bf79/zlib_state-0.1.12-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ba18c237c0c8176c6a68965a990f32980cc734f51cee16c997fcf83908505ce", size = 21421, upload-time = "2026-04-14T19:09:44.387Z" }, - { url = "https://files.pythonhosted.org/packages/8d/6e/a10048a6246efd69b96eeadc46a9bd9172aea8562ce6e9009e3bffd49cfd/zlib_state-0.1.12-cp310-cp310-win_amd64.whl", hash = "sha256:25ccf55445d9ebb6e3b89606a50da984a6f9a84ecfe52bb9841a925701ec5b53", size = 12777, upload-time = "2026-04-14T19:09:55.685Z" }, - { url = "https://files.pythonhosted.org/packages/cc/bf/c1c1eaab3a127f58c27139901eaa5e16b779d4a6896719a09d239887d8da/zlib_state-0.1.12-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1e37d5877273a7cfdc0f1a30c7efe50c95e617d1ff716ac88e2ea7589088a88e", size = 21451, upload-time = "2026-04-14T19:09:45.068Z" }, - { url = "https://files.pythonhosted.org/packages/26/02/fa5b6523a4cc3f52192a77a4f3d58563b200643d2408ed29e9808b17bd9b/zlib_state-0.1.12-cp311-cp311-win_amd64.whl", hash = "sha256:572a2a7984eba730cec204a906fb8d41fc1022b4818797c97c503acb213caef0", size = 12776, upload-time = "2026-04-14T19:10:04.205Z" }, - { url = "https://files.pythonhosted.org/packages/bb/20/b26f462b948ea8e66f878502c9594a32eda4e8ad96d9943667f4fe0f7a91/zlib_state-0.1.12-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:91a0b6c7007ed4bd3162ac33114ceea62c28b24cd585efe3a10d2626576b445a", size = 22006, upload-time = "2026-04-14T19:09:45.838Z" }, - { url = "https://files.pythonhosted.org/packages/da/7d/47646b78823a16d6dc055b89a16bc04b80f9df979cfa0765470a2bed8417/zlib_state-0.1.12-cp312-cp312-win_amd64.whl", hash = "sha256:f1d38461b99af652575f50cfc334f0958dbfef541679199f4ef09877cf9509c4", size = 12804, upload-time = "2026-04-14T19:09:18.848Z" }, - { url = "https://files.pythonhosted.org/packages/6c/09/bafb78fbd0471aadeb96c4d79dc76b25c172153935563357b250c1f0d80b/zlib_state-0.1.12-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:940674e4b4d6c4df7e3be75be48c6ad1f07c0f42cab2d0b5655f67b8f0532073", size = 22049, upload-time = "2026-04-14T19:09:46.601Z" }, - { url = "https://files.pythonhosted.org/packages/31/0d/76a524ee21b62cecd00c469d5cf1093ec2310684ba9e2cdbf6d323681d0f/zlib_state-0.1.12-cp313-cp313-win_amd64.whl", hash = "sha256:6466325e9e20a7e30b0c0291024fef2cb80a534324393609b5b30db42c065d1b", size = 12803, upload-time = "2026-04-14T19:09:16.351Z" }, - { url = "https://files.pythonhosted.org/packages/d0/ea/fd777b721efb8df2a7910cdfa4c551c26c2a2667440148b03fb6227d10b1/zlib_state-0.1.12-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d743776b126e3bb7843f0f4fa428954ea16d9c29dfa933cf3e6085ef7ad6392c", size = 22060, upload-time = "2026-04-14T19:09:47.315Z" }, - { url = "https://files.pythonhosted.org/packages/d4/b2/4980c6a03180c25e5ff10ba068fde1ec81a42728ea42f4dc3cae3f21a82b/zlib_state-0.1.12-cp314-cp314-win_amd64.whl", hash = "sha256:e3ed3230ae87770b3a809f987aa21d37a9063b102d3b9ef553fa871eb900638b", size = 13031, upload-time = "2026-04-14T19:09:48.409Z" }, - { url = "https://files.pythonhosted.org/packages/59/a4/f59dc9cbfc704cf5357bbc40d5ae0faf9358fe4b464276332e14df90e38e/zlib_state-0.1.12-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:360054c4c640b4e74a807a5739bcf99451b410892f15723d4a1cb56352a43e54", size = 24114, upload-time = "2026-04-14T19:09:48.036Z" }, -] - [[package]] name = "zstandard" version = "0.25.0"