diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..f4ff876 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,30 @@ +.git/ +.github/ +.venv/ +__pycache__/ +*.py[cod] +.pytest_cache/ +.ruff_cache/ +.mypy_cache/ + +.env +.env.* +!.env.example + +data/raw/* +!data/raw/.gitkeep +data/processed/* +!data/processed/.gitkeep +data/logs/* +!data/logs/.gitkeep +data/memory.db +data/memory.db-* + +eval/results/* +!eval/results/.gitkeep + +Dockerfile +docker-compose.yml +README.md +docs/ +tests/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..cb7b39e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,25 @@ +FROM python:3.12-slim + +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + UV_LINK_MODE=copy \ + PATH="/app/.venv/bin:$PATH" + +WORKDIR /app + +RUN apt-get update \ + && apt-get install -y --no-install-recommends curl \ + && rm -rf /var/lib/apt/lists/* \ + && pip install --no-cache-dir uv + +COPY pyproject.toml uv.lock ./ +RUN uv sync --frozen --no-dev --no-install-project \ + && rm -rf /root/.cache/uv + +COPY src ./src +COPY ui ./ui +COPY scripts ./scripts + +EXPOSE 8000 8501 + +CMD ["uvicorn", "src.api.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/README.md b/README.md index 3c22eaf..b988a7d 100644 --- a/README.md +++ b/README.md @@ -33,3 +33,21 @@ uvicorn src.api.main:app --reload # UI streamlit run ui/app.py ``` + +## Docker + +```bash +docker compose up --build +``` + +- UI: http://localhost:8501 +- API docs: http://localhost:8000/docs +- Health: http://localhost:8000/health +- Docker image excludes offline evaluation dependencies such as RAGAS. + +## Evaluation + +```bash +uv sync --extra eval +uv run --extra eval python eval/run_eval.py --help +``` diff --git a/docker-compose.yml b/docker-compose.yml index da236c3..5b7d98c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,5 +9,42 @@ services: - qdrant_storage:/qdrant/storage restart: unless-stopped + api: + image: rag-agent-app:latest + build: + context: . + container_name: rag_memory_api + env_file: + - .env + environment: + QDRANT_URL: http://qdrant:6333 + command: uvicorn src.api.main:app --host 0.0.0.0 --port 8000 + ports: + - "8000:8000" + volumes: + - ./data:/app/data + depends_on: + - qdrant + restart: unless-stopped + + ui: + image: rag-agent-app:latest + build: + context: . + container_name: rag_memory_ui + env_file: + - .env + environment: + API_BASE: http://api:8000 + QDRANT_URL: http://qdrant:6333 + command: streamlit run ui/app.py --server.address=0.0.0.0 --server.port=8501 + ports: + - "8501:8501" + volumes: + - ./data:/app/data + depends_on: + - api + restart: unless-stopped + volumes: qdrant_storage: diff --git a/pyproject.toml b/pyproject.toml index 56999fd..36117fd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,9 +31,6 @@ dependencies = [ # UI "streamlit>=1.40", - # Evaluation - "ragas>=0.2", - # Observability "langfuse>=2.50", "loguru>=0.7", @@ -48,6 +45,9 @@ dependencies = [ ] [project.optional-dependencies] +eval = [ + "ragas>=0.2", +] dev = [ "pytest>=8.3", "pytest-asyncio>=0.24", diff --git a/ui/app.py b/ui/app.py index dc177de..4873256 100644 --- a/ui/app.py +++ b/ui/app.py @@ -2,6 +2,7 @@ import asyncio import json +import os from collections.abc import AsyncIterator, Iterator from pathlib import Path from typing import Any @@ -9,7 +10,7 @@ import httpx import streamlit as st -API_BASE = "http://localhost:8000" +API_BASE = os.getenv("API_BASE", "http://localhost:8000") def http_client() -> httpx.Client: diff --git a/uv.lock b/uv.lock index ea20bed..3e04121 100644 --- a/uv.lock +++ b/uv.lock @@ -28,7 +28,6 @@ dependencies = [ { name = "pydantic-settings" }, { name = "python-multipart" }, { name = "qdrant-client" }, - { name = "ragas" }, { name = "rank-bm25" }, { name = "sqlalchemy" }, { name = "streamlit" }, @@ -43,6 +42,9 @@ dev = [ { name = "pytest-asyncio" }, { name = "ruff" }, ] +eval = [ + { name = "ragas" }, +] [package.metadata] requires-dist = [ @@ -63,7 +65,7 @@ requires-dist = [ { name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.24" }, { name = "python-multipart", specifier = ">=0.0.9" }, { name = "qdrant-client", specifier = ">=1.13" }, - { name = "ragas", specifier = ">=0.2" }, + { name = "ragas", marker = "extra == 'eval'", specifier = ">=0.2" }, { name = "rank-bm25", specifier = ">=0.2" }, { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.6" }, { name = "sqlalchemy", specifier = ">=2.0" }, @@ -71,7 +73,7 @@ requires-dist = [ { name = "trafilatura", specifier = ">=1.12" }, { name = "uvicorn", extras = ["standard"], specifier = ">=0.32" }, ] -provides-extras = ["dev"] +provides-extras = ["eval", "dev"] [[package]] name = "aiohappyeyeballs"