Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 84 additions & 18 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,32 +1,84 @@
# ==============================================================
# Qwen / DashScope API — default provider
# ==============================================================
# Get your key at: https://dashscope.aliyun.com/
LLM_API_KEY=sk-your-dashscope-api-key-here
LLM_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
# =============================================================================
# Shared application configuration
# Copy this file to .env at the repository root. edu-platform/.env is a symlink
# to the root file, so this is the only environment template to maintain.
# =============================================================================

# Docker image namespace (GitHub username or organization that owns the images)
GITHUB_OWNER=your-github-account
# IMAGE_TAG=latest
# Next.js listens on loopback by default. Keep this value behind a reverse proxy;
# use 0.0.0.0 only when port 3000 must be reachable directly from the network.
APP_BIND_ADDRESS=127.0.0.1
APP_PORT=3000

# Containerized PostgreSQL. Use URL-safe characters in POSTGRES_PASSWORD, or set
# CONTAINER_DATABASE_URL and CONTAINER_RAG_PG_DSN explicitly.
POSTGRES_USER=edu
POSTGRES_PASSWORD=change-me-database-password
POSTGRES_DB=edu_platform
RAG_POSTGRES_DB=edu_rag
# CONTAINER_DATABASE_URL=postgresql://edu:password@postgres:5432/edu_platform?schema=public
# CONTAINER_RAG_PG_DSN=postgresql://edu:password@postgres:5432/edu_rag

# Next.js authentication and internal service authentication
JWT_SECRET=change-me-to-a-long-random-string-at-least-32-chars
# JWT_ISS=edu-platform
INTERNAL_API_KEY=change-me-internal-api-key-16+

# Initial administrator created by `cd edu-platform && npm run db:seed`
SEED_ADMIN_USERNAME=admin
SEED_ADMIN_PASSWORD=set-a-strong-password-here

# Local object storage
MINIO_ENDPOINT=http://127.0.0.1:9000
MINIO_ACCESS_KEY=minio
MINIO_SECRET_KEY=minio_minio_minio
MINIO_BUCKET=edu-materials
MINIO_USE_SSL=false
# Public origin used in browser/model-facing presigned URLs. In production,
# reverse proxy this HTTPS hostname to 127.0.0.1:9000 on the Docker host.
# MINIO_PUBLIC_ENDPOINT=https://files.example.com

# Optional Cloudflare Tunnel (`docker compose --profile tunnel up -d`).
# CLOUDFLARE_TUNNEL_TOKEN=

# Next.js reaches the local Python RAG API through this URL
RAG_SERVICE_URL=http://127.0.0.1:8001

# ==============================================================
# DeepSeek API — default provider for app and evaluation LLM calls
# ==============================================================
# Create a key at: https://platform.deepseek.com/api_keys
LLM_API_KEY=sk-your-deepseek-api-key
LLM_BASE_URL=https://api.deepseek.com
LLM_MODEL=deepseek-flash

# ==============================================================
# Chat / assignment model — separate provider (e.g. DeepSeek)
# Falls back to LLM_API_KEY / LLM_BASE_URL / LLM_MODEL when unset.
# ==============================================================
# LLM_CHAT_API_KEY=sk-your-deepseek-key
# LLM_CHAT_BASE_URL=https://api.deepseek.com/v1
# LLM_CHAT_MODEL=deepseek-v4-pro
# LLM_CHAT_API_KEY=sk-your-deepseek-api-key
# LLM_CHAT_BASE_URL=https://api.deepseek.com
LLM_CHAT_MODEL=deepseek-flash

# ==============================================================
# Completion model — prefix continuation / FIM (e.g. teacher feedback ghost text)
# Keep separate key var for future model/provider switching clarity.
# ==============================================================
# LLM_COMPLETION_API_KEY=sk-your-deepseek-key
# LLM_COMPLETION_BASE_URL=https://api.deepseek.com/beta
# LLM_COMPLETION_MODEL=deepseek-v4-flash
# LLM_COMPLETION_API_KEY=sk-your-deepseek-api-key
LLM_COMPLETION_BASE_URL=https://api.deepseek.com/beta
LLM_COMPLETION_MODEL=deepseek-flash

# ==============================================================
# Model names (defaults match src/rag_mvp/config.py Settings)
# ==============================================================
# LLM_MODEL=qwen-plus-2025-04-28
# REFINE_MODEL=qwen-long
# VISION_MODEL=qwen3.6-plus
LLM_AUXILIARY_MODEL=deepseek-flash
LLM_TITLE_MODEL=deepseek-flash
LLM_VISION_MODEL=deepseek-flash
VISION_MODEL=deepseek-flash
REFINE_MODEL=deepseek-flash
VIDEO_SUMMARY_LLM_MODEL=deepseek-flash
#
# --- Embeddings (EMBEDDING_MODE selects backend; EMBEDDING_MODEL is interpreted in that namespace) ---
# Defaults in code: openai_compatible + text-embedding-v1 + EMBEDDING_DIM=1024 (DashScope compatible-mode; must match PG vector(N)).
Expand Down Expand Up @@ -54,6 +106,13 @@ LLM_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
# CHUNK_SIZE=1000 # max tokens per text chunk (default 1000)
# CHUNK_OVERLAP_SIZE=100 # overlap between chunks (default 100)
# EMBEDDING_TIMEOUT=120 # embedding request timeout in seconds
# Online query micro-batching (reduces one-request-per-query queueing under concurrency)
# EMBEDDING_QUERY_BATCH_ENABLED=true
# EMBEDDING_QUERY_BATCH_WINDOW_MS=10
# EMBEDDING_QUERY_BATCH_SIZE=16
# Maximum RAG context injected into the answer-generation prompt
# RAG_CONTEXT_MAX_CHARS=6000
# RAG_CONTEXT_MAX_CHARS_PER_HIT=2000
#
# --- Optional VLM summaries for images during multimodal vector indexing ---
# Default off. When true, calls VISION_MODEL per image before text embedding (extra latency + API cost).
Expand All @@ -77,7 +136,7 @@ LLM_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
# ==============================================================
# LLM_MAX_TOKENS=4096
# LLM_TEMPERATURE=0.1
# LLM_EXTRA_BODY={"enable_thinking": false} # For qwen-plus/pro: disable thinking mode to improve latency
LLM_EXTRA_BODY={"thinking":{"type":"disabled"}} # Stable non-thinking mode for evals

# ==============================================================
# Storage paths
Expand Down Expand Up @@ -152,7 +211,7 @@ MINERU_CLOUD_API_KEY=your-mineru-cloud-token-here

# ==============================================================
# RAG Service (src/rag_service/main.py — FastAPI microservice)
# RAG_SERVICE_API_KEY must match the value set in edu-platform/.env
# Shared by Next.js, rag-service, and the worker through the root .env file.
# ==============================================================
RAG_SERVICE_API_KEY=change-me-rag-service-key
# RAG_SERVICE_HOST=0.0.0.0 # default 0.0.0.0
Expand All @@ -165,10 +224,17 @@ REDIS_URL=redis://localhost:6379
DATABASE_URL=postgresql://edu:edu@localhost:5432/edu_platform?schema=public
# Vector RAG database (rag_* tables, isolated from the Prisma-managed database).
RAG_PG_DSN=postgresql://edu:edu@localhost:5432/edu_rag
# Query-side PostgreSQL pools (per RAG service process)
# RAG_PG_POOL_MIN_SIZE=1
# RAG_PG_POOL_MAX_SIZE=20
# RAG_PG_POOL_TIMEOUT=10
# DATABASE_POOL_MIN_SIZE=1
# DATABASE_POOL_MAX_SIZE=20
# DATABASE_POOL_TIMEOUT=10

# ==============================================================
# edu-rag-worker Redis Stream (src/rag_mvp/worker.py)
# Must match RAG_TASK_STREAM_NAME / RAG_TASK_STREAM_GROUP in edu-platform/.env
# Shared by Next.js and the worker through the root .env file.
# ==============================================================
# RAG_TASK_STREAM_NAME=edu:rag:tasks:stream
# RAG_TASK_STREAM_GROUP=edu-rag-workers
Expand Down
23 changes: 17 additions & 6 deletions .github/DEPLOYMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ desired, and configure these secrets:
- `DEPLOY_SSH_KEY`: private SSH key for that user.
- `DEPLOY_KNOWN_HOSTS`: pinned `known_hosts` entry for the server.
- `GHCR_USERNAME`: GitHub account used by the server to pull images.
- `GHCR_PULL_TOKEN`: fine-grained token with read access to both container images.
- `GHCR_PULL_TOKEN`: fine-grained token with read access to all project container images.

Configure these repository or environment variables:

Expand All @@ -24,8 +24,19 @@ Configure these repository or environment variables:
- `DEPLOY_PATH`: optional deployment directory, default `/opt/edu-platform`.

The server must already have Docker with Compose v2 and a production `.env` at
`DEPLOY_PATH/.env`. Never commit that file. The workflow copies only the Compose
manifest, logs in to GHCR, pulls the published images, applies database
migrations through the Next.js container startup command, restarts the stack,
and checks both the RAG and Next.js health endpoints. Production runs immutable
`sha-<commit>` image tags rather than relying on a mutable `latest` deployment.
`DEPLOY_PATH/.env`. Never commit that file. The workflow copies `compose.yml`,
logs in to GHCR, and pulls the published images. One-shot Compose services create
the RAG database, apply Prisma migrations, seed the initial administrator, and
create the MinIO bucket before the long-running services become healthy.
Production runs immutable `sha-<commit>` image tags rather than relying on a
mutable `latest` deployment.

GitHub Actions must be enabled for a fork before this workflow can publish its
GHCR images. Until the first successful image publication, deploy from the
checked-out source with `docker compose up -d --build --wait` instead of using
the pull-only production path.

Next.js and the MinIO API are bound to `127.0.0.1:3000` and `127.0.0.1:9000`.
Put TLS reverse proxies in front of both endpoints (and set
`MINIO_PUBLIC_ENDPOINT`), or enable the optional `tunnel` profile. PostgreSQL,
Redis, the MinIO console, and the RAG API remain private to the Compose network.
25 changes: 23 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,17 @@ jobs:
tags: |
ghcr.io/${{ github.repository_owner }}/edu-platform-nextjs:latest
ghcr.io/${{ github.repository_owner }}/edu-platform-nextjs:sha-${{ github.sha }}
- name: Build and push database initializer
uses: docker/build-push-action@v7
with:
context: ./edu-platform
target: init
push: true
cache-from: type=gha,scope=nextjs
cache-to: type=gha,mode=max,scope=nextjs
tags: |
ghcr.io/${{ github.repository_owner }}/edu-platform-init:latest
ghcr.io/${{ github.repository_owner }}/edu-platform-init:sha-${{ github.sha }}
- name: Build and push RAG service
uses: docker/build-push-action@v7
with:
Expand All @@ -94,6 +105,16 @@ jobs:
tags: |
ghcr.io/${{ github.repository_owner }}/edu-rag-service:latest
ghcr.io/${{ github.repository_owner }}/edu-rag-service:sha-${{ github.sha }}
- name: Build and push MinIO
uses: docker/build-push-action@v7
with:
context: ./docker/minio
push: true
cache-from: type=gha,scope=minio
cache-to: type=gha,mode=max,scope=minio
tags: |
ghcr.io/${{ github.repository_owner }}/edu-minio:latest
ghcr.io/${{ github.repository_owner }}/edu-minio:sha-${{ github.sha }}

deploy-production:
name: Deploy production
Expand All @@ -120,7 +141,7 @@ jobs:
DEPLOY_PATH: ${{ vars.DEPLOY_PATH || '/opt/edu-platform' }}
run: |
ssh -i ~/.ssh/deploy_key -p "$DEPLOY_PORT" "$DEPLOY_USER@$DEPLOY_HOST" "mkdir -p '$DEPLOY_PATH'"
scp -i ~/.ssh/deploy_key -P "$DEPLOY_PORT" edu-platform/docker-compose.yml "$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/docker-compose.yml"
scp -i ~/.ssh/deploy_key -P "$DEPLOY_PORT" compose.yml "$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/compose.yml"
- name: Pull and restart services
env:
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
Expand All @@ -133,4 +154,4 @@ jobs:
IMAGE_TAG: sha-${{ github.sha }}
run: |
printf '%s' "$GHCR_PULL_TOKEN" | ssh -i ~/.ssh/deploy_key -p "$DEPLOY_PORT" "$DEPLOY_USER@$DEPLOY_HOST" "docker login ghcr.io -u '$GHCR_USERNAME' --password-stdin"
ssh -i ~/.ssh/deploy_key -p "$DEPLOY_PORT" "$DEPLOY_USER@$DEPLOY_HOST" "cd '$DEPLOY_PATH' && test -f .env && GITHUB_OWNER='$IMAGE_OWNER' IMAGE_TAG='$IMAGE_TAG' docker compose --env-file .env pull nextjs rag-service rag-worker review-scheduler && GITHUB_OWNER='$IMAGE_OWNER' IMAGE_TAG='$IMAGE_TAG' docker compose --env-file .env up -d --no-build postgres redis minio rag-service rag-worker nextjs review-scheduler && curl --fail --retry 12 --retry-delay 5 http://127.0.0.1:8001/health && curl --fail --retry 12 --retry-delay 5 http://127.0.0.1:3000/login"
ssh -i ~/.ssh/deploy_key -p "$DEPLOY_PORT" "$DEPLOY_USER@$DEPLOY_HOST" "cd '$DEPLOY_PATH' && test -f .env && GITHUB_OWNER='$IMAGE_OWNER' IMAGE_TAG='$IMAGE_TAG' docker compose --env-file .env pull && GITHUB_OWNER='$IMAGE_OWNER' IMAGE_TAG='$IMAGE_TAG' docker compose --env-file .env up -d --no-build --wait && docker compose exec -T rag-service python -c \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8001/health', timeout=3)\" && curl --fail http://127.0.0.1:3000/login"
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ temp/
*.tmp
*.swp
*.swo
build_errors.txt

# Local databases and application state
sessions.db
Expand All @@ -69,6 +70,9 @@ rag_storage/
output/
data/
docs/
!docs/
docs/*
!docs/project-documentation.md
datasets/
models/
faster-whisper-base/
Expand All @@ -80,10 +84,18 @@ skills/pptx/scripts/
edu-platform/postgres/
scripts/truncate_except_prisma.sql
test.py
test_users.py
test_assignments.py

# Local authoring notes and agent instructions
AGENTS.md
thesis.md
system-evaluation-and-observability.md

# IDE and editor state
.idea/
.cursor/
.vscode/
*.code-workspace

# Operating system metadata
Expand Down
3 changes: 0 additions & 3 deletions .vscode/settings.json

This file was deleted.

Loading
Loading