diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index f2edb5f..bc23ae0 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -9,14 +9,10 @@ on: env: REGISTRY: crretoxmas2024.azurecr.io NAMESPACE: reto-xmas-2025-goland-ia-backend - ROLLOUT_TIMEOUT: 60s - READY_CHECK_RETRIES: 20 - READY_CHECK_SLEEP: 15 jobs: build-and-deploy: runs-on: ubuntu-latest - timeout-minutes: 15 strategy: fail-fast: false matrix: @@ -29,7 +25,7 @@ jobs: path: ./RAGManager image: reto-xmas-2025-goland-ia-backend-rag-manager deployment: rag-manager - + steps: - name: Checkout code uses: actions/checkout@v4 @@ -55,7 +51,6 @@ jobs: ${{ env.REGISTRY }}/${{ matrix.service.image }}:${{ github.sha }} cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ matrix.service.image }}:buildcache cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ matrix.service.image }}:buildcache,mode=max - provenance: false - name: Set up kubectl uses: azure/setup-kubectl@v3 @@ -68,78 +63,28 @@ jobs: echo "${{ secrets.KUBECONFIG }}" | base64 -d > $HOME/.kube/config chmod 600 $HOME/.kube/config - - name: Update deployment image + - name: Restart deployment run: | - kubectl set image deployment/${{ matrix.service.deployment }} \ - api=${{ env.REGISTRY }}/${{ matrix.service.image }}:${{ github.sha }} \ - -n ${{ env.NAMESPACE }} + kubectl rollout restart deployment/${{ matrix.service.deployment }} -n ${{ env.NAMESPACE }} + kubectl rollout status deployment/${{ matrix.service.deployment }} -n ${{ env.NAMESPACE }} --timeout=5m - - name: Wait for deployment to be ready (robust) + - name: Verify deployment run: | - set -e - - echo "Checking rollout status (non-blocking)..." - kubectl rollout status deployment/${{ matrix.service.deployment }} \ - -n ${{ env.NAMESPACE }} \ - --timeout=${{ env.ROLLOUT_TIMEOUT }} || true - - echo "Waiting for available replicas..." - - for i in $(seq 1 $READY_CHECK_RETRIES); do - DESIRED=$(kubectl get deployment/${{ matrix.service.deployment }} -n ${{ env.NAMESPACE }} -o jsonpath='{.spec.replicas}') - AVAILABLE=$(kubectl get deployment/${{ matrix.service.deployment }} -n ${{ env.NAMESPACE }} -o jsonpath='{.status.availableReplicas}') - - echo "Attempt $i: $AVAILABLE / $DESIRED replicas available" - - if [ "$AVAILABLE" = "$DESIRED" ]; then - echo "Deployment is ready" - exit 0 - fi - - sleep $READY_CHECK_SLEEP - done - - echo "Deployment did not become ready in time" - kubectl describe deployment/${{ matrix.service.deployment }} -n ${{ env.NAMESPACE }} - exit 1 - - - name: Verify pods - if: success() - run: | - kubectl get pods -n ${{ env.NAMESPACE }} -l app=${{ matrix.service.deployment }} -o wide - - - name: Get logs on failure - if: failure() - run: | - echo "=== Deployment ===" - kubectl describe deployment/${{ matrix.service.deployment }} -n ${{ env.NAMESPACE }} - - echo "=== Pods ===" - kubectl get pods -n ${{ env.NAMESPACE }} -l app=${{ matrix.service.deployment }} -o wide - - echo "=== Pod Logs ===" - kubectl logs -n ${{ env.NAMESPACE }} \ - -l app=${{ matrix.service.deployment }} \ - --tail=100 \ - --all-containers=true \ - --prefix=true || true + echo "✅ Deployment successful for ${{ matrix.service.name }}" + kubectl get pods -n ${{ env.NAMESPACE }} -l app=${{ matrix.service.deployment }} - name: Deployment Summary if: always() run: | - STATUS="${{ job.status }}" - - echo "### Deployment - ${{ matrix.service.name }}" >> $GITHUB_STEP_SUMMARY + echo "### 🚀 Deployment Summary" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY - echo "| Property | Value |" >> $GITHUB_STEP_SUMMARY - echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY - echo "| Image | \`${{ env.REGISTRY }}/${{ matrix.service.image }}:${{ github.sha }}\` |" >> $GITHUB_STEP_SUMMARY - echo "| Status | $STATUS |" >> $GITHUB_STEP_SUMMARY + echo "**Service:** ${{ matrix.service.name }}" >> $GITHUB_STEP_SUMMARY + echo "**Image:** ${{ env.REGISTRY }}/${{ matrix.service.image }}:${{ github.sha }}" >> $GITHUB_STEP_SUMMARY + echo "**Status:** ${{ job.status }}" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY - echo "#### Pods:" >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY - kubectl get pods -n ${{ env.NAMESPACE }} -l app=${{ matrix.service.deployment }} >> $GITHUB_STEP_SUMMARY || true + kubectl get pods -n ${{ env.NAMESPACE }} -l app=${{ matrix.service.deployment }} >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY notify-success: @@ -150,34 +95,8 @@ jobs: steps: - name: Success Summary run: | - echo "### All Services Deployed" >> $GITHUB_STEP_SUMMARY + echo "### ✅ Deployment Successful!" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY - echo "**Live URLs:**" >> $GITHUB_STEP_SUMMARY - echo "- [DocsManager](https://goland-ia-backend-docs-manager.reto-ucu.net/docs)" >> $GITHUB_STEP_SUMMARY - echo "- [RAGManager](https://goland-ia-backend-rag-manager.reto-ucu.net/docs)" >> $GITHUB_STEP_SUMMARY - - - name: Notificar a Discord (deploy exitoso) - run: | - curl -H "Content-Type: application/json" \ - -X POST \ - -d "{\"content\": \"✅ **Deploy exitoso**\\n\\n**Servicio:** \\\\`${{ matrix.service.name }}\\\\`\\n**Imagen:** \\\\`${{ env.REGISTRY }}/${{ matrix.service.image }}:${{ github.sha }}\\\\`\\n**Namespace:** \\\\`${{ env.NAMESPACE }}\\\\`\\n**Autor:** ${{ github.actor }}\\n**Commit:** [${{ github.sha }}](${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }})\\n\\n[Ver ejecución en GitHub Actions](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})\\n\\n:rocket: ¡Todo salió bien!\"}" \ - https://discord.com/api/webhooks/1449147972330852463/sMs3QYOvQ6oiaPHR4PlBluwNrhWSsVmfm3_Miz6UAFrPxj1SsbqNnHXc5eK9h8ZSaumk - - notify-failure: - name: Deployment Failed - runs-on: ubuntu-latest - needs: [build-and-deploy] - if: failure() - steps: - - name: Failure Summary - run: | - echo "### Deployment Failed" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "Please check deployment logs above." >> $GITHUB_STEP_SUMMARY - - - name: Notificar a Discord (deploy fallido) - run: | - curl -H "Content-Type: application/json" \ - -X POST \ - -d "{\"content\": \"❌ **Falló el deploy**\\n\\n**Servicio:** \\\\`${{ matrix.service.name }}\\\\`\\n**Imagen:** \\\\`${{ env.REGISTRY }}/${{ matrix.service.image }}:${{ github.sha }}\\\\`\\n**Namespace:** \\\\`${{ env.NAMESPACE }}\\\\`\\n**Autor:** ${{ github.actor }}\\n**Commit:** [${{ github.sha }}](${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }})\\n\\n[Ver ejecución en GitHub Actions](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})\\n\\n:warning: Revisa los logs para más detalles.\"}" \ - https://discord.com/api/webhooks/1449147972330852463/sMs3QYOvQ6oiaPHR4PlBluwNrhWSsVmfm3_Miz6UAFrPxj1SsbqNnHXc5eK9h8ZSaumk \ No newline at end of file + echo "All services deployed successfully:" >> $GITHUB_STEP_SUMMARY + echo "- 🌐 DocsManager: https://goland-ia-backend-docs-manager.reto-ucu.net" >> $GITHUB_STEP_SUMMARY + echo "- 🌐 RAGManager: https://goland-ia-backend-rag-manager.reto-ucu.net" >> $GITHUB_STEP_SUMMARY \ No newline at end of file diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml index 8a084f6..f78f51c 100644 --- a/.github/workflows/pr-validation.yml +++ b/.github/workflows/pr-validation.yml @@ -2,7 +2,6 @@ name: PR Validation on: pull_request: - types: [opened] branches: - main @@ -67,13 +66,6 @@ jobs: severity: 'CRITICAL,HIGH' exit-code: '0' - - name: SonarQube Scan - uses: sonarsource/sonarqube-scan-action@v2 - with: - host: https://sonarqube.reto-ucu.net/ - login: ${{ secrets.SONAR_TOKEN }} - projectKey: reto-xmas-2025-goland-ia-backend - pr-summary: name: PR Summary runs-on: ubuntu-latest @@ -104,16 +96,4 @@ jobs: owner: context.repo.owner, repo: context.repo.repo, body: message - }); - - discord-pr-notify: - name: Notificar PR en Discord - runs-on: ubuntu-latest - if: github.event_name == 'pull_request' - steps: - - name: Notificar a Discord (nuevo PR) - run: | - curl -H "Content-Type: application/json" \ - -X POST \ - -d "{\"content\": \"🔔 Nuevo Pull Request: [${{ github.event.pull_request.title }}](${{ github.event.pull_request.html_url }}) por ${{ github.event.pull_request.user.login }}\"}" \ - https://discord.com/api/webhooks/1449147972330852463/sMs3QYOvQ6oiaPHR4PlBluwNrhWSsVmfm3_Miz6UAFrPxj1SsbqNnHXc5eK9h8ZSaumk \ No newline at end of file + }); \ No newline at end of file diff --git a/DocsManager/.python-version b/DocsManager/.python-version index 24ee5b1..6324d40 100644 --- a/DocsManager/.python-version +++ b/DocsManager/.python-version @@ -1 +1 @@ -3.13 +3.14 diff --git a/DocsManager/Dockerfile b/DocsManager/Dockerfile index a8ac8ea..5fb3d6e 100644 --- a/DocsManager/Dockerfile +++ b/DocsManager/Dockerfile @@ -1,26 +1,13 @@ -FROM python:3.12-slim +FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim WORKDIR /app -RUN apt-get update && apt-get install -y \ - curl \ - && rm -rf /var/lib/apt/lists/* - -RUN curl -LsSf https://astral.sh/uv/install.sh | sh && \ - mv /root/.local/bin/uv /usr/local/bin/uv && \ - mv /root/.local/bin/uvx /usr/local/bin/uvx - +COPY pyproject.toml uv.lock* ./ +RUN uv sync --frozen --no-cache || uv sync --no-cache COPY . . -RUN uv sync --no-dev --no-cache \ - && uv pip list \ - && uv pip show fastapi uvicorn - EXPOSE 8000 -HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ - CMD curl -f http://localhost:8000/health || exit 1 - -CMD ["uv", "run", "--no-sync", "python", "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] \ No newline at end of file +CMD ["uv", "run", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] \ No newline at end of file diff --git a/DocsManager/app/api/routes/chatMessage.py b/DocsManager/app/api/routes/chatMessage.py index 944fb8d..343627b 100644 --- a/DocsManager/app/api/routes/chatMessage.py +++ b/DocsManager/app/api/routes/chatMessage.py @@ -1,9 +1,10 @@ from fastapi import APIRouter, Depends +from fastapi.responses import StreamingResponse from sqlalchemy.orm import Session from app.core.db_connection import get_db -from app.schemas.chatMessage import UserMessageIn, AssistantMessageOut -from app.services.chatMessage import create_user_message +from ag_ui.core import RunAgentInput +from app.services.chatMessage import process_agent_message router = APIRouter( @@ -14,19 +15,15 @@ @router.post( "/messages", - response_model=AssistantMessageOut ) -def post_user_message( - payload: UserMessageIn, +async def post_user_message( + payload: RunAgentInput, db: Session = Depends(get_db) ): - assistant_msg, session_id = create_user_message( - db=db, - message=payload.message, - session_id=payload.session_id + """Handle chat messages using the AG-UI protocol. + + This endpoint accepts RunAgentInput and returns a stream of AG-UI events. + """ + return StreamingResponse( + process_agent_message(db, payload) ) - - return { - "session_id": session_id, - "message": assistant_msg.message - } diff --git a/DocsManager/app/core/config.py b/DocsManager/app/core/config.py index 6853b4a..5c15864 100644 --- a/DocsManager/app/core/config.py +++ b/DocsManager/app/core/config.py @@ -1,5 +1,5 @@ from pydantic_settings import BaseSettings, SettingsConfigDict -from pydantic import field_validator, model_validator, Field +from pydantic import field_validator, model_validator from typing import Optional from urllib.parse import quote_plus import logging @@ -29,6 +29,7 @@ class Settings(BaseSettings): minio_access_key: str minio_secret_key: str minio_bucket: str = "documents" + minio_folder: str = "rag-docs" minio_use_ssl: bool = True # Database Configuration (for SQLAlchemy) diff --git a/DocsManager/app/schemas/chatMessage.py b/DocsManager/app/schemas/chatMessage.py deleted file mode 100644 index e900b7a..0000000 --- a/DocsManager/app/schemas/chatMessage.py +++ /dev/null @@ -1,12 +0,0 @@ -from pydantic import BaseModel -from uuid import UUID -from typing import Optional - -class UserMessageIn(BaseModel): - session_id: Optional[UUID] = None - message: str - - -class AssistantMessageOut(BaseModel): - session_id: UUID - message: str diff --git a/DocsManager/app/services/chatMessage.py b/DocsManager/app/services/chatMessage.py index 26a943a..5f87f1e 100644 --- a/DocsManager/app/services/chatMessage.py +++ b/DocsManager/app/services/chatMessage.py @@ -1,8 +1,25 @@ from sqlalchemy.orm import Session +from typing import AsyncIterator +from uuid import uuid4 from app.models.chat_message import ChatMessage from app.schemas.enums.sender_type import SenderType from app.models.chat_session import ChatSession +from ag_ui.core import ( + RunAgentInput, + RunStartedEvent, + TextMessageStartEvent, + TextMessageContentEvent, + TextMessageEndEvent, + RunFinishedEvent, + MessagesSnapshotEvent, + AssistantMessage, + TextInputContent +) +from ag_ui.encoder import ( + EventEncoder +) + def assistant_reply(text: str) -> str: return f"Respuesta del asistente a: {text}" @@ -14,7 +31,7 @@ def create_user_message( session_id=None ): # 1. Si no hay session_id → crear sesión nueva - if session_id is None: + if not session_id: session = ChatSession() db.add(session) db.flush() # genera el UUID sin commit @@ -45,4 +62,100 @@ def create_user_message( db.commit() db.refresh(assistant_msg) - return assistant_msg, session_id \ No newline at end of file + return assistant_msg, session_id + + +async def process_agent_message( + db: Session, + payload: RunAgentInput +) -> AsyncIterator[str]: + """Process agent message and generate AG-UI protocol events. + + Args: + db: Database session + payload: RunAgentInput containing the user's message and context + + Yields: + AG-UI protocol events as encoded strings + """ + encoder = EventEncoder() + + try: + # Extract the user message from the messages list + user_message_text = "" + if payload.messages: + last_message = payload.messages[-1] + if hasattr(last_message, 'content'): + if isinstance(last_message.content, list) and len(last_message.content) > 0: + user_message_text = last_message.content[0].text + elif isinstance(last_message.content, str): + user_message_text = last_message.content + + # Use thread_id as session_id (AG-UI uses thread_id for conversation tracking) + session_id = payload.thread_id if hasattr(payload, 'thread_id') else None + + if not payload.run_id: + payload.run_id = str(uuid4()) + + # Emit RUN_STARTED event + run_started = RunStartedEvent( + run_id=payload.run_id, + thread_id=payload.thread_id + ) + yield encoder.encode(run_started) + + # Process the message through the service + assistant_msg, session_id = create_user_message( + db=db, + message=user_message_text, + session_id=session_id + ) + + # Generate a unique message ID for the assistant response + message_id = f"msg_{payload.run_id}" + + # Emit TEXT_MESSAGE_START event + text_start = TextMessageStartEvent( + run_id=payload.run_id, + message_id=message_id + ) + yield encoder.encode(text_start) + + # Emit TEXT_MESSAGE_CONTENT event with the assistant's response + text_content = TextMessageContentEvent( + run_id=payload.run_id, + message_id=message_id, + delta=assistant_msg.message + ) + yield encoder.encode(text_content) + + # Emit TEXT_MESSAGE_END event + text_end = TextMessageEndEvent( + run_id=payload.run_id, + message_id=message_id + ) + yield encoder.encode(text_end) + + # Emit MESSAGES_SNAPSHOT event with the full conversation + messages_snapshot = MessagesSnapshotEvent( + run_id=payload.run_id, + messages=[ + *payload.messages, # Include previous messages + AssistantMessage( + id=message_id, + content=[TextInputContent(text=assistant_msg.message)] + ) + ] + ) + yield encoder.encode(messages_snapshot) + + # Emit RUN_FINISHED event + run_finished = RunFinishedEvent( + run_id=payload.run_id + ) + yield encoder.encode(run_finished) + + except Exception as e: + # In case of error, we should emit a RUN_ERROR event + # For now, we'll just re-raise the exception + raise \ No newline at end of file diff --git a/DocsManager/pyproject.toml b/DocsManager/pyproject.toml index 1f7001f..d18bf00 100644 --- a/DocsManager/pyproject.toml +++ b/DocsManager/pyproject.toml @@ -3,27 +3,22 @@ name = "goland-ia" version = "0.1.0" description = "Add your description here" readme = "README.md" -requires-python = ">=3.12,<3.14" - +requires-python = ">=3.14" dependencies = [ "fastapi>=0.124.2", - "sqlalchemy>=2.0.35", - "asyncpg>=0.29.0", - "psycopg2-binary>=2.9.9", - "langgraph>=1.0.4", + "ipython>=9.8.0", "typing-extensions>=4.15.0", "uvicorn>=0.38.0", "sqlalchemy>=2.0.0", + "psycopg2-binary>=2.9.0", "pgvector>=0.3.0", "pydantic-settings>=2.0.0", "pika>=1.3.0", "minio>=7.2.0", "python-multipart>=0.0.6", + "ag-ui-protocol>=0.1.10", ] -[project.scripts] -start = "uvicorn main:app --reload --host 0.0.0.0 --port 8000" - [project.optional-dependencies] dev = [ "ruff>=0.14.0", @@ -31,7 +26,7 @@ dev = [ [tool.ruff] # Enable pyproject.toml support -target-version = "py313" +target-version = "py314" line-length = 120 # Enable auto-fix @@ -84,4 +79,4 @@ indent-style = "space" skip-magic-trailing-comma = false # Automatically detect line ending -line-ending = "auto" \ No newline at end of file +line-ending = "auto" diff --git a/DocsManager/uv.lock b/DocsManager/uv.lock index 335add6..3bd1267 100644 --- a/DocsManager/uv.lock +++ b/DocsManager/uv.lock @@ -2,6 +2,18 @@ version = 1 revision = 3 requires-python = ">=3.12, <3.14" +[[package]] +name = "ag-ui-protocol" +version = "0.1.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/bb/5a5ec893eea5805fb9a3db76a9888c3429710dfb6f24bbb37568f2cf7320/ag_ui_protocol-0.1.10.tar.gz", hash = "sha256:3213991c6b2eb24bb1a8c362ee270c16705a07a4c5962267a083d0959ed894f4", size = 6945, upload-time = "2025-11-06T15:17:17.068Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/78/eb55fabaab41abc53f52c0918a9a8c0f747807e5306273f51120fd695957/ag_ui_protocol-0.1.10-py3-none-any.whl", hash = "sha256:c81e6981f30aabdf97a7ee312bfd4df0cd38e718d9fc10019c7d438128b93ab5", size = 7889, upload-time = "2025-11-06T15:17:15.325Z" }, +] + [[package]] name = "annotated-doc" version = "0.0.4" @@ -216,6 +228,7 @@ name = "goland-ia" version = "0.1.0" source = { virtual = "." } dependencies = [ + { name = "ag-ui-protocol" }, { name = "asyncpg" }, { name = "fastapi" }, { name = "langgraph" }, @@ -237,13 +250,13 @@ dev = [ [package.metadata] requires-dist = [ + { name = "ag-ui-protocol", specifier = ">=0.1.10" }, { name = "asyncpg", specifier = ">=0.29.0" }, { name = "fastapi", specifier = ">=0.124.2" }, { name = "langgraph", specifier = ">=1.0.4" }, { name = "minio", specifier = ">=7.2.0" }, { name = "pgvector", specifier = ">=0.3.0" }, { name = "pika", specifier = ">=1.3.0" }, - { name = "psycopg2-binary", specifier = ">=2.9.0" }, { name = "psycopg2-binary", specifier = ">=2.9.9" }, { name = "pydantic-settings", specifier = ">=2.0.0" }, { name = "python-multipart", specifier = ">=0.0.6" }, diff --git a/RAGManager/.env.example b/RAGManager/.env.example index 895cbeb..f61c8f4 100644 --- a/RAGManager/.env.example +++ b/RAGManager/.env.example @@ -1,7 +1,4 @@ -# Database Configuration -# NOTE: Use psycopg2 (sync driver), NOT asyncpg. The RAGManager uses synchronous SQLAlchemy. -# LangChain PGVector will automatically convert this to psycopg3 format internally. -DATABASE_URL=postgresql+psycopg2://postgres:postgres@postgres:5432/vectordb +DATABASE_URL=postgresql://postgres:postgres@postgres:5432/vectordb # RabbitMQ Configuration RABBITMQ_USER=guest diff --git a/RAGManager/Dockerfile b/RAGManager/Dockerfile index 33c9eb8..38ce443 100644 --- a/RAGManager/Dockerfile +++ b/RAGManager/Dockerfile @@ -1,22 +1,13 @@ -FROM python:3.12-slim +FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim WORKDIR /app -RUN apt-get update && apt-get install -y \ - curl \ - && rm -rf /var/lib/apt/lists/* +COPY pyproject.toml uv.lock* ./ -RUN curl -LsSf https://astral.sh/uv/install.sh | sh && \ - mv /root/.local/bin/uv /usr/local/bin/uv && \ - mv /root/.local/bin/uvx /usr/local/bin/uvx +RUN uv sync --frozen --no-cache || uv sync --no-cache COPY . . -RUN uv sync --no-dev --no-cache - EXPOSE 8000 -HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ - CMD curl -f http://localhost:8000/health || exit 1 - -CMD ["uv", "run", "--no-sync", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] \ No newline at end of file +CMD ["uv", "run", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] \ No newline at end of file diff --git a/RAGManager/RAG Manager API/Get Chat History.bru b/RAGManager/RAG Manager API/Get Chat History.bru deleted file mode 100644 index 30b5e13..0000000 --- a/RAGManager/RAG Manager API/Get Chat History.bru +++ /dev/null @@ -1,15 +0,0 @@ -meta { - name: Get Chat History - type: http - seq: 1 -} - -get { - url: {{base_url}}/api/{{api_version}}/chat/history/{{test_session_id}} - body: none - auth: none -} - -settings { - encodeUrl: true -} diff --git a/RAGManager/RAG Manager API/bruno.json b/RAGManager/RAG Manager API/bruno.json deleted file mode 100644 index 6c9acf1..0000000 --- a/RAGManager/RAG Manager API/bruno.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "version": "1", - "name": "RAG Manager API", - "type": "collection", - "ignore": [ - "node_modules", - ".git" - ] -} \ No newline at end of file diff --git a/RAGManager/RAG Manager API/environments/Local Development.bru b/RAGManager/RAG Manager API/environments/Local Development.bru deleted file mode 100644 index 7f6c634..0000000 --- a/RAGManager/RAG Manager API/environments/Local Development.bru +++ /dev/null @@ -1,5 +0,0 @@ -vars { - base_url: http://localhost:8000 - api_version: v1 - test_session_id: 550e8400-e29b-41d4-a716-446655440000 -} diff --git a/RAGManager/app/agents/graph.py b/RAGManager/app/agents/graph.py index 665feec..8916ea5 100644 --- a/RAGManager/app/agents/graph.py +++ b/RAGManager/app/agents/graph.py @@ -23,12 +23,12 @@ def create_agent_graph() -> StateGraph: Create and configure the LangGraph agent graph. The graph implements the following flow: - 1. START -> agent_host (Nodo 1) - Prepares state and retrieves chat history + 1. START -> agent_host (Nodo 1) - Prepares state, no DB operations 2. agent_host -> guard_inicial (Nodo 2) - Validates for malicious content 3. guard_inicial -> [conditional]: - - malicious -> fallback_inicial -> END (stops processing, chat history available) + - malicious -> fallback_inicial -> END (stops processing, no DB save) - continue -> parafraseo (Nodo 4) - 4. parafraseo -> Saves message to DB and paraphrases using chat history + 4. parafraseo -> Saves message to DB, retrieves chat history, paraphrases 5. parafraseo -> retriever (Nodo 5) - Retrieves relevant chunks from vector DB 6. retriever -> context_builder (Nodo 6) - Builds enriched query and generates response 7. context_builder -> guard_final (Nodo 8) - Validates response for risky content diff --git a/RAGManager/app/agents/nodes/agent_host.py b/RAGManager/app/agents/nodes/agent_host.py index e1e1aa1..b619573 100644 --- a/RAGManager/app/agents/nodes/agent_host.py +++ b/RAGManager/app/agents/nodes/agent_host.py @@ -1,11 +1,8 @@ """Nodo 1: Agent Host - Entry point that prepares initial state.""" import logging -from uuid import UUID from app.agents.state import AgentState -from app.core.database_connection import SessionLocal -from app.services.chat import get_chat_history logger = logging.getLogger(__name__) @@ -17,17 +14,16 @@ def agent_host(state: AgentState) -> AgentState: This node: 1. Receives the initial prompt and optional chat_session_id 2. Extracts the prompt from messages - 3. Retrieves chat history if session_id is provided - 4. Prepares state with context for the entire flow + 3. Prepares state for validation (no DB operations yet) - Note: Chat history is retrieved here (not in parafraseo) so it's available - throughout the entire flow, regardless of which path is taken (normal or fallback). + Note: Chat history retrieval and message saving is deferred to parafraseo + node to ensure malicious messages are not saved to the database. Args: state: Agent state containing the user prompt and optional chat_session_id Returns: - Updated state with prompt, chat_messages, and initial_context set + Updated state with prompt and initial_context set (no DB operations) """ updated_state = state.copy() @@ -36,50 +32,18 @@ def agent_host(state: AgentState) -> AgentState: last_message = messages[-1] if messages else None prompt = last_message.content if last_message else "" - # Set prompt and initial context + # Validate user_id is provided + user_id = state.get("user_id") + if not user_id: + logger.error("user_id is required in state but was not provided") + updated_state["error_message"] = "user_id is required" + return updated_state + + # Set prompt and initial context (no DB operations) updated_state["prompt"] = prompt updated_state["initial_context"] = prompt + updated_state["chat_messages"] = None # Will be set in parafraseo after validation - # Retrieve chat history if session exists - chat_session_id = state.get("chat_session_id") - - if chat_session_id: - try: - # Convert to UUID if string - session_uuid = UUID(chat_session_id) if isinstance(chat_session_id, str) else chat_session_id - - db = SessionLocal() - try: - # Get chat history from database - chat_messages = get_chat_history(db, session_uuid) - - # Convert SQLAlchemy models to dicts for state - updated_state["chat_messages"] = [ - { - "id": msg.id, - "sender": msg.sender, - "message": msg.message, - "created_at": msg.created_at.isoformat() if msg.created_at else None, - } - for msg in chat_messages - ] - - logger.info(f"Retrieved {len(chat_messages)} messages for session {session_uuid}") - finally: - db.close() - - except ValueError as e: - # Session doesn't exist - this is okay for new sessions - logger.info(f"Chat session not found (likely new session): {e}") - updated_state["chat_messages"] = [] - except Exception as e: - logger.error(f"Error retrieving chat history: {e}", exc_info=True) - updated_state["chat_messages"] = [] - else: - # No session ID provided - new conversation - logger.info("No chat_session_id provided - starting new conversation") - updated_state["chat_messages"] = [] - - logger.debug(f"Agent host prepared state with {len(updated_state.get('chat_messages', []))} chat messages") + logger.debug("Agent host prepared state for validation (no DB operations)") return updated_state diff --git a/RAGManager/app/agents/nodes/context_builder.py b/RAGManager/app/agents/nodes/context_builder.py index b550659..79f2195 100644 --- a/RAGManager/app/agents/nodes/context_builder.py +++ b/RAGManager/app/agents/nodes/context_builder.py @@ -1,19 +1,10 @@ """Nodo 6: Context Builder - Enriches query with retrieved context.""" -import logging - from app.agents.state import AgentState -from app.core.config import settings -from langchain_core.messages import HumanMessage, SystemMessage +from langchain_core.messages import SystemMessage from langchain_openai import ChatOpenAI -logger = logging.getLogger(__name__) - -# Initialize Primary LLM with configuration from settings -llm = ChatOpenAI( - model="gpt-4.1-mini", # Primary LLM model for context-aware responses - openai_api_key=settings.openai_api_key, -) +llm = ChatOpenAI(model="gpt-5-nano") def context_builder(state: AgentState) -> AgentState: @@ -32,82 +23,30 @@ def context_builder(state: AgentState) -> AgentState: Returns: Updated state with enriched_query and primary_response set """ + # TODO: Implement context building and primary LLM call + # This should: + # 1. Combine paraphrased_text with relevant_chunks into enriched_query + # 2. Format the query appropriately (e.g., with system prompts, context sections) + # 3. Call Primary LLM with the enriched query + # 4. Store the LLM response in primary_response + + # Placeholder: For now, we'll create a simple enriched query updated_state = state.copy() - - # Get paraphrased text and relevant chunks from state paraphrased = state.get("paraphrased_text", "") chunks = state.get("relevant_chunks", []) - - # Fallback if paraphrased_text is not available - if not paraphrased: - logger.warning("No paraphrased text found in state. Using original prompt.") - messages = state.get("messages", []) - if messages: - paraphrased = messages[-1].content if hasattr(messages[-1], "content") else str(messages[-1]) - else: - paraphrased = state.get("prompt", "") - + # Build enriched query with context - if chunks: - context_section = "\n\n---\n\n".join([f"Context {i+1}:\n{chunk}" for i, chunk in enumerate(chunks)]) - else: - context_section = "No relevant context found in the knowledge base." - logger.warning("No relevant chunks found for context building") + context_section = "\n\n".join(chunks) if chunks else "No relevant context found." - # Create enriched query combining paraphrased text and context - enriched_query = f"""User Question: {paraphrased} + system_content = f"""You are a helpful assistant. Use the following context to answer the user's question. +If the answer is not in the context, say you don't know. -Relevant Context from Knowledge Base: -{context_section} +Context: +{context_section}""" -Please provide a comprehensive answer based on the context provided above. If the context does not contain enough information to answer the question, please indicate that clearly.""" - - # System prompt for the Primary LLM - system_content = """You are a helpful assistant specialized in providing accurate, context-based answers about nutrition and culinary topics. + messages = [SystemMessage(content=system_content)] + state["messages"] -Your task is to: -1. Use the provided context to answer the user's question accurately -2. If the context contains relevant information, provide a comprehensive answer -3. If the context does not contain enough information, clearly state that you don't have sufficient information in the knowledge base -4. Always base your answer on the provided context - do not make up information -5. If the question is not related to the context, politely redirect the conversation + # Call Primary LLM + response = llm.invoke(messages) -Be concise, accurate, and helpful.""" - - # Prepare messages for LLM - messages_for_llm = [ - SystemMessage(content=system_content), - HumanMessage(content=enriched_query) - ] - - try: - # Call Primary LLM - logger.info("Calling Primary LLM with enriched query") - response = llm.invoke(messages_for_llm) - - # Extract response content - primary_response = response.content if hasattr(response, "content") else str(response) - - # Update state with enriched query and primary response (as defined in state.py) - updated_state["enriched_query"] = enriched_query - updated_state["primary_response"] = primary_response - - # Also set generated_response for guard_final (Nodo 7: Generator not yet implemented) - # For now, generated_response = primary_response - # This allows guard_final to validate the response - updated_state["generated_response"] = primary_response - - # Also update messages for LangGraph compatibility - updated_state["messages"] = state.get("messages", []) + [response] - - logger.info("Successfully generated primary response from LLM") - - except Exception as e: - logger.error(f"Error calling Primary LLM: {e}", exc_info=True) - # Set error state - updated_state["error_message"] = f"Error in context builder: {str(e)}" - updated_state["enriched_query"] = enriched_query - updated_state["primary_response"] = None - updated_state["generated_response"] = None - - return updated_state + return {"messages": [response]} diff --git a/RAGManager/app/agents/nodes/parafraseo.py b/RAGManager/app/agents/nodes/parafraseo.py index eed68a9..675e19a 100644 --- a/RAGManager/app/agents/nodes/parafraseo.py +++ b/RAGManager/app/agents/nodes/parafraseo.py @@ -1,4 +1,4 @@ -"""Nodo 4: Parafraseo - Saves message and paraphrases user input using chat history.""" +"""Nodo 4: Parafraseo - Saves message, retrieves chat history, and paraphrases user input.""" import json import logging @@ -14,22 +14,21 @@ def parafraseo(state: AgentState) -> AgentState: """ - Parafraseo node - Saves message to DB and paraphrases user input using chat history. + Parafraseo node - Saves message to DB, retrieves chat history, and paraphrases user input. This node: 1. Receives a validated user message (after guard_inicial validation) - 2. Saves the user's message to the chat session in PostgreSQL (TODO - placeholder) - 3. Uses chat history (already retrieved by agent_host) as context - 4. Uses the last message to understand user's intentions and chat history as context + 2. Saves the user's message to the chat session in PostgreSQL (endpoint 1 - placeholder) + 3. Retrieves the last 10 messages of the conversation (endpoint 2 - placeholder) + 4. Uses the last message to understand user's intentions and the remaining 9 (older) messages as context 5. Sends to LLM with instructions to return 3 differently phrased statements that encapsulate the user's intentions according to the last message and chat history Args: - state: Agent state containing validated user message, chat_messages (from agent_host), - chat_session_id, and user_id + state: Agent state containing validated user message, chat_session_id, and user_id Returns: - Updated state with paraphrased_text and paraphrased_statements set + Updated state with chat_messages, paraphrased_text, and paraphrased_statements set """ updated_state = state.copy() @@ -44,16 +43,31 @@ def parafraseo(state: AgentState) -> AgentState: last_user_message = messages[-1] user_message_content = last_user_message.content if hasattr(last_user_message, 'content') else str(last_user_message) - # TODO: Save message to PostgreSQL database according to chat session + # TODO: Endpoint 1 - Save message to PostgreSQL database according to chat session # This should: - # 1. Call a service or endpoint to save the current user message to the chat session - # 2. Use chat_session_id from state + # 1. Call an endpoint (not yet developed) that: + # - Saves the current user message to the chat session in PostgreSQL + # - Uses chat_session_id and user_id from state + # - Returns success/failure status + # 2. Handle errors appropriately (session not found, permission denied, etc.) + logger.info("Endpoint 1 (save message to DB) not yet implemented - skipping") + + # TODO: Endpoint 2 - Retrieve last 10 messages of the conversation + # This should: + # 1. Call an endpoint (not yet developed) that: + # - Retrieves the last 10 messages for the chat session + # - Returns a list of message dictionaries with structure: [{"sender": "user|assistant|system", "message": "...", "created_at": "..."}, ...] + # - Messages should be ordered from oldest to newest (or newest to oldest, depending on API design) + # 2. Update state with chat_messages from the endpoint response # 3. Handle errors appropriately (session not found, permission denied, etc.) - logger.info("Message save to DB not yet implemented - skipping") - # Get chat history from state (already retrieved by agent_host) - chat_messages = state.get("chat_messages", []) - logger.info(f"Using {len(chat_messages)} chat messages from state (retrieved by agent_host)") + # Placeholder: For now, we'll simulate chat history with just the current message + # Once the endpoint is implemented, replace this with the actual endpoint call + chat_messages = [ + {"sender": "user", "message": user_message_content, "created_at": "2025-01-01T00:00:00"} + ] + updated_state["chat_messages"] = chat_messages + logger.warning("Endpoint 2 (retrieve chat history) not yet implemented - using current message only") # Process chat history: last message (intentions) + 9 older messages (context) # The last message is the most recent one (for understanding intentions) diff --git a/RAGManager/app/agents/nodes/retriever.py b/RAGManager/app/agents/nodes/retriever.py index 90bbdd0..0f2ec42 100644 --- a/RAGManager/app/agents/nodes/retriever.py +++ b/RAGManager/app/agents/nodes/retriever.py @@ -163,4 +163,4 @@ def retriever(state: AgentState) -> AgentState: # Store the unique chunk contents in state updated_state["relevant_chunks"] = unique_chunks - return updated_state \ No newline at end of file + return updated_state diff --git a/RAGManager/app/api/routes/__init__.py b/RAGManager/app/api/routes/__init__.py index 82dfe27..ef443ea 100644 --- a/RAGManager/app/api/routes/__init__.py +++ b/RAGManager/app/api/routes/__init__.py @@ -2,9 +2,8 @@ from fastapi import APIRouter -from app.api.routes import chat, documents +from app.api.routes import documents router = APIRouter(prefix="/api/v1") -router.include_router(documents.router) -router.include_router(chat.router) \ No newline at end of file +router.include_router(documents.router) \ No newline at end of file diff --git a/RAGManager/app/api/routes/chat.py b/RAGManager/app/api/routes/chat.py deleted file mode 100644 index 2cdcff6..0000000 --- a/RAGManager/app/api/routes/chat.py +++ /dev/null @@ -1,70 +0,0 @@ -"""API routes for chat-related endpoints.""" - -import logging -from uuid import UUID - -from fastapi import APIRouter, Depends, HTTPException -from sqlalchemy.orm import Session - -from app.core.database_connection import get_db -from app.schemas.chat import ChatHistoryResponse, ChatMessageResponse -from app.services.chat import get_chat_history - -logger = logging.getLogger(__name__) - -router = APIRouter(prefix="/chat", tags=["chat"]) - - -@router.get("/history/{chat_session_id}", response_model=ChatHistoryResponse) -async def get_chat_history_endpoint( - chat_session_id: UUID, - db: Session = Depends(get_db), -) -> ChatHistoryResponse: - """ - Retrieve the last 10 messages from a chat session. - - This endpoint returns the most recent 10 messages from the specified chat session, - ordered by creation time (most recent first). - - Args: - chat_session_id: UUID of the chat session - db: Database session dependency - - Returns: - ChatHistoryResponse containing the session_id, list of messages, and count - - Raises: - HTTPException: 404 if chat session doesn't exist - HTTPException: 400 if invalid UUID format - HTTPException: 500 for database errors - """ - logger.info(f"Received chat history request for session: {chat_session_id}") - - try: - # Get chat history from service - messages = get_chat_history(db, chat_session_id) - - # Convert SQLAlchemy models to Pydantic models - message_responses = [ - ChatMessageResponse( - id=msg.id, - session_id=msg.session_id, - sender=msg.sender, - message=msg.message, - created_at=msg.created_at, - ) - for msg in messages - ] - - return ChatHistoryResponse( - session_id=chat_session_id, - messages=message_responses, - count=len(message_responses), - ) - - except ValueError as e: - logger.warning(f"Chat session not found: {e}") - raise HTTPException(status_code=404, detail=str(e)) - except Exception as e: - logger.error(f"Error retrieving chat history: {e}") - raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}") diff --git a/RAGManager/app/core/config.py b/RAGManager/app/core/config.py index 264d591..b96d119 100644 --- a/RAGManager/app/core/config.py +++ b/RAGManager/app/core/config.py @@ -13,7 +13,7 @@ class Settings(BaseSettings): minio_access_key: str minio_secret_key: str minio_bucket: str - minio_use_ssl: bool = True + minio_secure: bool = True # OpenAI Configuration openai_api_key: str @@ -21,13 +21,6 @@ class Settings(BaseSettings): # Database Configuration database_url: str - # RabbitMQ Configuration - rabbitmq_user: str - rabbitmq_password: str - rabbitmq_host: str = "localhost" - rabbitmq_port: int = 5672 - rabbitmq_queue_name: str = "document.process" - # Chunking Configuration chunk_size: int = 1000 chunk_overlap: int = 200 @@ -74,15 +67,6 @@ class Settings(BaseSettings): extra="ignore", ) - @property - def rabbitmq_url(self) -> str: - """Returns the RabbitMQ connection URL with URL-encoded credentials.""" - from urllib.parse import quote_plus - - encoded_user = quote_plus(self.rabbitmq_user) - encoded_password = quote_plus(self.rabbitmq_password) - return f"amqp://{encoded_user}:{encoded_password}@{self.rabbitmq_host}:{self.rabbitmq_port}/" - settings = Settings() diff --git a/RAGManager/app/core/rabbitmq.py b/RAGManager/app/core/rabbitmq.py deleted file mode 100644 index 53b5b26..0000000 --- a/RAGManager/app/core/rabbitmq.py +++ /dev/null @@ -1,99 +0,0 @@ -import json -import logging -from typing import Callable, Optional - -import pika -from pika.exceptions import AMQPConnectionError - -from app.core.config import settings - -logger = logging.getLogger(__name__) - - -class RabbitMQConnection: - """Handles connection and operations with RabbitMQ""" - - def __init__(self): - self.connection: Optional[pika.BlockingConnection] = None - self.channel: Optional[pika.channel.Channel] = None - - def connect(self): - """Establishes connection with RabbitMQ""" - try: - url = settings.rabbitmq_url - logger.info(f"Connecting to RabbitMQ at {settings.rabbitmq_host}:{settings.rabbitmq_port}") - logger.debug( - f"RabbitMQ URL: amqp://{settings.rabbitmq_user}:***@{settings.rabbitmq_host}:{settings.rabbitmq_port}/" - ) - - self.connection = pika.BlockingConnection(pika.URLParameters(url)) - self.channel = self.connection.channel() - logger.info("Connected to RabbitMQ") - except AMQPConnectionError as e: - logger.error(f"Failed to connect to RabbitMQ: {e}") - logger.error(f"Configured host: {settings.rabbitmq_host}") - logger.error(f"Configured port: {settings.rabbitmq_port}") - raise - except Exception as e: - logger.error(f"Unexpected error connecting to RabbitMQ: {e}") - logger.error(f"Error type: {type(e).__name__}") - raise - - def close(self): - """Closes the connection""" - if self.channel and not self.channel.is_closed: - self.channel.close() - logger.info("RabbitMQ channel closed") - if self.connection and not self.connection.is_closed: - self.connection.close() - logger.info("RabbitMQ connection closed") - - def declare_queue(self, queue_name: str, durable: bool = True): - """Declares a queue""" - if not self.channel: - self.connect() - self.channel.queue_declare(queue=queue_name, durable=durable) - logger.info(f"Queue '{queue_name}' declared") - - def consume_messages(self, queue_name: str, callback: Callable): - """ - Start consuming messages from the queue. - - Args: - queue_name: Name of the queue to consume from - callback: Callback function to process messages - """ - if not self.channel: - self.connect() - - # Declare queue (idempotent operation) - self.declare_queue(queue_name) - - # Set QoS to process one message at a time - self.channel.basic_qos(prefetch_count=1) - - # Start consuming - self.channel.basic_consume( - queue=queue_name, - on_message_callback=callback, - auto_ack=False, # Manual acknowledgment - ) - - logger.info(f"Started consuming messages from queue '{queue_name}'") - logger.info("Waiting for messages. To exit press CTRL+C") - - try: - self.channel.start_consuming() - except KeyboardInterrupt: - logger.info("Interrupted by user") - self.channel.stop_consuming() - except Exception as e: - logger.error(f"Error while consuming messages: {e}") - raise - finally: - self.close() - - -# Global instance -rabbitmq = RabbitMQConnection() - diff --git a/RAGManager/app/models/chat.py b/RAGManager/app/models/chat.py index e9c40fd..f5eec38 100644 --- a/RAGManager/app/models/chat.py +++ b/RAGManager/app/models/chat.py @@ -15,8 +15,9 @@ class ChatSession(Base): __tablename__ = "chat_sessions" id = Column(PGUUID(as_uuid=True), primary_key=True, server_default="uuid_generate_v4()") + user_id = Column(Text, nullable=True, index=True) # User ID for ownership validation created_at = Column(TIMESTAMP, default=datetime.utcnow, nullable=False) - session_metadata = Column("metadata", JSONB, nullable=True) # Map to 'metadata' column in DB + metadata = Column(JSONB, nullable=True) # Relationship to messages messages = relationship("ChatMessage", back_populates="session", cascade="all, delete-orphan") diff --git a/RAGManager/app/schemas/chat.py b/RAGManager/app/schemas/chat.py deleted file mode 100644 index babc689..0000000 --- a/RAGManager/app/schemas/chat.py +++ /dev/null @@ -1,29 +0,0 @@ -"""Pydantic schemas for chat-related endpoints.""" - -from datetime import datetime -from uuid import UUID - -from pydantic import BaseModel - - -class ChatMessageResponse(BaseModel): - """Response schema for a single chat message.""" - - id: int - session_id: UUID - sender: str # 'user', 'assistant', or 'system' - message: str - created_at: datetime - - class Config: - """Pydantic config.""" - - from_attributes = True - - -class ChatHistoryResponse(BaseModel): - """Response schema for chat history endpoint.""" - - session_id: UUID - messages: list[ChatMessageResponse] - count: int diff --git a/RAGManager/app/services/chat.py b/RAGManager/app/services/chat.py deleted file mode 100644 index ed98c3f..0000000 --- a/RAGManager/app/services/chat.py +++ /dev/null @@ -1,44 +0,0 @@ -"""Service functions for chat-related operations.""" - -import logging -from uuid import UUID - -from sqlalchemy.orm import Session - -from app.models.chat import ChatMessage, ChatSession - -logger = logging.getLogger(__name__) - - -def get_chat_history(db: Session, session_id: UUID) -> list[ChatMessage]: - """ - Retrieve the last 10 messages from a chat session. - - Args: - db: SQLAlchemy database session - session_id: UUID of the chat session - - Returns: - List of ChatMessage objects ordered by created_at DESC (most recent first) - - Raises: - ValueError: If the chat session doesn't exist - """ - # First, validate that the session exists - session = db.query(ChatSession).filter(ChatSession.id == session_id).first() - if not session: - raise ValueError(f"Chat session {session_id} not found") - - # Query the last 10 messages for this session, ordered by created_at DESC - messages = ( - db.query(ChatMessage) - .filter(ChatMessage.session_id == session_id) - .order_by(ChatMessage.created_at.desc()) - .limit(10) - .all() - ) - - # Reverse to get chronological order (oldest first) - # But the plan says "most recent first", so we'll keep DESC order - logger.info(f"Retrieved {len(messages)} messages for session {session_id}") - return messages diff --git a/RAGManager/app/services/embedding_service.py b/RAGManager/app/services/embedding_service.py new file mode 100644 index 0000000..b70500d --- /dev/null +++ b/RAGManager/app/services/embedding_service.py @@ -0,0 +1,25 @@ +from typing import List, Tuple + +from langchain_core.documents import Document + + +def chunks_to_embeddings(chunks: List[Document]) -> List[Tuple[str, List[float]]]: + """ + Placeholder function - to be implemented later. + + This function will: + 1. Generate embeddings for each chunk using OpenAI's embedding API + 2. Return a list of tuples containing chunk content and its embedding vector + + Args: + chunks: List of LangChain Document chunks to embed + + Returns: + List[Tuple[str, List[float]]]: List of tuples containing (content, embedding_vector) + where embedding_vector is a list of floats with dimension 1536 + + Raises: + NotImplementedError: This function is not yet implemented + """ + raise NotImplementedError("This function will be implemented later") + diff --git a/RAGManager/app/services/minio_client.py b/RAGManager/app/services/minio_client.py index 17843ed..2cfa106 100644 --- a/RAGManager/app/services/minio_client.py +++ b/RAGManager/app/services/minio_client.py @@ -1,7 +1,6 @@ # MinIO client configuration and utilities. import logging -from urllib.parse import urlparse import certifi import urllib3 @@ -15,19 +14,6 @@ def get_minio_client() -> Minio: """Create a MinIO client with proper timeout and retry configuration.""" - # Parse endpoint to extract host and port (urlparse strips the scheme automatically) - parsed = urlparse(settings.minio_endpoint) - endpoint = parsed.netloc or parsed.path - - # Validate that the endpoint is not empty - if not endpoint or endpoint.strip() == "": - error_msg = ( - f"Invalid MinIO endpoint configuration: '{settings.minio_endpoint}'. " - "Endpoint must be a valid host or host:port (e.g., 'localhost:9000')" - ) - logger.error(error_msg) - raise ValueError(error_msg) - # Configure timeout: 10s connect, 30s read timeout = UrllibTimeout(connect=10, read=30) @@ -48,10 +34,10 @@ def get_minio_client() -> Minio: ) return Minio( - endpoint=endpoint, + endpoint=settings.minio_endpoint, access_key=settings.minio_access_key, secret_key=settings.minio_secret_key, - secure=settings.minio_use_ssl, + secure=settings.minio_secure, http_client=http_client, ) diff --git a/RAGManager/app/services/pipeline.py b/RAGManager/app/services/pipeline.py index 47fd346..d9cce0c 100644 --- a/RAGManager/app/services/pipeline.py +++ b/RAGManager/app/services/pipeline.py @@ -1,38 +1,15 @@ import logging +from langchain_core.documents import Document + from app.core.config import settings -from app.core.database_connection import SessionLocal -from app.models.document import Document from app.services.chunking_service import document_to_chunks +from app.services.embedding_service import chunks_to_embeddings from app.services.pdf_processor import pdf_to_document -from app.services.vector_store import store_chunks_with_embeddings logger = logging.getLogger(__name__) -def _create_document_record(filename: str, minio_path: str) -> int: - """ - Create a Document record in the database. - - Args: - filename: Original filename of the PDF - minio_path: Path to the PDF in MinIO bucket - - Returns: - int: The created document's ID - """ - db = SessionLocal() - try: - document = Document(filename=filename, minio_path=minio_path) - db.add(document) - db.commit() - db.refresh(document) - logger.info(f"Created document record with id={document.id}") - return document.id - finally: - db.close() - - def process_pdf_pipeline(object_name: str) -> int: """ Orchestrates the PDF processing pipeline. @@ -40,16 +17,17 @@ def process_pdf_pipeline(object_name: str) -> int: This function coordinates the three-stage pipeline: 1. PDF to LangChain Document 2. Document to Chunks - 3. Embed and Store in database using PGVector + 3. Chunks to Embeddings + 4. Store in database (to be implemented) Args: object_name: Path/name of the PDF object in the MinIO bucket Returns: - int: document_id of the created document + int: document_id of the created document (mock value for now) Raises: - Exception: If any of the pipeline stages fail + NotImplementedError: If any of the pipeline stages are not yet implemented """ logger.info(f"Starting PDF processing pipeline for object: {object_name}") @@ -66,28 +44,27 @@ def process_pdf_pipeline(object_name: str) -> int: chunks = document_to_chunks(document, settings.chunk_size, settings.chunk_overlap) logger.info(f"Stage 2 completed successfully. Created {len(chunks)} chunks") - # Stage 3: Embed and Store in database - # First, create the document record to get the document_id - logger.info("Stage 3: Embedding and storing chunks in database") - - # Extract filename from object_name (e.g., "folder/file.pdf" -> "file.pdf") - filename = object_name.split("/")[-1] if "/" in object_name else object_name - - # Create document record in the documents table - document_id = _create_document_record(filename=filename, minio_path=object_name) - - # Store chunks with embeddings using PGVector - # This generates embeddings via OpenAI and stores in the vector database - chunks_stored = store_chunks_with_embeddings( - document_id=document_id, - filename=filename, - chunks=chunks, - ) - logger.info(f"Stage 3 completed successfully. Stored {chunks_stored} chunks with embeddings") - - logger.info(f"Pipeline completed successfully. Document ID: {document_id}") - return document_id - + # Stage 3: Chunks to Embeddings + logger.info("Stage 3: Generating embeddings for chunks") + embeddings = chunks_to_embeddings(chunks) + logger.info(f"Stage 3 completed successfully. Generated {len(embeddings)} embeddings") + + # Stage 4: Store in database (placeholder - not implemented yet) + logger.info("Stage 4: Storing chunks and embeddings in database") + # TODO: Implement database storage + # This will: + # 1. Create a Document record in the documents table + # 2. Create DocumentChunk records with embeddings in the document_chunks table + # 3. Return the document_id + raise NotImplementedError("Database storage will be implemented later") + + except NotImplementedError as e: + logger.warning(f"Pipeline stage not implemented: {e}") + # Return a mock document_id for now + # In production, this should be replaced with actual database storage + mock_document_id = 1 + logger.info(f"Pipeline completed with mock document_id: {mock_document_id}") + return mock_document_id except Exception as e: logger.error(f"Error in PDF processing pipeline: {e}") raise diff --git a/RAGManager/app/services/vector_store.py b/RAGManager/app/services/vector_store.py deleted file mode 100644 index 8f57c9e..0000000 --- a/RAGManager/app/services/vector_store.py +++ /dev/null @@ -1,162 +0,0 @@ -""" -Vector Store Service - Handles embedding generation and storage using LangChain PGVector. - -This service provides functionality to: -1. Initialize PGVector connection with OpenAI embeddings -2. Store document chunks with their embeddings in batches -3. Convert database URLs to psycopg3 format required by langchain-postgres -""" - -import logging -from urllib.parse import urlparse, urlunparse - -from langchain_core.documents import Document -from langchain_openai import OpenAIEmbeddings -from langchain_postgres import PGVector - -from app.core.config import settings - -logger = logging.getLogger(__name__) - -# Collection name for the vector store -COLLECTION_NAME = "document_chunks" - -# Batch size for inserting documents (to handle large PDFs efficiently) -DEFAULT_BATCH_SIZE = 100 - - -def _convert_database_url_to_psycopg(database_url: str) -> str: - """ - Convert database URL to postgresql+psycopg format required by langchain-postgres. - - LangChain PGVector requires postgresql+psycopg:// (psycopg3) format. - This function converts common formats (postgresql://, postgresql+psycopg2://) to the required format. - - Args: - database_url: Original database URL - - Returns: - Database URL in postgresql+psycopg:// format - """ - parsed = urlparse(database_url) - - # Replace driver with psycopg (psycopg3) - if parsed.scheme.startswith("postgresql"): - # Remove any existing driver (e.g., +psycopg2) - base_scheme = "postgresql" - if "+" in parsed.scheme: - base_scheme = parsed.scheme.split("+")[0] - - new_scheme = f"{base_scheme}+psycopg" - new_parsed = parsed._replace(scheme=new_scheme) - return urlunparse(new_parsed) - - return database_url - - -def _get_embeddings() -> OpenAIEmbeddings: - """ - Get OpenAI embeddings instance configured from settings. - - Returns: - OpenAIEmbeddings instance - """ - return OpenAIEmbeddings( - model=settings.embedding_model, - openai_api_key=settings.openai_api_key, - ) - - -def _get_vector_store() -> PGVector: - """ - Get or create PGVector instance for document storage. - - Returns: - PGVector instance configured with embeddings and connection - """ - connection_string = _convert_database_url_to_psycopg(settings.database_url) - embeddings = _get_embeddings() - - vector_store = PGVector( - embeddings=embeddings, - collection_name=COLLECTION_NAME, - connection=connection_string, - use_jsonb=True, - ) - - return vector_store - - -def store_chunks_with_embeddings( - document_id: int, - filename: str, - chunks: list[Document], - batch_size: int = DEFAULT_BATCH_SIZE, -) -> int: - """ - Store document chunks with their embeddings in PGVector. - - This function: - 1. Prepares chunks with metadata (document_id, chunk_index, filename) - 2. Uses LangChain PGVector to generate embeddings and store in batches - 3. Returns the number of chunks stored - - Args: - document_id: ID of the parent document in the documents table - filename: Original filename for metadata - chunks: List of LangChain Document chunks to embed and store - batch_size: Number of chunks to process per batch (default: 100) - - Returns: - int: Number of chunks successfully stored - - Raises: - Exception: If storage fails - """ - if not chunks: - logger.warning("No chunks provided for storage") - return 0 - - logger.info(f"Storing {len(chunks)} chunks for document_id={document_id}") - - # Prepare documents with metadata for PGVector - prepared_docs = [] - for idx, chunk in enumerate(chunks): - # Create a new document with enriched metadata - metadata = { - "document_id": document_id, - "chunk_index": idx, - "filename": filename, - # Preserve any existing metadata from chunking - **chunk.metadata, - } - prepared_docs.append( - Document( - page_content=chunk.page_content, - metadata=metadata, - ) - ) - - # Get vector store instance - vector_store = _get_vector_store() - - # Store documents in batches - total_stored = 0 - for i in range(0, len(prepared_docs), batch_size): - batch = prepared_docs[i : i + batch_size] - batch_num = (i // batch_size) + 1 - total_batches = (len(prepared_docs) + batch_size - 1) // batch_size - - logger.info(f"Processing batch {batch_num}/{total_batches} ({len(batch)} chunks)") - - try: - # PGVector.add_documents handles embedding generation internally - vector_store.add_documents(batch) - total_stored += len(batch) - logger.debug(f"Batch {batch_num} stored successfully") - except Exception as e: - logger.error(f"Error storing batch {batch_num}: {e}") - raise - - logger.info(f"Successfully stored {total_stored} chunks for document_id={document_id}") - return total_stored diff --git a/RAGManager/app/workers/__init__.py b/RAGManager/app/workers/__init__.py deleted file mode 100644 index cd00fa7..0000000 --- a/RAGManager/app/workers/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -"""Workers package for background processing tasks.""" - diff --git a/RAGManager/app/workers/pdf_processor_consumer.py b/RAGManager/app/workers/pdf_processor_consumer.py deleted file mode 100644 index 01dcc4d..0000000 --- a/RAGManager/app/workers/pdf_processor_consumer.py +++ /dev/null @@ -1,121 +0,0 @@ -"""RabbitMQ consumer for processing PDFs from MinIO events.""" - -import json -import logging -from urllib.parse import unquote - -from app.core.config import settings -from app.core.rabbitmq import RabbitMQConnection -from app.services.pipeline import process_pdf_pipeline - -logger = logging.getLogger(__name__) - - -def extract_pdf_path(message_body: dict) -> str: - """ - Extract the PDF path from MinIO event message. - - Args: - message_body: Parsed JSON message from MinIO - - Returns: - str: Decoded object path (e.g., "rag-docs/file.pdf") - - Raises: - ValueError: If message structure is invalid - """ - records = message_body.get("Records", []) - if not records: - raise ValueError("No Records found in message") - - # Extract key from Records[0].s3.object.key - try: - object_key = records[0]["s3"]["object"]["key"] - except (KeyError, IndexError) as e: - raise ValueError(f"Invalid message structure: {e}") from e - - # URL decode: rag-docs%2Farchivo.pdf -> rag-docs/archivo.pdf - decoded_path = unquote(object_key) - - return decoded_path - - -def message_callback(ch, method, properties, body): - """ - Callback function to process RabbitMQ messages. - - Args: - ch: Channel - method: Method - properties: Properties - body: Message body (bytes) - """ - try: - # Parse JSON message - message = json.loads(body) - logger.info(f"Received message from RabbitMQ") - logger.debug(f"Message content: {message}") - - # Extract PDF path - pdf_path = extract_pdf_path(message) - logger.info(f"Extracted PDF path: {pdf_path}") - - # Only process PDFs - if not pdf_path.lower().endswith('.pdf'): - logger.info(f"Skipping non-PDF file: {pdf_path}") - ch.basic_ack(delivery_tag=method.delivery_tag) - return - - # Call the existing pipeline - logger.info(f"Starting PDF processing pipeline for: {pdf_path}") - document_id = process_pdf_pipeline(pdf_path) - logger.info(f"PDF processed successfully: {pdf_path} -> Document ID: {document_id}") - - # Acknowledge the message - ch.basic_ack(delivery_tag=method.delivery_tag) - logger.info(f"Message acknowledged for: {pdf_path}") - - except json.JSONDecodeError as e: - logger.error(f"Failed to parse JSON message: {e}") - # NACK without requeue - malformed messages won't be fixed by retrying - ch.basic_nack(delivery_tag=method.delivery_tag, requeue=False) - except ValueError as e: - logger.error(f"Invalid message structure: {e}") - # NACK without requeue - invalid structure won't be fixed by retrying - ch.basic_nack(delivery_tag=method.delivery_tag, requeue=False) - except Exception as e: - logger.error(f"Error processing message: {e}", exc_info=True) - # NACK without requeue to avoid infinite loops - # In production, consider implementing a dead-letter queue - ch.basic_nack(delivery_tag=method.delivery_tag, requeue=False) - - -def start_consumer(): - """ - Start the RabbitMQ consumer to process PDF files. - - This function runs in a blocking loop and should be executed - in a separate thread or process. - """ - logger.info("Starting PDF processor consumer") - - try: - # Create RabbitMQ connection - rabbitmq = RabbitMQConnection() - rabbitmq.connect() - - # Start consuming messages - queue_name = settings.rabbitmq_queue_name - logger.info(f"Consuming messages from queue: {queue_name}") - - rabbitmq.consume_messages( - queue_name=queue_name, - callback=message_callback - ) - - except KeyboardInterrupt: - logger.info("Consumer interrupted by user") - except Exception as e: - logger.error(f"Fatal error in consumer: {e}", exc_info=True) - raise - diff --git a/RAGManager/main.py b/RAGManager/main.py index 100a13d..dd6914a 100644 --- a/RAGManager/main.py +++ b/RAGManager/main.py @@ -1,12 +1,9 @@ import logging -import threading - from fastapi import FastAPI from app.api.routes import router as api_router from app.api.routes.base import router as base_router from app.core.database_connection import init_db -from app.workers.pdf_processor_consumer import start_consumer # Configure logging logging.basicConfig( @@ -24,28 +21,10 @@ @app.on_event("startup") async def startup_event(): - """Initialize database and start RabbitMQ consumer on startup.""" + """Initialize database on startup.""" try: init_db() logging.info("Database initialized successfully") - - # Start RabbitMQ consumer in a separate daemon thread - consumer_thread = threading.Thread(target=start_consumer, daemon=True) - consumer_thread.start() - logging.info("RabbitMQ consumer started successfully") - except Exception as e: - logging.error(f"Failed to initialize: {e}") - raise - - -@app.get("/") -async def root(): - return {"message": "Hello World"} - - -@app.get("/health") -def health_check(): - return {"message": "200 corriendo..."} - - + logging.error(f"Failed to initialize database: {e}") + raise \ No newline at end of file diff --git a/RAGManager/pyproject.toml b/RAGManager/pyproject.toml index fb90f0e..4525963 100644 --- a/RAGManager/pyproject.toml +++ b/RAGManager/pyproject.toml @@ -1,23 +1,22 @@ [project] -name = "rag-manager" +name = "goland-ia" version = "0.1.0" -description = "RAG Manager for document processing" +description = "Add your description here" readme = "README.md" requires-python = ">=3.12,<3.13" dependencies = [ - "fastapi>=0.124.2", + "fastapi>=0.124.2", "ipython>=9.8.0", "langgraph>=1.0.4", "langchain-core>=0.3.0", "langchain-text-splitters>=0.3.0", - "langchain-postgres>=0.0.14", - "langchain-openai>=0.3.0", + "langchain-postgres>=0.0.5", "typing-extensions>=4.15.0", + "uvicorn>=0.38.0", "sqlalchemy>=2.0.0", - "psycopg2-binary>=2.9.9", + "psycopg2-binary>=2.9.0", "pgvector>=0.3.0", "pydantic-settings>=2.0.0", - "uvicorn>=0.38.0", "guardrails-ai>=0.5.10", "langchain>=1.2.0", "langchain-community>=0.4.1", @@ -26,7 +25,6 @@ dependencies = [ "minio>=7.2.20", "presidio-analyzer>=2.2.360", "presidio-anonymizer>=2.2.360", - "pika>=1.3.0", ] [project.optional-dependencies] @@ -35,9 +33,14 @@ dev = [ ] [tool.ruff] -target-version = "py312" +# Enable pyproject.toml support +target-version = "py314" line-length = 120 + +# Enable auto-fix fix = true + +# Exclude directories exclude = [ ".git", ".venv", @@ -49,6 +52,7 @@ exclude = [ ] [tool.ruff.lint] +# Enable a comprehensive set of rules select = [ "E", # pycodestyle errors "W", # pycodestyle warnings @@ -60,16 +64,26 @@ select = [ "C4", # flake8-comprehensions "SIM", # flake8-simplify ] + ignore = [ - "E501", # Line too long + "E501", # Line too long (handled by formatter) ] + +# Allow autofix for all enabled rules fixable = ["ALL"] unfixable = [] + +# Allow unused variables when underscore-prefixed dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" [tool.ruff.format] +# Use double quotes for strings (like Black) quote-style = "double" + +# Indent with spaces indent-style = "space" + +# Respect magic trailing commas skip-magic-trailing-comma = false # Automatically detect line ending diff --git a/RAGManager/uv.lock b/RAGManager/uv.lock index 519ef2d..caa1305 100644 --- a/RAGManager/uv.lock +++ b/RAGManager/uv.lock @@ -144,22 +144,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl", hash = "sha256:15a3ebc0f43c2d0a50eeafea25e19046c68398e487b9f1f5b517f7c0f40f976a", size = 27047, upload-time = "2025-11-15T16:43:16.109Z" }, ] -[[package]] -name = "asyncpg" -version = "0.31.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fe/cc/d18065ce2380d80b1bcce927c24a2642efd38918e33fd724bc4bca904877/asyncpg-0.31.0.tar.gz", hash = "sha256:c989386c83940bfbd787180f2b1519415e2d3d6277a70d9d0f0145ac73500735", size = 993667, upload-time = "2025-11-24T23:27:00.812Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/a6/59d0a146e61d20e18db7396583242e32e0f120693b67a8de43f1557033e2/asyncpg-0.31.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b44c31e1efc1c15188ef183f287c728e2046abb1d26af4d20858215d50d91fad", size = 662042, upload-time = "2025-11-24T23:25:49.578Z" }, - { url = "https://files.pythonhosted.org/packages/36/01/ffaa189dcb63a2471720615e60185c3f6327716fdc0fc04334436fbb7c65/asyncpg-0.31.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0c89ccf741c067614c9b5fc7f1fc6f3b61ab05ae4aaa966e6fd6b93097c7d20d", size = 638504, upload-time = "2025-11-24T23:25:51.501Z" }, - { url = "https://files.pythonhosted.org/packages/9f/62/3f699ba45d8bd24c5d65392190d19656d74ff0185f42e19d0bbd973bb371/asyncpg-0.31.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:12b3b2e39dc5470abd5e98c8d3373e4b1d1234d9fbdedf538798b2c13c64460a", size = 3426241, upload-time = "2025-11-24T23:25:53.278Z" }, - { url = "https://files.pythonhosted.org/packages/8c/d1/a867c2150f9c6e7af6462637f613ba67f78a314b00db220cd26ff559d532/asyncpg-0.31.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:aad7a33913fb8bcb5454313377cc330fbb19a0cd5faa7272407d8a0c4257b671", size = 3520321, upload-time = "2025-11-24T23:25:54.982Z" }, - { url = "https://files.pythonhosted.org/packages/7a/1a/cce4c3f246805ecd285a3591222a2611141f1669d002163abef999b60f98/asyncpg-0.31.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3df118d94f46d85b2e434fd62c84cb66d5834d5a890725fe625f498e72e4d5ec", size = 3316685, upload-time = "2025-11-24T23:25:57.43Z" }, - { url = "https://files.pythonhosted.org/packages/40/ae/0fc961179e78cc579e138fad6eb580448ecae64908f95b8cb8ee2f241f67/asyncpg-0.31.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bd5b6efff3c17c3202d4b37189969acf8927438a238c6257f66be3c426beba20", size = 3471858, upload-time = "2025-11-24T23:25:59.636Z" }, - { url = "https://files.pythonhosted.org/packages/52/b2/b20e09670be031afa4cbfabd645caece7f85ec62d69c312239de568e058e/asyncpg-0.31.0-cp312-cp312-win32.whl", hash = "sha256:027eaa61361ec735926566f995d959ade4796f6a49d3bde17e5134b9964f9ba8", size = 527852, upload-time = "2025-11-24T23:26:01.084Z" }, - { url = "https://files.pythonhosted.org/packages/b5/f0/f2ed1de154e15b107dc692262395b3c17fc34eafe2a78fc2115931561730/asyncpg-0.31.0-cp312-cp312-win_amd64.whl", hash = "sha256:72d6bdcbc93d608a1158f17932de2321f68b1a967a13e014998db87a72ed3186", size = 597175, upload-time = "2025-11-24T23:26:02.564Z" }, -] - [[package]] name = "attrs" version = "25.4.0" @@ -169,33 +153,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, ] -[[package]] -name = "blis" -version = "1.3.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d0/d0/d8cc8c9a4488a787e7fa430f6055e5bd1ddb22c340a751d9e901b82e2efe/blis-1.3.3.tar.gz", hash = "sha256:034d4560ff3cc43e8aa37e188451b0440e3261d989bb8a42ceee865607715ecd", size = 2644873, upload-time = "2025-11-17T12:28:30.511Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/16/d1/429cf0cf693d4c7dc2efed969bd474e315aab636e4a95f66c4ed7264912d/blis-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2a1c74e100665f8e918ebdbae2794576adf1f691680b5cdb8b29578432f623ef", size = 6929663, upload-time = "2025-11-17T12:27:44.482Z" }, - { url = "https://files.pythonhosted.org/packages/11/69/363c8df8d98b3cc97be19aad6aabb2c9c53f372490d79316bdee92d476e7/blis-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3f6c595185176ce021316263e1a1d636a3425b6c48366c1fd712d08d0b71849a", size = 1230939, upload-time = "2025-11-17T12:27:46.19Z" }, - { url = "https://files.pythonhosted.org/packages/96/2a/fbf65d906d823d839076c5150a6f8eb5ecbc5f9135e0b6510609bda1e6b7/blis-1.3.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d734b19fba0be7944f272dfa7b443b37c61f9476d9ab054a9ac53555ceadd2e0", size = 2818835, upload-time = "2025-11-17T12:27:48.167Z" }, - { url = "https://files.pythonhosted.org/packages/d5/ad/58deaa3ad856dd3cc96493e40ffd2ed043d18d4d304f85a65cde1ccbf644/blis-1.3.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ef6d6e2b599a3a2788eb6d9b443533961265aa4ec49d574ed4bb846e548dcdb", size = 11366550, upload-time = "2025-11-17T12:27:49.958Z" }, - { url = "https://files.pythonhosted.org/packages/78/82/816a7adfe1f7acc8151f01ec86ef64467a3c833932d8f19f8e06613b8a4e/blis-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8c888438ae99c500422d50698e3028b65caa8ebb44e24204d87fda2df64058f7", size = 3023686, upload-time = "2025-11-17T12:27:52.062Z" }, - { url = "https://files.pythonhosted.org/packages/1e/e2/0e93b865f648b5519360846669a35f28ee8f4e1d93d054f6850d8afbabde/blis-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8177879fd3590b5eecdd377f9deafb5dc8af6d684f065bd01553302fb3fcf9a7", size = 14250939, upload-time = "2025-11-17T12:27:53.847Z" }, - { url = "https://files.pythonhosted.org/packages/20/07/fb43edc2ff0a6a367e4a94fc39eb3b85aa1e55e24cc857af2db145ce9f0d/blis-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:f20f7ad69aaffd1ce14fe77de557b6df9b61e0c9e582f75a843715d836b5c8af", size = 6192759, upload-time = "2025-11-17T12:27:56.176Z" }, -] - -[[package]] -name = "catalogue" -version = "2.0.10" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/38/b4/244d58127e1cdf04cf2dc7d9566f0d24ef01d5ce21811bab088ecc62b5ea/catalogue-2.0.10.tar.gz", hash = "sha256:4f56daa940913d3f09d589c191c74e5a6d51762b3a9e37dd53b7437afd6cda15", size = 19561, upload-time = "2023-09-25T06:29:24.962Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/96/d32b941a501ab566a16358d68b6eb4e4acc373fab3c3c4d7d9e649f7b4bb/catalogue-2.0.10-py3-none-any.whl", hash = "sha256:58c2de0020aa90f4a2da7dfad161bf7b3b054c86a5f09fcedc0b2b740c109a9f", size = 17325, upload-time = "2023-09-25T06:29:23.337Z" }, -] - [[package]] name = "certifi" version = "2025.11.12" @@ -265,15 +222,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a2/58/1f37bf81e3c689cc74ffa42102fa8915b59085f54a6e4a80bc6265c0f6bf/click-8.2.0-py3-none-any.whl", hash = "sha256:6b303f0b2aa85f1cb4e5303078fadcbcd4e476f114fab9b5007005711839325c", size = 102156, upload-time = "2025-05-10T22:21:01.352Z" }, ] -[[package]] -name = "cloudpathlib" -version = "0.23.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f4/18/2ac35d6b3015a0c74e923d94fc69baf8307f7c3233de015d69f99e17afa8/cloudpathlib-0.23.0.tar.gz", hash = "sha256:eb38a34c6b8a048ecfd2b2f60917f7cbad4a105b7c979196450c2f541f4d6b4b", size = 53126, upload-time = "2025-10-07T22:47:56.278Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ae/8a/c4bb04426d608be4a3171efa2e233d2c59a5c8937850c10d098e126df18e/cloudpathlib-0.23.0-py3-none-any.whl", hash = "sha256:8520b3b01468fee77de37ab5d50b1b524ea6b4a8731c35d1b7407ac0cd716002", size = 62755, upload-time = "2025-10-07T22:47:54.905Z" }, -] - [[package]] name = "colorama" version = "0.4.6" @@ -283,68 +231,45 @@ 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 = "confection" -version = "0.1.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pydantic" }, - { name = "srsly" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/51/d3/57c6631159a1b48d273b40865c315cf51f89df7a9d1101094ef12e3a37c2/confection-0.1.5.tar.gz", hash = "sha256:8e72dd3ca6bd4f48913cd220f10b8275978e740411654b6e8ca6d7008c590f0e", size = 38924, upload-time = "2024-05-31T16:17:01.559Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/00/3106b1854b45bd0474ced037dfe6b73b90fe68a68968cef47c23de3d43d2/confection-0.1.5-py3-none-any.whl", hash = "sha256:e29d3c3f8eac06b3f77eb9dfb4bf2fc6bcc9622a98ca00a698e3d019c6430b14", size = 35451, upload-time = "2024-05-31T16:16:59.075Z" }, -] - [[package]] name = "cryptography" -version = "44.0.3" +version = "46.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/53/d6/1411ab4d6108ab167d06254c5be517681f1e331f90edf1379895bcb87020/cryptography-44.0.3.tar.gz", hash = "sha256:fe19d8bc5536a91a24a8133328880a41831b6c5df54599a8417b62fe015d3053", size = 711096, upload-time = "2025-05-02T19:36:04.667Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/08/53/c776d80e9d26441bb3868457909b4e74dd9ccabd182e10b2b0ae7a07e265/cryptography-44.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:962bc30480a08d133e631e8dfd4783ab71cc9e33d5d7c1e192f0b7c06397bb88", size = 6670281, upload-time = "2025-05-02T19:34:50.665Z" }, - { url = "https://files.pythonhosted.org/packages/6a/06/af2cf8d56ef87c77319e9086601bef621bedf40f6f59069e1b6d1ec498c5/cryptography-44.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffc61e8f3bf5b60346d89cd3d37231019c17a081208dfbbd6e1605ba03fa137", size = 3959305, upload-time = "2025-05-02T19:34:53.042Z" }, - { url = "https://files.pythonhosted.org/packages/ae/01/80de3bec64627207d030f47bf3536889efee8913cd363e78ca9a09b13c8e/cryptography-44.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58968d331425a6f9eedcee087f77fd3c927c88f55368f43ff7e0a19891f2642c", size = 4171040, upload-time = "2025-05-02T19:34:54.675Z" }, - { url = "https://files.pythonhosted.org/packages/bd/48/bb16b7541d207a19d9ae8b541c70037a05e473ddc72ccb1386524d4f023c/cryptography-44.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e28d62e59a4dbd1d22e747f57d4f00c459af22181f0b2f787ea83f5a876d7c76", size = 3963411, upload-time = "2025-05-02T19:34:56.61Z" }, - { url = "https://files.pythonhosted.org/packages/42/b2/7d31f2af5591d217d71d37d044ef5412945a8a8e98d5a2a8ae4fd9cd4489/cryptography-44.0.3-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af653022a0c25ef2e3ffb2c673a50e5a0d02fecc41608f4954176f1933b12359", size = 3689263, upload-time = "2025-05-02T19:34:58.591Z" }, - { url = "https://files.pythonhosted.org/packages/25/50/c0dfb9d87ae88ccc01aad8eb93e23cfbcea6a6a106a9b63a7b14c1f93c75/cryptography-44.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:157f1f3b8d941c2bd8f3ffee0af9b049c9665c39d3da9db2dc338feca5e98a43", size = 4196198, upload-time = "2025-05-02T19:35:00.988Z" }, - { url = "https://files.pythonhosted.org/packages/66/c9/55c6b8794a74da652690c898cb43906310a3e4e4f6ee0b5f8b3b3e70c441/cryptography-44.0.3-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:c6cd67722619e4d55fdb42ead64ed8843d64638e9c07f4011163e46bc512cf01", size = 3966502, upload-time = "2025-05-02T19:35:03.091Z" }, - { url = "https://files.pythonhosted.org/packages/b6/f7/7cb5488c682ca59a02a32ec5f975074084db4c983f849d47b7b67cc8697a/cryptography-44.0.3-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:b424563394c369a804ecbee9b06dfb34997f19d00b3518e39f83a5642618397d", size = 4196173, upload-time = "2025-05-02T19:35:05.018Z" }, - { url = "https://files.pythonhosted.org/packages/d2/0b/2f789a8403ae089b0b121f8f54f4a3e5228df756e2146efdf4a09a3d5083/cryptography-44.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c91fc8e8fd78af553f98bc7f2a1d8db977334e4eea302a4bfd75b9461c2d8904", size = 4087713, upload-time = "2025-05-02T19:35:07.187Z" }, - { url = "https://files.pythonhosted.org/packages/1d/aa/330c13655f1af398fc154089295cf259252f0ba5df93b4bc9d9c7d7f843e/cryptography-44.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:25cd194c39fa5a0aa4169125ee27d1172097857b27109a45fadc59653ec06f44", size = 4299064, upload-time = "2025-05-02T19:35:08.879Z" }, - { url = "https://files.pythonhosted.org/packages/10/a8/8c540a421b44fd267a7d58a1fd5f072a552d72204a3f08194f98889de76d/cryptography-44.0.3-cp37-abi3-win32.whl", hash = "sha256:3be3f649d91cb182c3a6bd336de8b61a0a71965bd13d1a04a0e15b39c3d5809d", size = 2773887, upload-time = "2025-05-02T19:35:10.41Z" }, - { url = "https://files.pythonhosted.org/packages/b9/0d/c4b1657c39ead18d76bbd122da86bd95bdc4095413460d09544000a17d56/cryptography-44.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:3883076d5c4cc56dbef0b898a74eb6992fdac29a7b9013870b34efe4ddb39a0d", size = 3209737, upload-time = "2025-05-02T19:35:12.12Z" }, - { url = "https://files.pythonhosted.org/packages/34/a3/ad08e0bcc34ad436013458d7528e83ac29910943cea42ad7dd4141a27bbb/cryptography-44.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:5639c2b16764c6f76eedf722dbad9a0914960d3489c0cc38694ddf9464f1bb2f", size = 6673501, upload-time = "2025-05-02T19:35:13.775Z" }, - { url = "https://files.pythonhosted.org/packages/b1/f0/7491d44bba8d28b464a5bc8cc709f25a51e3eac54c0a4444cf2473a57c37/cryptography-44.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3ffef566ac88f75967d7abd852ed5f182da252d23fac11b4766da3957766759", size = 3960307, upload-time = "2025-05-02T19:35:15.917Z" }, - { url = "https://files.pythonhosted.org/packages/f7/c8/e5c5d0e1364d3346a5747cdcd7ecbb23ca87e6dea4f942a44e88be349f06/cryptography-44.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:192ed30fac1728f7587c6f4613c29c584abdc565d7417c13904708db10206645", size = 4170876, upload-time = "2025-05-02T19:35:18.138Z" }, - { url = "https://files.pythonhosted.org/packages/73/96/025cb26fc351d8c7d3a1c44e20cf9a01e9f7cf740353c9c7a17072e4b264/cryptography-44.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7d5fe7195c27c32a64955740b949070f21cba664604291c298518d2e255931d2", size = 3964127, upload-time = "2025-05-02T19:35:19.864Z" }, - { url = "https://files.pythonhosted.org/packages/01/44/eb6522db7d9f84e8833ba3bf63313f8e257729cf3a8917379473fcfd6601/cryptography-44.0.3-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3f07943aa4d7dad689e3bb1638ddc4944cc5e0921e3c227486daae0e31a05e54", size = 3689164, upload-time = "2025-05-02T19:35:21.449Z" }, - { url = "https://files.pythonhosted.org/packages/68/fb/d61a4defd0d6cee20b1b8a1ea8f5e25007e26aeb413ca53835f0cae2bcd1/cryptography-44.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cb90f60e03d563ca2445099edf605c16ed1d5b15182d21831f58460c48bffb93", size = 4198081, upload-time = "2025-05-02T19:35:23.187Z" }, - { url = "https://files.pythonhosted.org/packages/1b/50/457f6911d36432a8811c3ab8bd5a6090e8d18ce655c22820994913dd06ea/cryptography-44.0.3-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:ab0b005721cc0039e885ac3503825661bd9810b15d4f374e473f8c89b7d5460c", size = 3967716, upload-time = "2025-05-02T19:35:25.426Z" }, - { url = "https://files.pythonhosted.org/packages/35/6e/dca39d553075980ccb631955c47b93d87d27f3596da8d48b1ae81463d915/cryptography-44.0.3-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:3bb0847e6363c037df8f6ede57d88eaf3410ca2267fb12275370a76f85786a6f", size = 4197398, upload-time = "2025-05-02T19:35:27.678Z" }, - { url = "https://files.pythonhosted.org/packages/9b/9d/d1f2fe681eabc682067c66a74addd46c887ebacf39038ba01f8860338d3d/cryptography-44.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b0cc66c74c797e1db750aaa842ad5b8b78e14805a9b5d1348dc603612d3e3ff5", size = 4087900, upload-time = "2025-05-02T19:35:29.312Z" }, - { url = "https://files.pythonhosted.org/packages/c4/f5/3599e48c5464580b73b236aafb20973b953cd2e7b44c7c2533de1d888446/cryptography-44.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6866df152b581f9429020320e5eb9794c8780e90f7ccb021940d7f50ee00ae0b", size = 4301067, upload-time = "2025-05-02T19:35:31.547Z" }, - { url = "https://files.pythonhosted.org/packages/a7/6c/d2c48c8137eb39d0c193274db5c04a75dab20d2f7c3f81a7dcc3a8897701/cryptography-44.0.3-cp39-abi3-win32.whl", hash = "sha256:c138abae3a12a94c75c10499f1cbae81294a6f983b3af066390adee73f433028", size = 2775467, upload-time = "2025-05-02T19:35:33.805Z" }, - { url = "https://files.pythonhosted.org/packages/c9/ad/51f212198681ea7b0deaaf8846ee10af99fba4e894f67b353524eab2bbe5/cryptography-44.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:5d186f32e52e66994dce4f766884bcb9c68b8da62d61d9d215bfe5fb56d21334", size = 3210375, upload-time = "2025-05-02T19:35:35.369Z" }, -] - -[[package]] -name = "cymem" -version = "2.0.13" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/2f0fbb32535c3731b7c2974c569fb9325e0a38ed5565a08e1139a3b71e82/cymem-2.0.13.tar.gz", hash = "sha256:1c91a92ae8c7104275ac26bd4d29b08ccd3e7faff5893d3858cb6fadf1bc1588", size = 12320, upload-time = "2025-11-14T14:58:36.902Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c9/52/478a2911ab5028cb710b4900d64aceba6f4f882fcb13fd8d40a456a1b6dc/cymem-2.0.13-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e8afbc5162a0fe14b6463e1c4e45248a1b2fe2cbcecc8a5b9e511117080da0eb", size = 43745, upload-time = "2025-11-14T14:57:32.52Z" }, - { url = "https://files.pythonhosted.org/packages/f9/71/f0f8adee945524774b16af326bd314a14a478ed369a728a22834e6785a18/cymem-2.0.13-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9251d889348fe79a75e9b3e4d1b5fa651fca8a64500820685d73a3acc21b6a8", size = 42927, upload-time = "2025-11-14T14:57:33.827Z" }, - { url = "https://files.pythonhosted.org/packages/62/6d/159780fe162ff715d62b809246e5fc20901cef87ca28b67d255a8d741861/cymem-2.0.13-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:742fc19764467a49ed22e56a4d2134c262d73a6c635409584ae3bf9afa092c33", size = 258346, upload-time = "2025-11-14T14:57:34.917Z" }, - { url = "https://files.pythonhosted.org/packages/eb/12/678d16f7aa1996f947bf17b8cfb917ea9c9674ef5e2bd3690c04123d5680/cymem-2.0.13-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f190a92fe46197ee64d32560eb121c2809bb843341733227f51538ce77b3410d", size = 260843, upload-time = "2025-11-14T14:57:36.503Z" }, - { url = "https://files.pythonhosted.org/packages/31/5d/0dd8c167c08cd85e70d274b7235cfe1e31b3cebc99221178eaf4bbb95c6f/cymem-2.0.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d670329ee8dbbbf241b7c08069fe3f1d3a1a3e2d69c7d05ea008a7010d826298", size = 254607, upload-time = "2025-11-14T14:57:38.036Z" }, - { url = "https://files.pythonhosted.org/packages/b7/c9/d6514a412a1160aa65db539836b3d47f9b59f6675f294ec34ae32f867c82/cymem-2.0.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a84ba3178d9128b9ffb52ce81ebab456e9fe959125b51109f5b73ebdfc6b60d6", size = 262421, upload-time = "2025-11-14T14:57:39.265Z" }, - { url = "https://files.pythonhosted.org/packages/dd/fe/3ee37d02ca4040f2fb22d34eb415198f955862b5dd47eee01df4c8f5454c/cymem-2.0.13-cp312-cp312-win_amd64.whl", hash = "sha256:2ff1c41fd59b789579fdace78aa587c5fc091991fa59458c382b116fc36e30dc", size = 40176, upload-time = "2025-11-14T14:57:40.706Z" }, - { url = "https://files.pythonhosted.org/packages/94/fb/1b681635bfd5f2274d0caa8f934b58435db6c091b97f5593738065ddb786/cymem-2.0.13-cp312-cp312-win_arm64.whl", hash = "sha256:6bbd701338df7bf408648191dff52472a9b334f71bcd31a21a41d83821050f67", size = 35959, upload-time = "2025-11-14T14:57:41.682Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/9f/33/c00162f49c0e2fe8064a62cb92b93e50c74a72bc370ab92f86112b33ff62/cryptography-46.0.3.tar.gz", hash = "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1", size = 749258, upload-time = "2025-10-15T23:18:31.74Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/42/9c391dd801d6cf0d561b5890549d4b27bafcc53b39c31a817e69d87c625b/cryptography-46.0.3-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a", size = 7225004, upload-time = "2025-10-15T23:16:52.239Z" }, + { url = "https://files.pythonhosted.org/packages/1c/67/38769ca6b65f07461eb200e85fc1639b438bdc667be02cf7f2cd6a64601c/cryptography-46.0.3-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc", size = 4296667, upload-time = "2025-10-15T23:16:54.369Z" }, + { url = "https://files.pythonhosted.org/packages/5c/49/498c86566a1d80e978b42f0d702795f69887005548c041636df6ae1ca64c/cryptography-46.0.3-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d", size = 4450807, upload-time = "2025-10-15T23:16:56.414Z" }, + { url = "https://files.pythonhosted.org/packages/4b/0a/863a3604112174c8624a2ac3c038662d9e59970c7f926acdcfaed8d61142/cryptography-46.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb", size = 4299615, upload-time = "2025-10-15T23:16:58.442Z" }, + { url = "https://files.pythonhosted.org/packages/64/02/b73a533f6b64a69f3cd3872acb6ebc12aef924d8d103133bb3ea750dc703/cryptography-46.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849", size = 4016800, upload-time = "2025-10-15T23:17:00.378Z" }, + { url = "https://files.pythonhosted.org/packages/25/d5/16e41afbfa450cde85a3b7ec599bebefaef16b5c6ba4ec49a3532336ed72/cryptography-46.0.3-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8", size = 4984707, upload-time = "2025-10-15T23:17:01.98Z" }, + { url = "https://files.pythonhosted.org/packages/c9/56/e7e69b427c3878352c2fb9b450bd0e19ed552753491d39d7d0a2f5226d41/cryptography-46.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec", size = 4482541, upload-time = "2025-10-15T23:17:04.078Z" }, + { url = "https://files.pythonhosted.org/packages/78/f6/50736d40d97e8483172f1bb6e698895b92a223dba513b0ca6f06b2365339/cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91", size = 4299464, upload-time = "2025-10-15T23:17:05.483Z" }, + { url = "https://files.pythonhosted.org/packages/00/de/d8e26b1a855f19d9994a19c702fa2e93b0456beccbcfe437eda00e0701f2/cryptography-46.0.3-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e", size = 4950838, upload-time = "2025-10-15T23:17:07.425Z" }, + { url = "https://files.pythonhosted.org/packages/8f/29/798fc4ec461a1c9e9f735f2fc58741b0daae30688f41b2497dcbc9ed1355/cryptography-46.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926", size = 4481596, upload-time = "2025-10-15T23:17:09.343Z" }, + { url = "https://files.pythonhosted.org/packages/15/8d/03cd48b20a573adfff7652b76271078e3045b9f49387920e7f1f631d125e/cryptography-46.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71", size = 4426782, upload-time = "2025-10-15T23:17:11.22Z" }, + { url = "https://files.pythonhosted.org/packages/fa/b1/ebacbfe53317d55cf33165bda24c86523497a6881f339f9aae5c2e13e57b/cryptography-46.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac", size = 4698381, upload-time = "2025-10-15T23:17:12.829Z" }, + { url = "https://files.pythonhosted.org/packages/96/92/8a6a9525893325fc057a01f654d7efc2c64b9de90413adcf605a85744ff4/cryptography-46.0.3-cp311-abi3-win32.whl", hash = "sha256:f260d0d41e9b4da1ed1e0f1ce571f97fe370b152ab18778e9e8f67d6af432018", size = 3055988, upload-time = "2025-10-15T23:17:14.65Z" }, + { url = "https://files.pythonhosted.org/packages/7e/bf/80fbf45253ea585a1e492a6a17efcb93467701fa79e71550a430c5e60df0/cryptography-46.0.3-cp311-abi3-win_amd64.whl", hash = "sha256:a9a3008438615669153eb86b26b61e09993921ebdd75385ddd748702c5adfddb", size = 3514451, upload-time = "2025-10-15T23:17:16.142Z" }, + { url = "https://files.pythonhosted.org/packages/2e/af/9b302da4c87b0beb9db4e756386a7c6c5b8003cd0e742277888d352ae91d/cryptography-46.0.3-cp311-abi3-win_arm64.whl", hash = "sha256:5d7f93296ee28f68447397bf5198428c9aeeab45705a55d53a6343455dcb2c3c", size = 2928007, upload-time = "2025-10-15T23:17:18.04Z" }, + { url = "https://files.pythonhosted.org/packages/fd/23/45fe7f376a7df8daf6da3556603b36f53475a99ce4faacb6ba2cf3d82021/cryptography-46.0.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:cb3d760a6117f621261d662bccc8ef5bc32ca673e037c83fbe565324f5c46936", size = 7218248, upload-time = "2025-10-15T23:17:46.294Z" }, + { url = "https://files.pythonhosted.org/packages/27/32/b68d27471372737054cbd34c84981f9edbc24fe67ca225d389799614e27f/cryptography-46.0.3-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683", size = 4294089, upload-time = "2025-10-15T23:17:48.269Z" }, + { url = "https://files.pythonhosted.org/packages/26/42/fa8389d4478368743e24e61eea78846a0006caffaf72ea24a15159215a14/cryptography-46.0.3-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d", size = 4440029, upload-time = "2025-10-15T23:17:49.837Z" }, + { url = "https://files.pythonhosted.org/packages/5f/eb/f483db0ec5ac040824f269e93dd2bd8a21ecd1027e77ad7bdf6914f2fd80/cryptography-46.0.3-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0", size = 4297222, upload-time = "2025-10-15T23:17:51.357Z" }, + { url = "https://files.pythonhosted.org/packages/fd/cf/da9502c4e1912cb1da3807ea3618a6829bee8207456fbbeebc361ec38ba3/cryptography-46.0.3-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc", size = 4012280, upload-time = "2025-10-15T23:17:52.964Z" }, + { url = "https://files.pythonhosted.org/packages/6b/8f/9adb86b93330e0df8b3dcf03eae67c33ba89958fc2e03862ef1ac2b42465/cryptography-46.0.3-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3", size = 4978958, upload-time = "2025-10-15T23:17:54.965Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a0/5fa77988289c34bdb9f913f5606ecc9ada1adb5ae870bd0d1054a7021cc4/cryptography-46.0.3-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971", size = 4473714, upload-time = "2025-10-15T23:17:56.754Z" }, + { url = "https://files.pythonhosted.org/packages/14/e5/fc82d72a58d41c393697aa18c9abe5ae1214ff6f2a5c18ac470f92777895/cryptography-46.0.3-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac", size = 4296970, upload-time = "2025-10-15T23:17:58.588Z" }, + { url = "https://files.pythonhosted.org/packages/78/06/5663ed35438d0b09056973994f1aec467492b33bd31da36e468b01ec1097/cryptography-46.0.3-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04", size = 4940236, upload-time = "2025-10-15T23:18:00.897Z" }, + { url = "https://files.pythonhosted.org/packages/fc/59/873633f3f2dcd8a053b8dd1d38f783043b5fce589c0f6988bf55ef57e43e/cryptography-46.0.3-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506", size = 4472642, upload-time = "2025-10-15T23:18:02.749Z" }, + { url = "https://files.pythonhosted.org/packages/3d/39/8e71f3930e40f6877737d6f69248cf74d4e34b886a3967d32f919cc50d3b/cryptography-46.0.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963", size = 4423126, upload-time = "2025-10-15T23:18:04.85Z" }, + { url = "https://files.pythonhosted.org/packages/cd/c7/f65027c2810e14c3e7268353b1681932b87e5a48e65505d8cc17c99e36ae/cryptography-46.0.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4", size = 4686573, upload-time = "2025-10-15T23:18:06.908Z" }, + { url = "https://files.pythonhosted.org/packages/0a/6e/1c8331ddf91ca4730ab3086a0f1be19c65510a33b5a441cb334e7a2d2560/cryptography-46.0.3-cp38-abi3-win32.whl", hash = "sha256:6276eb85ef938dc035d59b87c8a7dc559a232f954962520137529d77b18ff1df", size = 3036695, upload-time = "2025-10-15T23:18:08.672Z" }, + { url = "https://files.pythonhosted.org/packages/90/45/b0d691df20633eff80955a0fc7695ff9051ffce8b69741444bd9ed7bd0db/cryptography-46.0.3-cp38-abi3-win_amd64.whl", hash = "sha256:416260257577718c05135c55958b674000baef9a1c7d9e8f306ec60d71db850f", size = 3501720, upload-time = "2025-10-15T23:18:10.632Z" }, + { url = "https://files.pythonhosted.org/packages/e8/cb/2da4cc83f5edb9c3257d09e1e7ab7b23f049c7962cae8d842bbef0a9cec9/cryptography-46.0.3-cp38-abi3-win_arm64.whl", hash = "sha256:d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372", size = 2918740, upload-time = "2025-10-15T23:18:12.277Z" }, ] [[package]] @@ -494,6 +419,56 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/51/c7/b64cae5dba3a1b138d7123ec36bb5ccd39d39939f18454407e5468f4763f/fsspec-2025.12.0-py3-none-any.whl", hash = "sha256:8bf1fe301b7d8acfa6e8571e3b1c3d158f909666642431cc78a1b7b4dbc5ec5b", size = 201422, upload-time = "2025-12-03T15:23:41.434Z" }, ] +[[package]] +name = "goland-ia" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "fastapi" }, + { name = "guardrails-ai" }, + { name = "ipython" }, + { name = "langchain" }, + { name = "langchain-community" }, + { name = "langchain-core" }, + { name = "langchain-text-splitters" }, + { name = "langgraph" }, + { name = "minio" }, + { name = "pdfplumber" }, + { name = "pgvector" }, + { name = "psycopg2-binary" }, + { name = "pydantic-settings" }, + { name = "sqlalchemy" }, + { name = "typing-extensions" }, + { name = "uvicorn" }, +] + +[package.optional-dependencies] +dev = [ + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [ + { name = "fastapi", specifier = ">=0.124.2" }, + { name = "guardrails-ai", specifier = ">=0.5.10" }, + { name = "ipython", specifier = ">=9.8.0" }, + { name = "langchain", specifier = ">=1.2.0" }, + { name = "langchain-community", specifier = ">=0.4.1" }, + { name = "langchain-core", specifier = ">=0.3.0" }, + { name = "langchain-text-splitters", specifier = ">=1.1.0" }, + { name = "langgraph", specifier = ">=1.0.4" }, + { name = "minio", specifier = ">=7.2.20" }, + { name = "pdfplumber", specifier = ">=0.11.8" }, + { name = "pgvector", specifier = ">=0.3.0" }, + { name = "psycopg2-binary", specifier = ">=2.9.0" }, + { name = "pydantic-settings", specifier = ">=2.0.0" }, + { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.14.0" }, + { name = "sqlalchemy", specifier = ">=2.0.0" }, + { name = "typing-extensions", specifier = ">=4.15.0" }, + { name = "uvicorn", specifier = ">=0.38.0" }, +] +provides-extras = ["dev"] + [[package]] name = "googleapis-common-protos" version = "1.72.0" @@ -947,38 +922,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/59/97/57497c8b26829e38c8dd4abe972d75e38fc3904324a3042bb01d9e0753b8/langchain_core-1.2.2-py3-none-any.whl", hash = "sha256:3a83dc14217de5cba11b1a0bd43c48702401bbd18dc25cac2ffab5ac83a61cd0", size = 476125, upload-time = "2025-12-16T20:25:52.581Z" }, ] -[[package]] -name = "langchain-openai" -version = "1.1.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "langchain-core" }, - { name = "openai" }, - { name = "tiktoken" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/fc/1c/008a6dd7b3523121be1a4f24701b099ae79193dab9b329dfb787bece08bf/langchain_openai-1.1.5.tar.gz", hash = "sha256:a8ca5f3919bd948867c7d427a575b34f7c141110ef7cbc14ea7bbc46363871de", size = 1038129, upload-time = "2025-12-17T19:14:36.392Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/c5/22b690a27ba6b1ca6876270473aab1610cb8767314e5038cb6b826d9b69b/langchain_openai-1.1.5-py3-none-any.whl", hash = "sha256:d3a3b0c39e1513bbb9e5d4526c194909a00c5733195dbe90bfea6619b00420ca", size = 84569, upload-time = "2025-12-17T19:14:35.529Z" }, -] - -[[package]] -name = "langchain-postgres" -version = "0.0.16" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "asyncpg" }, - { name = "langchain-core" }, - { name = "numpy" }, - { name = "pgvector" }, - { name = "psycopg", extra = ["binary"] }, - { name = "psycopg-pool" }, - { name = "sqlalchemy", extra = ["asyncio"] }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6f/5e/00065782aa0ad7b5faa9ff6881bcf361f2a7741e39db8e2b3e86164f80c8/langchain_postgres-0.0.16.tar.gz", hash = "sha256:d09aa4ea77ee8600a9ff64de9c185fb558aa388c816c7be04dd4559c878530b7", size = 232526, upload-time = "2025-10-17T15:00:29.203Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/a2/516934f8be231e50bb2afda8112641850154b057f5c45d82f42d216bed3d/langchain_postgres-0.0.16-py3-none-any.whl", hash = "sha256:a7375cf9fc9b6965efc207dbcc959424e96b8ffe75d5ced6055676d2613f8d37", size = 46028, upload-time = "2025-10-17T15:00:27.772Z" }, -] - [[package]] name = "langchain-text-splitters" version = "1.1.0" @@ -1240,22 +1183,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/da/7d22601b625e241d4f23ef1ebff8acfc60da633c9e7e7922e24d10f592b3/multidict-6.7.0-py3-none-any.whl", hash = "sha256:394fc5c42a333c9ffc3e421a4c85e08580d990e08b99f6bf35b4132114c5dcb3", size = 12317, upload-time = "2025-10-06T14:52:29.272Z" }, ] -[[package]] -name = "murmurhash" -version = "1.0.15" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/23/2e/88c147931ea9725d634840d538622e94122bceaf346233349b7b5c62964b/murmurhash-1.0.15.tar.gz", hash = "sha256:58e2b27b7847f9e2a6edf10b47a8c8dd70a4705f45dccb7bf76aeadacf56ba01", size = 13291, upload-time = "2025-11-14T09:51:15.272Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b6/46/be8522d3456fdccf1b8b049c6d82e7a3c1114c4fc2cfe14b04cba4b3e701/murmurhash-1.0.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d37e3ae44746bca80b1a917c2ea625cf216913564ed43f69d2888e5df97db0cb", size = 27884, upload-time = "2025-11-14T09:50:13.133Z" }, - { url = "https://files.pythonhosted.org/packages/ed/cc/630449bf4f6178d7daf948ce46ad00b25d279065fc30abd8d706be3d87e0/murmurhash-1.0.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0861cb11039409eaf46878456b7d985ef17b6b484103a6fc367b2ecec846891d", size = 27855, upload-time = "2025-11-14T09:50:14.859Z" }, - { url = "https://files.pythonhosted.org/packages/ff/30/ea8f601a9bf44db99468696efd59eb9cff1157cd55cb586d67116697583f/murmurhash-1.0.15-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5a301decfaccfec70fe55cb01dde2a012c3014a874542eaa7cc73477bb749616", size = 134088, upload-time = "2025-11-14T09:50:15.958Z" }, - { url = "https://files.pythonhosted.org/packages/c9/de/c40ce8c0877d406691e735b8d6e9c815f36a82b499d358313db5dbe219d7/murmurhash-1.0.15-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:32c6fde7bd7e9407003370a07b5f4addacabe1556ad3dc2cac246b7a2bba3400", size = 133978, upload-time = "2025-11-14T09:50:17.572Z" }, - { url = "https://files.pythonhosted.org/packages/47/84/bd49963ecd84ebab2fe66595e2d1ed41d5e8b5153af5dc930f0bd827007c/murmurhash-1.0.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d8b43a7011540dc3c7ce66f2134df9732e2bc3bbb4a35f6458bc755e48bde26", size = 132956, upload-time = "2025-11-14T09:50:18.742Z" }, - { url = "https://files.pythonhosted.org/packages/4f/7c/2530769c545074417c862583f05f4245644599f1e9ff619b3dfe2969aafc/murmurhash-1.0.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:43bf4541892ecd95963fcd307bf1c575fc0fee1682f41c93007adee71ca2bb40", size = 134184, upload-time = "2025-11-14T09:50:19.941Z" }, - { url = "https://files.pythonhosted.org/packages/84/a4/b249b042f5afe34d14ada2dc4afc777e883c15863296756179652e081c44/murmurhash-1.0.15-cp312-cp312-win_amd64.whl", hash = "sha256:f4ac15a2089dc42e6eb0966622d42d2521590a12c92480aafecf34c085302cca", size = 25647, upload-time = "2025-11-14T09:50:21.049Z" }, - { url = "https://files.pythonhosted.org/packages/13/bf/028179259aebc18fd4ba5cae2601d1d47517427a537ab44336446431a215/murmurhash-1.0.15-cp312-cp312-win_arm64.whl", hash = "sha256:4a70ca4ae19e600d9be3da64d00710e79dde388a4d162f22078d64844d0ebdda", size = 23338, upload-time = "2025-11-14T09:50:22.359Z" }, -] - [[package]] name = "mypy-extensions" version = "1.1.0" @@ -1502,23 +1429,14 @@ wheels = [ [[package]] name = "pgvector" -version = "0.3.6" +version = "0.4.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7d/d8/fd6009cee3e03214667df488cdcf9609461d729968da94e4f95d6359d304/pgvector-0.3.6.tar.gz", hash = "sha256:31d01690e6ea26cea8a633cde5f0f55f5b246d9c8292d68efdef8c22ec994ade", size = 25421, upload-time = "2024-10-27T00:15:09.632Z" } +sdist = { url = "https://files.pythonhosted.org/packages/25/6c/6d8b4b03b958c02fa8687ec6063c49d952a189f8c91ebbe51e877dfab8f7/pgvector-0.4.2.tar.gz", hash = "sha256:322cac0c1dc5d41c9ecf782bd9991b7966685dee3a00bc873631391ed949513a", size = 31354, upload-time = "2025-12-05T01:07:17.87Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/81/f457d6d361e04d061bef413749a6e1ab04d98cfeec6d8abcfe40184750f3/pgvector-0.3.6-py3-none-any.whl", hash = "sha256:f6c269b3c110ccb7496bac87202148ed18f34b390a0189c783e351062400a75a", size = 24880, upload-time = "2024-10-27T00:15:08.045Z" }, -] - -[[package]] -name = "phonenumbers" -version = "9.0.20" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1b/2a/d6e7fecd1bcd37e65fbb9386e492082bd84e196c9cab14e39166b35cea4b/phonenumbers-9.0.20.tar.gz", hash = "sha256:849788eec8e5a9737a99c8b906d18a62d9fced6497ba7033784b6a7e4c89bb2d", size = 2298276, upload-time = "2025-12-05T12:03:51.072Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/90/b0/e3fd0ae68bc51593ef6f77a44901d1fa14c1b230cdce848b50ed10881ca3/phonenumbers-9.0.20-py2.py3-none-any.whl", hash = "sha256:03bf5dd14891891284ba96f803d0e5e7e11b9306a0ec4fdf25756ada39eacb86", size = 2584219, upload-time = "2025-12-05T12:03:48.388Z" }, + { url = "https://files.pythonhosted.org/packages/5a/26/6cee8a1ce8c43625ec561aff19df07f9776b7525d9002c86bceb3e0ac970/pgvector-0.4.2-py3-none-any.whl", hash = "sha256:549d45f7a18593783d5eec609ea1684a724ba8405c4cb182a0b2b08aeff04e08", size = 27441, upload-time = "2025-12-05T01:07:16.536Z" }, ] [[package]] @@ -1549,52 +1467,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/44/3c/d717024885424591d5376220b5e836c2d5293ce2011523c9de23ff7bf068/pip-25.3-py3-none-any.whl", hash = "sha256:9655943313a94722b7774661c21049070f6bbb0a1516bf02f7c8d5d9201514cd", size = 1778622, upload-time = "2025-10-25T00:55:39.247Z" }, ] -[[package]] -name = "preshed" -version = "3.0.12" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cymem" }, - { name = "murmurhash" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/bf/34/eb4f5f0f678e152a96e826da867d2f41c4b18a2d589e40e1dd3347219e91/preshed-3.0.12.tar.gz", hash = "sha256:b73f9a8b54ee1d44529cc6018356896cff93d48f755f29c134734d9371c0d685", size = 15027, upload-time = "2025-11-17T13:00:33.621Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/f7/ff3aca937eeaee19c52c45ddf92979546e52ed0686e58be4bc09c47e7d88/preshed-3.0.12-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2779861f5d69480493519ed123a622a13012d1182126779036b99d9d989bf7e9", size = 129958, upload-time = "2025-11-17T12:59:33.391Z" }, - { url = "https://files.pythonhosted.org/packages/80/24/fd654a9c0f5f3ed1a9b1d8a392f063ae9ca29ad0b462f0732ae0147f7cee/preshed-3.0.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffe1fd7d92f51ed34383e20d8b734780c814ca869cfdb7e07f2d31651f90cdf4", size = 124550, upload-time = "2025-11-17T12:59:34.688Z" }, - { url = "https://files.pythonhosted.org/packages/71/49/8271c7f680696f4b0880f44357d2a903d649cb9f6e60a1efc97a203104df/preshed-3.0.12-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:91893404858502cc4e856d338fef3d2a4a552135f79a1041c24eb919817c19db", size = 874987, upload-time = "2025-11-17T12:59:36.062Z" }, - { url = "https://files.pythonhosted.org/packages/a3/a5/ca200187ca1632f1e2c458b72f1bd100fa8b55deecd5d72e1e4ebf09e98c/preshed-3.0.12-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9e06e8f2ba52f183eb9817a616cdebe84a211bb859a2ffbc23f3295d0b189638", size = 866499, upload-time = "2025-11-17T12:59:37.586Z" }, - { url = "https://files.pythonhosted.org/packages/87/a1/943b61f850c44899910c21996cb542d0ef5931744c6d492fdfdd8457e693/preshed-3.0.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bbe8b8a2d4f9af14e8a39ecca524b9de6defc91d8abcc95eb28f42da1c23272c", size = 878064, upload-time = "2025-11-17T12:59:39.651Z" }, - { url = "https://files.pythonhosted.org/packages/3e/75/d7fff7f1fa3763619aa85d6ba70493a5d9c6e6ea7958a6e8c9d3e6e88bbe/preshed-3.0.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5d0aaac9c5862f5471fddd0c931dc64d3af2efc5fe3eb48b50765adb571243b9", size = 900540, upload-time = "2025-11-17T12:59:41.384Z" }, - { url = "https://files.pythonhosted.org/packages/e4/12/a2285b78bd097a1e53fb90a1743bc8ce0d35e5b65b6853f3b3c47da398ca/preshed-3.0.12-cp312-cp312-win_amd64.whl", hash = "sha256:0eb8d411afcb1e3b12a0602fb6a0e33140342a732a795251a0ce452aba401dc0", size = 118298, upload-time = "2025-11-17T12:59:42.65Z" }, - { url = "https://files.pythonhosted.org/packages/0b/34/4e8443fe99206a2fcfc63659969a8f8c8ab184836533594a519f3899b1ad/preshed-3.0.12-cp312-cp312-win_arm64.whl", hash = "sha256:dcd3d12903c9f720a39a5c5f1339f7f46e3ab71279fb7a39776768fb840b6077", size = 104746, upload-time = "2025-11-17T12:59:43.934Z" }, -] - -[[package]] -name = "presidio-analyzer" -version = "2.2.360" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "phonenumbers" }, - { name = "pyyaml" }, - { name = "regex" }, - { name = "spacy" }, - { name = "tldextract" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/dd/df0d00ff44977868878f391b08feb65b8269904262d0f1387de827c13c7d/presidio_analyzer-2.2.360-py3-none-any.whl", hash = "sha256:aa6e83779b8f23587000ccfa3ef47aa34356a60b51284742e0d2a96b33205c4e", size = 128661, upload-time = "2025-09-09T09:24:20.75Z" }, -] - -[[package]] -name = "presidio-anonymizer" -version = "2.2.360" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cryptography" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/79/d8438168402057092a36ba8f7aee099f107a7960a38eee505f1948f9e0c9/presidio_anonymizer-2.2.360-py3-none-any.whl", hash = "sha256:5f46f36bf3a0b8817b91f09ed3e51093cc331192e148311f1cd69ae0bf3f4a6c", size = 35864, upload-time = "2025-09-09T09:24:12.176Z" }, -] - [[package]] name = "prompt-toolkit" version = "3.0.52" @@ -1647,51 +1519,22 @@ wheels = [ ] [[package]] -name = "psycopg" -version = "3.3.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, - { name = "tzdata", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e0/1a/7d9ef4fdc13ef7f15b934c393edc97a35c281bb7d3c3329fbfcbe915a7c2/psycopg-3.3.2.tar.gz", hash = "sha256:707a67975ee214d200511177a6a80e56e654754c9afca06a7194ea6bbfde9ca7", size = 165630, upload-time = "2025-12-06T17:34:53.899Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8c/51/2779ccdf9305981a06b21a6b27e8547c948d85c41c76ff434192784a4c93/psycopg-3.3.2-py3-none-any.whl", hash = "sha256:3e94bc5f4690247d734599af56e51bae8e0db8e4311ea413f801fef82b14a99b", size = 212774, upload-time = "2025-12-06T17:31:41.414Z" }, -] - -[package.optional-dependencies] -binary = [ - { name = "psycopg-binary", marker = "implementation_name != 'pypy'" }, -] - -[[package]] -name = "psycopg-binary" -version = "3.3.2" +name = "psycopg2-binary" +version = "2.9.11" source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/6c/8767aaa597ba424643dc87348c6f1754dd9f48e80fdc1b9f7ca5c3a7c213/psycopg2-binary-2.9.11.tar.gz", hash = "sha256:b6aed9e096bf63f9e75edf2581aa9a7e7186d97ab5c177aa6c87797cd591236c", size = 379620, upload-time = "2025-10-10T11:14:48.041Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/1e/8614b01c549dd7e385dacdcd83fe194f6b3acb255a53cc67154ee6bf00e7/psycopg_binary-3.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a9387ab615f929e71ef0f4a8a51e986fa06236ccfa9f3ec98a88f60fbf230634", size = 4579832, upload-time = "2025-12-06T17:33:01.388Z" }, - { url = "https://files.pythonhosted.org/packages/26/97/0bb093570fae2f4454d42c1ae6000f15934391867402f680254e4a7def54/psycopg_binary-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3ff7489df5e06c12d1829544eaec64970fe27fe300f7cf04c8495fe682064688", size = 4658786, upload-time = "2025-12-06T17:33:05.022Z" }, - { url = "https://files.pythonhosted.org/packages/61/20/1d9383e3f2038826900a14137b0647d755f67551aab316e1021443105ed5/psycopg_binary-3.3.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:9742580ecc8e1ac45164e98d32ca6df90da509c2d3ff26be245d94c430f92db4", size = 5454896, upload-time = "2025-12-06T17:33:09.023Z" }, - { url = "https://files.pythonhosted.org/packages/a6/62/513c80ad8bbb545e364f7737bf2492d34a4c05eef4f7b5c16428dc42260d/psycopg_binary-3.3.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d45acedcaa58619355f18e0f42af542fcad3fd84ace4b8355d3a5dea23318578", size = 5132731, upload-time = "2025-12-06T17:33:12.519Z" }, - { url = "https://files.pythonhosted.org/packages/f3/28/ddf5f5905f088024bccb19857949467407c693389a14feb527d6171d8215/psycopg_binary-3.3.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d88f32ff8c47cb7f4e7e7a9d1747dcee6f3baa19ed9afa9e5694fd2fb32b61ed", size = 6724495, upload-time = "2025-12-06T17:33:16.624Z" }, - { url = "https://files.pythonhosted.org/packages/6e/93/a1157ebcc650960b264542b547f7914d87a42ff0cc15a7584b29d5807e6b/psycopg_binary-3.3.2-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:59d0163c4617a2c577cb34afbed93d7a45b8c8364e54b2bd2020ff25d5f5f860", size = 4964979, upload-time = "2025-12-06T17:33:20.179Z" }, - { url = "https://files.pythonhosted.org/packages/0e/27/65939ba6798f9c5be4a5d9cd2061ebaf0851798525c6811d347821c8132d/psycopg_binary-3.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e750afe74e6c17b2c7046d2c3e3173b5a3f6080084671c8aa327215323df155b", size = 4493648, upload-time = "2025-12-06T17:33:23.464Z" }, - { url = "https://files.pythonhosted.org/packages/8a/c4/5e9e4b9b1c1e27026e43387b0ba4aaf3537c7806465dd3f1d5bde631752a/psycopg_binary-3.3.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f26f113013c4dcfbfe9ced57b5bad2035dda1a7349f64bf726021968f9bccad3", size = 4173392, upload-time = "2025-12-06T17:33:26.88Z" }, - { url = "https://files.pythonhosted.org/packages/c6/81/cf43fb76993190cee9af1cbcfe28afb47b1928bdf45a252001017e5af26e/psycopg_binary-3.3.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8309ee4569dced5e81df5aa2dcd48c7340c8dee603a66430f042dfbd2878edca", size = 3909241, upload-time = "2025-12-06T17:33:30.092Z" }, - { url = "https://files.pythonhosted.org/packages/9d/20/c6377a0d17434674351627489deca493ea0b137c522b99c81d3a106372c8/psycopg_binary-3.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c6464150e25b68ae3cb04c4e57496ea11ebfaae4d98126aea2f4702dd43e3c12", size = 4219746, upload-time = "2025-12-06T17:33:33.097Z" }, - { url = "https://files.pythonhosted.org/packages/25/32/716c57b28eefe02a57a4c9d5bf956849597f5ea476c7010397199e56cfde/psycopg_binary-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:716a586f99bbe4f710dc58b40069fcb33c7627e95cc6fc936f73c9235e07f9cf", size = 3537494, upload-time = "2025-12-06T17:33:35.82Z" }, -] - -[[package]] -name = "psycopg-pool" -version = "3.3.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/56/9a/9470d013d0d50af0da9c4251614aeb3c1823635cab3edc211e3839db0bcf/psycopg_pool-3.3.0.tar.gz", hash = "sha256:fa115eb2860bd88fce1717d75611f41490dec6135efb619611142b24da3f6db5", size = 31606, upload-time = "2025-12-01T11:34:33.11Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/c3/26b8a0908a9db249de3b4169692e1c7c19048a9bc41a4d3209cee7dbb758/psycopg_pool-3.3.0-py3-none-any.whl", hash = "sha256:2e44329155c410b5e8666372db44276a8b1ebd8c90f1c3026ebba40d4bc81063", size = 39995, upload-time = "2025-12-01T11:34:29.761Z" }, + { url = "https://files.pythonhosted.org/packages/d8/91/f870a02f51be4a65987b45a7de4c2e1897dd0d01051e2b559a38fa634e3e/psycopg2_binary-2.9.11-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:be9b840ac0525a283a96b556616f5b4820e0526addb8dcf6525a0fa162730be4", size = 3756603, upload-time = "2025-10-10T11:11:52.213Z" }, + { url = "https://files.pythonhosted.org/packages/27/fa/cae40e06849b6c9a95eb5c04d419942f00d9eaac8d81626107461e268821/psycopg2_binary-2.9.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f090b7ddd13ca842ebfe301cd587a76a4cf0913b1e429eb92c1be5dbeb1a19bc", size = 3864509, upload-time = "2025-10-10T11:11:56.452Z" }, + { url = "https://files.pythonhosted.org/packages/2d/75/364847b879eb630b3ac8293798e380e441a957c53657995053c5ec39a316/psycopg2_binary-2.9.11-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ab8905b5dcb05bf3fb22e0cf90e10f469563486ffb6a96569e51f897c750a76a", size = 4411159, upload-time = "2025-10-10T11:12:00.49Z" }, + { url = "https://files.pythonhosted.org/packages/6f/a0/567f7ea38b6e1c62aafd58375665a547c00c608a471620c0edc364733e13/psycopg2_binary-2.9.11-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:bf940cd7e7fec19181fdbc29d76911741153d51cab52e5c21165f3262125685e", size = 4468234, upload-time = "2025-10-10T11:12:04.892Z" }, + { url = "https://files.pythonhosted.org/packages/30/da/4e42788fb811bbbfd7b7f045570c062f49e350e1d1f3df056c3fb5763353/psycopg2_binary-2.9.11-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fa0f693d3c68ae925966f0b14b8edda71696608039f4ed61b1fe9ffa468d16db", size = 4166236, upload-time = "2025-10-10T11:12:11.674Z" }, + { url = "https://files.pythonhosted.org/packages/3c/94/c1777c355bc560992af848d98216148be5f1be001af06e06fc49cbded578/psycopg2_binary-2.9.11-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a1cf393f1cdaf6a9b57c0a719a1068ba1069f022a59b8b1fe44b006745b59757", size = 3983083, upload-time = "2025-10-30T02:55:15.73Z" }, + { url = "https://files.pythonhosted.org/packages/bd/42/c9a21edf0e3daa7825ed04a4a8588686c6c14904344344a039556d78aa58/psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ef7a6beb4beaa62f88592ccc65df20328029d721db309cb3250b0aae0fa146c3", size = 3652281, upload-time = "2025-10-10T11:12:17.713Z" }, + { url = "https://files.pythonhosted.org/packages/12/22/dedfbcfa97917982301496b6b5e5e6c5531d1f35dd2b488b08d1ebc52482/psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:31b32c457a6025e74d233957cc9736742ac5a6cb196c6b68499f6bb51390bd6a", size = 3298010, upload-time = "2025-10-10T11:12:22.671Z" }, + { url = "https://files.pythonhosted.org/packages/66/ea/d3390e6696276078bd01b2ece417deac954dfdd552d2edc3d03204416c0c/psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:edcb3aeb11cb4bf13a2af3c53a15b3d612edeb6409047ea0b5d6a21a9d744b34", size = 3044641, upload-time = "2025-10-30T02:55:19.929Z" }, + { url = "https://files.pythonhosted.org/packages/12/9a/0402ded6cbd321da0c0ba7d34dc12b29b14f5764c2fc10750daa38e825fc/psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b6d93d7c0b61a1dd6197d208ab613eb7dcfdcca0a49c42ceb082257991de9d", size = 3347940, upload-time = "2025-10-10T11:12:26.529Z" }, + { url = "https://files.pythonhosted.org/packages/b1/d2/99b55e85832ccde77b211738ff3925a5d73ad183c0b37bcbbe5a8ff04978/psycopg2_binary-2.9.11-cp312-cp312-win_amd64.whl", hash = "sha256:b33fabeb1fde21180479b2d4667e994de7bbf0eec22832ba5d9b5e4cf65b6c6d", size = 2714147, upload-time = "2025-10-10T11:12:29.535Z" }, ] [[package]] @@ -1896,66 +1739,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, ] -[[package]] -name = "rag-manager" -version = "0.1.0" -source = { virtual = "." } -dependencies = [ - { name = "fastapi" }, - { name = "guardrails-ai" }, - { name = "ipython" }, - { name = "langchain" }, - { name = "langchain-community" }, - { name = "langchain-core" }, - { name = "langchain-openai" }, - { name = "langchain-postgres" }, - { name = "langchain-text-splitters" }, - { name = "langgraph" }, - { name = "minio" }, - { name = "pdfplumber" }, - { name = "pgvector" }, - { name = "presidio-analyzer" }, - { name = "presidio-anonymizer" }, - { name = "psycopg", extra = ["binary"] }, - { name = "pydantic-settings" }, - { name = "sqlalchemy" }, - { name = "typing-extensions" }, - { name = "uvicorn" }, -] - -[package.optional-dependencies] -dev = [ - { name = "ruff" }, -] - -[package.metadata] -requires-dist = [ - { name = "fastapi", specifier = ">=0.124.2" }, - { name = "guardrails-ai", specifier = ">=0.5.10" }, - { name = "ipython", specifier = ">=9.8.0" }, - { name = "langchain", specifier = ">=0.3.0" }, - { name = "langchain", specifier = ">=1.2.0" }, - { name = "langchain-community", specifier = ">=0.4.1" }, - { name = "langchain-core", specifier = ">=0.3.0" }, - { name = "langchain-openai", specifier = ">=0.3.0" }, - { name = "langchain-postgres", specifier = ">=0.0.14" }, - { name = "langchain-text-splitters", specifier = ">=0.3.0" }, - { name = "langchain-text-splitters", specifier = ">=1.1.0" }, - { name = "langgraph", specifier = ">=1.0.4" }, - { name = "minio", specifier = ">=7.2.20" }, - { name = "pdfplumber", specifier = ">=0.11.8" }, - { name = "pgvector", specifier = ">=0.3.0" }, - { name = "presidio-analyzer", specifier = ">=2.2.360" }, - { name = "presidio-anonymizer", specifier = ">=2.2.360" }, - { name = "psycopg", extras = ["binary"], specifier = ">=3.0.0" }, - { name = "pydantic-settings", specifier = ">=2.0.0" }, - { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.14.0" }, - { name = "sqlalchemy", specifier = ">=2.0.0" }, - { name = "typing-extensions", specifier = ">=4.15.0" }, - { name = "uvicorn", specifier = ">=0.38.0" }, -] -provides-extras = ["dev"] - [[package]] name = "referencing" version = "0.37.0" @@ -2007,18 +1790,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, ] -[[package]] -name = "requests-file" -version = "3.0.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3c/f8/5dc70102e4d337063452c82e1f0d95e39abfe67aa222ed8a5ddeb9df8de8/requests_file-3.0.1.tar.gz", hash = "sha256:f14243d7796c588f3521bd423c5dea2ee4cc730e54a3cac9574d78aca1272576", size = 6967, upload-time = "2025-10-20T18:56:42.279Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/d5/de8f089119205a09da657ed4784c584ede8381a0ce6821212a6d4ca47054/requests_file-3.0.1-py2.py3-none-any.whl", hash = "sha256:d0f5eb94353986d998f80ac63c7f146a307728be051d4d1cd390dbdb59c10fa2", size = 4514, upload-time = "2025-10-20T18:56:41.184Z" }, -] - [[package]] name = "requests-toolbelt" version = "1.0.0" @@ -2144,15 +1915,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a6/24/4d91e05817e92e3a61c8a21e08fd0f390f5301f1c448b137c57c4bc6e543/semver-3.0.4-py3-none-any.whl", hash = "sha256:9c824d87ba7f7ab4a1890799cec8596f15c1241cb473404ea1cb0c55e4b04746", size = 17912, upload-time = "2025-01-24T13:19:24.949Z" }, ] -[[package]] -name = "setuptools" -version = "80.9.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, -] - [[package]] name = "shellingham" version = "1.5.4" @@ -2171,18 +1933,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 = "smart-open" -version = "7.5.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "wrapt" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/67/9a/0a7acb748b86e2922982366d780ca4b16c33f7246fa5860d26005c97e4f3/smart_open-7.5.0.tar.gz", hash = "sha256:f394b143851d8091011832ac8113ea4aba6b92e6c35f6e677ddaaccb169d7cb9", size = 53920, upload-time = "2025-11-08T21:38:40.698Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ad/95/bc978be7ea0babf2fb48a414b6afaad414c6a9e8b1eafc5b8a53c030381a/smart_open-7.5.0-py3-none-any.whl", hash = "sha256:87e695c5148bbb988f15cec00971602765874163be85acb1c9fb8abc012e6599", size = 63940, upload-time = "2025-11-08T21:38:39.024Z" }, -] - [[package]] name = "sniffio" version = "1.3.1" @@ -2192,60 +1942,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, ] -[[package]] -name = "spacy" -version = "3.8.11" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "catalogue" }, - { name = "cymem" }, - { name = "jinja2" }, - { name = "murmurhash" }, - { name = "numpy" }, - { name = "packaging" }, - { name = "preshed" }, - { name = "pydantic" }, - { name = "requests" }, - { name = "setuptools" }, - { name = "spacy-legacy" }, - { name = "spacy-loggers" }, - { name = "srsly" }, - { name = "thinc" }, - { name = "tqdm" }, - { name = "typer-slim" }, - { name = "wasabi" }, - { name = "weasel" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/59/9f/424244b0e2656afc9ff82fb7a96931a47397bfce5ba382213827b198312a/spacy-3.8.11.tar.gz", hash = "sha256:54e1e87b74a2f9ea807ffd606166bf29ac45e2bd81ff7f608eadc7b05787d90d", size = 1326804, upload-time = "2025-11-17T20:40:03.079Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/51/fb/01eadf4ba70606b3054702dc41fc2ccf7d70fb14514b3cd57f0ff78ebea8/spacy-3.8.11-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:aa1ee8362074c30098feaaf2dd888c829a1a79c4311eec1b117a0a61f16fa6dd", size = 6073726, upload-time = "2025-11-17T20:39:01.679Z" }, - { url = "https://files.pythonhosted.org/packages/3a/f8/07b03a2997fc2621aaeafae00af50f55522304a7da6926b07027bb6d0709/spacy-3.8.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:75a036d04c2cf11d6cb566c0a689860cc5a7a75b439e8fea1b3a6b673dabf25d", size = 5724702, upload-time = "2025-11-17T20:39:03.486Z" }, - { url = "https://files.pythonhosted.org/packages/13/0c/c4fa0f379dbe3258c305d2e2df3760604a9fcd71b34f8f65c23e43f4cf55/spacy-3.8.11-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cb599d2747d4a59a5f90e8a453c149b13db382a8297925cf126333141dbc4f7", size = 32727774, upload-time = "2025-11-17T20:39:05.894Z" }, - { url = "https://files.pythonhosted.org/packages/ce/8e/6a4ba82bed480211ebdf5341b0f89e7271b454307525ac91b5e447825914/spacy-3.8.11-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:94632e302ad2fb79dc285bf1e9e4d4a178904d5c67049e0e02b7fb4a77af85c4", size = 33215053, upload-time = "2025-11-17T20:39:08.588Z" }, - { url = "https://files.pythonhosted.org/packages/a6/bc/44d863d248e9d7358c76a0aa8b3f196b8698df520650ed8de162e18fbffb/spacy-3.8.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aeca6cf34009d48cda9fb1bbfb532469e3d643817241a73e367b34ab99a5806f", size = 32074195, upload-time = "2025-11-17T20:39:11.601Z" }, - { url = "https://files.pythonhosted.org/packages/6f/7d/0b115f3f16e1dd2d3f99b0f89497867fc11c41aed94f4b7a4367b4b54136/spacy-3.8.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:368a79b8df925b15d89dccb5e502039446fb2ce93cf3020e092d5b962c3349b9", size = 32996143, upload-time = "2025-11-17T20:39:14.705Z" }, - { url = "https://files.pythonhosted.org/packages/7d/48/7e9581b476df76aaf9ee182888d15322e77c38b0bbbd5e80160ba0bddd4c/spacy-3.8.11-cp312-cp312-win_amd64.whl", hash = "sha256:88d65941a87f58d75afca1785bd64d01183a92f7269dcbcf28bd9d6f6a77d1a7", size = 14217511, upload-time = "2025-11-17T20:39:17.316Z" }, - { url = "https://files.pythonhosted.org/packages/7b/1f/307a16f32f90aa5ee7ad8d29ff8620a57132b80a4c8c536963d46d192e1a/spacy-3.8.11-cp312-cp312-win_arm64.whl", hash = "sha256:97b865d6d3658e2ab103a67d6c8a2d678e193e84a07f40d9938565b669ceee39", size = 13614446, upload-time = "2025-11-17T20:39:19.748Z" }, -] - -[[package]] -name = "spacy-legacy" -version = "3.0.12" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d9/79/91f9d7cc8db5642acad830dcc4b49ba65a7790152832c4eceb305e46d681/spacy-legacy-3.0.12.tar.gz", hash = "sha256:b37d6e0c9b6e1d7ca1cf5bc7152ab64a4c4671f59c85adaf7a3fcb870357a774", size = 23806, upload-time = "2023-01-23T09:04:15.104Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/55/12e842c70ff8828e34e543a2c7176dac4da006ca6901c9e8b43efab8bc6b/spacy_legacy-3.0.12-py2.py3-none-any.whl", hash = "sha256:476e3bd0d05f8c339ed60f40986c07387c0a71479245d6d0f4298dbd52cda55f", size = 29971, upload-time = "2023-01-23T09:04:13.45Z" }, -] - -[[package]] -name = "spacy-loggers" -version = "1.0.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/67/3d/926db774c9c98acf66cb4ed7faf6c377746f3e00b84b700d0868b95d0712/spacy-loggers-1.0.5.tar.gz", hash = "sha256:d60b0bdbf915a60e516cc2e653baeff946f0cfc461b452d11a4d5458c6fe5f24", size = 20811, upload-time = "2023-09-11T12:26:52.323Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/33/78/d1a1a026ef3af911159398c939b1509d5c36fe524c7b644f34a5146c4e16/spacy_loggers-1.0.5-py3-none-any.whl", hash = "sha256:196284c9c446cc0cdb944005384270d775fdeaf4f494d8e269466cfa497ef645", size = 22343, upload-time = "2023-09-11T12:26:50.586Z" }, -] - [[package]] name = "sqlalchemy" version = "2.0.45" @@ -2265,29 +1961,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bf/e1/3ccb13c643399d22289c6a9786c1a91e3dcbb68bce4beb44926ac2c557bf/sqlalchemy-2.0.45-py3-none-any.whl", hash = "sha256:5225a288e4c8cc2308dbdd874edad6e7d0fd38eac1e9e5f23503425c8eee20d0", size = 1936672, upload-time = "2025-12-09T21:54:52.608Z" }, ] -[package.optional-dependencies] -asyncio = [ - { name = "greenlet" }, -] - -[[package]] -name = "srsly" -version = "2.5.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "catalogue" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cf/77/5633c4ba65e3421b72b5b4bd93aa328360b351b3a1e5bf3c90eb224668e5/srsly-2.5.2.tar.gz", hash = "sha256:4092bc843c71b7595c6c90a0302a197858c5b9fe43067f62ae6a45bc3baa1c19", size = 492055, upload-time = "2025-11-17T14:11:02.543Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8f/1c/21f658d98d602a559491b7886c7ca30245c2cd8987ff1b7709437c0f74b1/srsly-2.5.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6f92b4f883e6be4ca77f15980b45d394d310f24903e25e1b2c46df783c7edcce", size = 656161, upload-time = "2025-11-17T14:10:03.181Z" }, - { url = "https://files.pythonhosted.org/packages/2f/a2/bc6fd484ed703857043ae9abd6c9aea9152f9480a6961186ee6c1e0c49e8/srsly-2.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ac4790a54b00203f1af5495b6b8ac214131139427f30fcf05cf971dde81930eb", size = 653237, upload-time = "2025-11-17T14:10:04.636Z" }, - { url = "https://files.pythonhosted.org/packages/ab/ea/e3895da29a15c8d325e050ad68a0d1238eece1d2648305796adf98dcba66/srsly-2.5.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ce5c6b016050857a7dd365c9dcdd00d96e7ac26317cfcb175db387e403de05bf", size = 1174418, upload-time = "2025-11-17T14:10:05.945Z" }, - { url = "https://files.pythonhosted.org/packages/a6/a5/21996231f53ee97191d0746c3a672ba33a4d86a19ffad85a1c0096c91c5f/srsly-2.5.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:539c6d0016e91277b5e9be31ebed03f03c32580d49c960e4a92c9003baecf69e", size = 1183089, upload-time = "2025-11-17T14:10:07.335Z" }, - { url = "https://files.pythonhosted.org/packages/7b/df/eb17aa8e4a828e8df7aa7dc471295529d9126e6b710f1833ebe0d8568a8e/srsly-2.5.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9f24b2c4f4c29da04083f09158543eb3f8893ba0ac39818693b3b259ee8044f0", size = 1122594, upload-time = "2025-11-17T14:10:08.899Z" }, - { url = "https://files.pythonhosted.org/packages/80/74/1654a80e6c8ec3ee32370ea08a78d3651e0ba1c4d6e6be31c9efdb9a2d10/srsly-2.5.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d34675047460a3f6999e43478f40d9b43917ea1e93a75c41d05bf7648f3e872d", size = 1139594, upload-time = "2025-11-17T14:10:10.286Z" }, - { url = "https://files.pythonhosted.org/packages/73/aa/8393344ca7f0e81965febba07afc5cad68335ed0426408d480b861ab915b/srsly-2.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:81fd133ba3c66c07f0e3a889d2b4c852984d71ea833a665238a9d47d8e051ba5", size = 654750, upload-time = "2025-11-17T14:10:11.637Z" }, -] - [[package]] name = "stack-data" version = "0.6.3" @@ -2324,36 +1997,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e5/30/643397144bfbfec6f6ef821f36f33e57d35946c44a2352d3c9f0ae847619/tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138", size = 28248, upload-time = "2025-04-02T08:25:07.678Z" }, ] -[[package]] -name = "thinc" -version = "8.3.10" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "blis" }, - { name = "catalogue" }, - { name = "confection" }, - { name = "cymem" }, - { name = "murmurhash" }, - { name = "numpy" }, - { name = "packaging" }, - { name = "preshed" }, - { name = "pydantic" }, - { name = "setuptools" }, - { name = "srsly" }, - { name = "wasabi" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/2f/3a/2d0f0be132b9faaa6d56f04565ae122684273e4bf4eab8dee5f48dc00f68/thinc-8.3.10.tar.gz", hash = "sha256:5a75109f4ee1c968fc055ce651a17cb44b23b000d9e95f04a4d047ab3cb3e34e", size = 194196, upload-time = "2025-11-17T17:21:46.435Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d3/34/ba3b386d92edf50784b60ee34318d47c7f49c198268746ef7851c5bbe8cf/thinc-8.3.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:51bc6ef735bdbcab75ab2916731b8f61f94c66add6f9db213d900d3c6a244f95", size = 794509, upload-time = "2025-11-17T17:21:03.21Z" }, - { url = "https://files.pythonhosted.org/packages/07/f3/9f52d18115cd9d8d7b2590d226cb2752d2a5ffec61576b19462b48410184/thinc-8.3.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4f48b4d346915f98e9722c0c50ef911cc16c6790a2b7afebc6e1a2c96a6ce6c6", size = 741084, upload-time = "2025-11-17T17:21:04.568Z" }, - { url = "https://files.pythonhosted.org/packages/ad/9c/129c2b740c4e3d3624b6fb3dec1577ef27cb804bc1647f9bc3e1801ea20c/thinc-8.3.10-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5003f4db2db22cc8d686db8db83509acc3c50f4c55ebdcb2bbfcc1095096f7d2", size = 3846337, upload-time = "2025-11-17T17:21:06.079Z" }, - { url = "https://files.pythonhosted.org/packages/22/d2/738cf188dea8240c2be081c83ea47270fea585eba446171757d2cdb9b675/thinc-8.3.10-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b12484c3ed0632331fada2c334680dd6bc35972d0717343432dfc701f04a9b4c", size = 3901216, upload-time = "2025-11-17T17:21:07.842Z" }, - { url = "https://files.pythonhosted.org/packages/22/92/32f66eb9b1a29b797bf378a0874615d810d79eefca1d6c736c5ca3f8b918/thinc-8.3.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8677c446d3f9b97a465472c58683b785b25dfcf26c683e3f4e8f8c7c188e4362", size = 4827286, upload-time = "2025-11-17T17:21:09.62Z" }, - { url = "https://files.pythonhosted.org/packages/c4/5f/7ceae1e1f2029efd67ed88e23cd6dc13a5ee647cdc2b35113101b2a62c10/thinc-8.3.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:759c385ac08dcf950238b60b96a28f9c04618861141766928dff4a51b1679b25", size = 5024421, upload-time = "2025-11-17T17:21:11.199Z" }, - { url = "https://files.pythonhosted.org/packages/0b/66/30f9d8d41049b78bc614213d492792fbcfeb1b28642adf661c42110a7ebd/thinc-8.3.10-cp312-cp312-win_amd64.whl", hash = "sha256:bf3f188c3fa1fdcefd547d1f90a1245c29025d6d0e3f71d7fdf21dad210b990c", size = 1718631, upload-time = "2025-11-17T17:21:12.965Z" }, - { url = "https://files.pythonhosted.org/packages/f8/44/32e2a5018a1165a304d25eb9b1c74e5310da19a533a35331e8d824dc6a88/thinc-8.3.10-cp312-cp312-win_arm64.whl", hash = "sha256:234b7e57a6ef4e0260d99f4e8fdc328ed12d0ba9bbd98fdaa567294a17700d1c", size = 1642224, upload-time = "2025-11-17T17:21:14.371Z" }, -] - [[package]] name = "tiktoken" version = "0.12.0" @@ -2373,21 +2016,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/81/10/b8523105c590c5b8349f2587e2fdfe51a69544bd5a76295fc20f2374f470/tiktoken-0.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffc5288f34a8bc02e1ea7047b8d041104791d2ddbf42d1e5fa07822cbffe16bd", size = 878694, upload-time = "2025-10-06T20:21:59.876Z" }, ] -[[package]] -name = "tldextract" -version = "5.3.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "filelock" }, - { name = "idna" }, - { name = "requests" }, - { name = "requests-file" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/97/78/182641ea38e3cfd56e9c7b3c0d48a53d432eea755003aa544af96403d4ac/tldextract-5.3.0.tar.gz", hash = "sha256:b3d2b70a1594a0ecfa6967d57251527d58e00bb5a91a74387baa0d87a0678609", size = 128502, upload-time = "2025-04-22T06:19:37.491Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/67/7c/ea488ef48f2f544566947ced88541bc45fae9e0e422b2edbf165ee07da99/tldextract-5.3.0-py3-none-any.whl", hash = "sha256:f70f31d10b55c83993f55e91ecb7c5d84532a8972f22ec578ecfbe5ea2292db2", size = 107384, upload-time = "2025-04-22T06:19:36.304Z" }, -] - [[package]] name = "tokenizers" version = "0.22.1" @@ -2558,18 +2186,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ee/d9/d88e73ca598f4f6ff671fb5fde8a32925c2e08a637303a1d12883c7305fa/uvicorn-0.38.0-py3-none-any.whl", hash = "sha256:48c0afd214ceb59340075b4a052ea1ee91c16fbc2a9b1469cca0e54566977b02", size = 68109, upload-time = "2025-10-18T13:46:42.958Z" }, ] -[[package]] -name = "wasabi" -version = "1.1.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ac/f9/054e6e2f1071e963b5e746b48d1e3727470b2a490834d18ad92364929db3/wasabi-1.1.3.tar.gz", hash = "sha256:4bb3008f003809db0c3e28b4daf20906ea871a2bb43f9914197d540f4f2e0878", size = 30391, upload-time = "2024-05-31T16:56:18.99Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/06/7c/34330a89da55610daa5f245ddce5aab81244321101614751e7537f125133/wasabi-1.1.3-py3-none-any.whl", hash = "sha256:f76e16e8f7e79f8c4c8be49b4024ac725713ab10cd7f19350ad18a8e3f71728c", size = 27880, upload-time = "2024-05-31T16:56:16.699Z" }, -] - [[package]] name = "wcwidth" version = "0.2.14" @@ -2579,26 +2195,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1", size = 37286, upload-time = "2025-09-22T16:29:51.641Z" }, ] -[[package]] -name = "weasel" -version = "0.4.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cloudpathlib" }, - { name = "confection" }, - { name = "packaging" }, - { name = "pydantic" }, - { name = "requests" }, - { name = "smart-open" }, - { name = "srsly" }, - { name = "typer-slim" }, - { name = "wasabi" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/09/d7/edd9c24e60cf8e5de130aa2e8af3b01521f4d0216c371d01212f580d0d8e/weasel-0.4.3.tar.gz", hash = "sha256:f293d6174398e8f478c78481e00c503ee4b82ea7a3e6d0d6a01e46a6b1396845", size = 38733, upload-time = "2025-11-13T23:52:28.193Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/74/a148b41572656904a39dfcfed3f84dd1066014eed94e209223ae8e9d088d/weasel-0.4.3-py3-none-any.whl", hash = "sha256:08f65b5d0dbded4879e08a64882de9b9514753d9eaa4c4e2a576e33666ac12cf", size = 50757, upload-time = "2025-11-13T23:52:26.982Z" }, -] - [[package]] name = "webcolors" version = "25.10.0" @@ -2608,27 +2204,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl", hash = "sha256:032c727334856fc0b968f63daa252a1ac93d33db2f5267756623c210e57a4f1d", size = 14905, upload-time = "2025-10-31T07:51:01.778Z" }, ] -[[package]] -name = "wrapt" -version = "2.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/49/2a/6de8a50cb435b7f42c46126cf1a54b2aab81784e74c8595c8e025e8f36d3/wrapt-2.0.1.tar.gz", hash = "sha256:9c9c635e78497cacb81e84f8b11b23e0aacac7a136e73b8e5b2109a1d9fc468f", size = 82040, upload-time = "2025-11-07T00:45:33.312Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/73/8cb252858dc8254baa0ce58ce382858e3a1cf616acebc497cb13374c95c6/wrapt-2.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1fdbb34da15450f2b1d735a0e969c24bdb8d8924892380126e2a293d9902078c", size = 78129, upload-time = "2025-11-07T00:43:48.852Z" }, - { url = "https://files.pythonhosted.org/packages/19/42/44a0db2108526ee6e17a5ab72478061158f34b08b793df251d9fbb9a7eb4/wrapt-2.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3d32794fe940b7000f0519904e247f902f0149edbe6316c710a8562fb6738841", size = 61205, upload-time = "2025-11-07T00:43:50.402Z" }, - { url = "https://files.pythonhosted.org/packages/4d/8a/5b4b1e44b791c22046e90d9b175f9a7581a8cc7a0debbb930f81e6ae8e25/wrapt-2.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:386fb54d9cd903ee0012c09291336469eb7b244f7183d40dc3e86a16a4bace62", size = 61692, upload-time = "2025-11-07T00:43:51.678Z" }, - { url = "https://files.pythonhosted.org/packages/11/53/3e794346c39f462bcf1f58ac0487ff9bdad02f9b6d5ee2dc84c72e0243b2/wrapt-2.0.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7b219cb2182f230676308cdcacd428fa837987b89e4b7c5c9025088b8a6c9faf", size = 121492, upload-time = "2025-11-07T00:43:55.017Z" }, - { url = "https://files.pythonhosted.org/packages/c6/7e/10b7b0e8841e684c8ca76b462a9091c45d62e8f2de9c4b1390b690eadf16/wrapt-2.0.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:641e94e789b5f6b4822bb8d8ebbdfc10f4e4eae7756d648b717d980f657a9eb9", size = 123064, upload-time = "2025-11-07T00:43:56.323Z" }, - { url = "https://files.pythonhosted.org/packages/0e/d1/3c1e4321fc2f5ee7fd866b2d822aa89b84495f28676fd976c47327c5b6aa/wrapt-2.0.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fe21b118b9f58859b5ebaa4b130dee18669df4bd111daad082b7beb8799ad16b", size = 117403, upload-time = "2025-11-07T00:43:53.258Z" }, - { url = "https://files.pythonhosted.org/packages/a4/b0/d2f0a413cf201c8c2466de08414a15420a25aa83f53e647b7255cc2fab5d/wrapt-2.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:17fb85fa4abc26a5184d93b3efd2dcc14deb4b09edcdb3535a536ad34f0b4dba", size = 121500, upload-time = "2025-11-07T00:43:57.468Z" }, - { url = "https://files.pythonhosted.org/packages/bd/45/bddb11d28ca39970a41ed48a26d210505120f925918592283369219f83cc/wrapt-2.0.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:b89ef9223d665ab255ae42cc282d27d69704d94be0deffc8b9d919179a609684", size = 116299, upload-time = "2025-11-07T00:43:58.877Z" }, - { url = "https://files.pythonhosted.org/packages/81/af/34ba6dd570ef7a534e7eec0c25e2615c355602c52aba59413411c025a0cb/wrapt-2.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a453257f19c31b31ba593c30d997d6e5be39e3b5ad9148c2af5a7314061c63eb", size = 120622, upload-time = "2025-11-07T00:43:59.962Z" }, - { url = "https://files.pythonhosted.org/packages/e2/3e/693a13b4146646fb03254636f8bafd20c621955d27d65b15de07ab886187/wrapt-2.0.1-cp312-cp312-win32.whl", hash = "sha256:3e271346f01e9c8b1130a6a3b0e11908049fe5be2d365a5f402778049147e7e9", size = 58246, upload-time = "2025-11-07T00:44:03.169Z" }, - { url = "https://files.pythonhosted.org/packages/a7/36/715ec5076f925a6be95f37917b66ebbeaa1372d1862c2ccd7a751574b068/wrapt-2.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:2da620b31a90cdefa9cd0c2b661882329e2e19d1d7b9b920189956b76c564d75", size = 60492, upload-time = "2025-11-07T00:44:01.027Z" }, - { url = "https://files.pythonhosted.org/packages/ef/3e/62451cd7d80f65cc125f2b426b25fbb6c514bf6f7011a0c3904fc8c8df90/wrapt-2.0.1-cp312-cp312-win_arm64.whl", hash = "sha256:aea9c7224c302bc8bfc892b908537f56c430802560e827b75ecbde81b604598b", size = 58987, upload-time = "2025-11-07T00:44:02.095Z" }, - { url = "https://files.pythonhosted.org/packages/15/d1/b51471c11592ff9c012bd3e2f7334a6ff2f42a7aed2caffcf0bdddc9cb89/wrapt-2.0.1-py3-none-any.whl", hash = "sha256:4d2ce1bf1a48c5277d7969259232b57645aae5686dba1eaeade39442277afbca", size = 44046, upload-time = "2025-11-07T00:45:32.116Z" }, -] - [[package]] name = "xxhash" version = "3.6.0"