From 6c41d7afdec77a106e625562e5bdf62bb1ef17a8 Mon Sep 17 00:00:00 2001 From: Stephan Auerhahn Date: Tue, 13 Dec 2022 11:24:22 -0800 Subject: [PATCH 01/21] Add dockerfile for bot and build/publish actions --- .github/workflows/docker/backend.yaml | 14 +++++++ .github/workflows/docker/bot.yaml | 14 +++++++ .github/workflows/docker/build.yaml | 53 +++++++++++++++++++++++++++ bot/Dockerfile | 7 ++++ 4 files changed, 88 insertions(+) create mode 100644 .github/workflows/docker/backend.yaml create mode 100644 .github/workflows/docker/bot.yaml create mode 100644 .github/workflows/docker/build.yaml create mode 100644 bot/Dockerfile diff --git a/.github/workflows/docker/backend.yaml b/.github/workflows/docker/backend.yaml new file mode 100644 index 00000000..b054e819 --- /dev/null +++ b/.github/workflows/docker/backend.yaml @@ -0,0 +1,14 @@ +name: (Backend) Publish Docker Image + +on: + push: + paths: + - 'backend/**' + +jobs: + build: + uses: ./.github/workflows/docker/build.yaml + with: + image-name: backend + folder: backend + build-args: "" \ No newline at end of file diff --git a/.github/workflows/docker/bot.yaml b/.github/workflows/docker/bot.yaml new file mode 100644 index 00000000..a0ec3a5f --- /dev/null +++ b/.github/workflows/docker/bot.yaml @@ -0,0 +1,14 @@ +name: (Bot) Publish Docker Image + +on: + push: + paths: + - 'bot/**' + +jobs: + build: + uses: ./.github/workflows/docker/build.yaml + with: + image-name: bot + folder: bot + build-args: "" \ No newline at end of file diff --git a/.github/workflows/docker/build.yaml b/.github/workflows/docker/build.yaml new file mode 100644 index 00000000..8ba7bd26 --- /dev/null +++ b/.github/workflows/docker/build.yaml @@ -0,0 +1,53 @@ +name: Build + +on: + workflow_call: + inputs: + folder: + required: true + type: string + image-name: + required: true + type: string + build-args: + required: false + type: string + +jobs: + build: + name: Build Images + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2.2.1 + - name: Login to container registry + uses: docker/login-action@v2.1.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Get base registry + run: | + echo "REGISTRY=ghcr.io/${GITHUB_REPOSITORY,,}" >> $GITHUB_ENV + - name: Set tag prefix + if: github.ref_name != 'main' + run: | + echo "TAG_PREFIX=${{ github.ref_name }}-" >> $GITHUB_ENV + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v4.1.1 + with: + images: ${{ env.REGISTRY }}/${{ inputs.image-name }} + tags: | + type=sha,prefix=${{ env.TAG_PREFIX }},format=short + - name: Build and push Docker image + uses: docker/build-push-action@v3.2.0 + with: + context: ${{ inputs.folder }} + build-args: ${{ inputs.build-args }} + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ inputs.image-name }}:buildcache + cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ inputs.image-name }}:buildcache,mode=max diff --git a/bot/Dockerfile b/bot/Dockerfile new file mode 100644 index 00000000..9cff3dc2 --- /dev/null +++ b/bot/Dockerfile @@ -0,0 +1,7 @@ +FROM python:3.10-slim-bullseye +RUN mkdir /app +ADD requirements.txt /app/requirements.txt +WORKDIR /app +RUN pip install -r requirements.txt +ADD . /app +CMD ["python", "bot.py"] \ No newline at end of file From 57cf7d2a72eb87c037315362e0f2b1b7dca5043c Mon Sep 17 00:00:00 2001 From: Stephan Auerhahn Date: Tue, 13 Dec 2022 18:02:09 -0800 Subject: [PATCH 02/21] use gha cache instead of registry --- .github/workflows/docker/build.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker/build.yaml b/.github/workflows/docker/build.yaml index 8ba7bd26..ee32d374 100644 --- a/.github/workflows/docker/build.yaml +++ b/.github/workflows/docker/build.yaml @@ -49,5 +49,5 @@ jobs: push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} - cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ inputs.image-name }}:buildcache - cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ inputs.image-name }}:buildcache,mode=max + cache-from: type=gha + cache-to: type=gha,mode=max \ No newline at end of file From df5fcc873ca0002b3827cb4894d81aae29613ab5 Mon Sep 17 00:00:00 2001 From: Stephan Auerhahn Date: Tue, 13 Dec 2022 18:35:22 -0800 Subject: [PATCH 03/21] reorg --- .github/workflows/{docker/backend.yaml => docker-backend.yaml} | 2 +- .github/workflows/{docker/bot.yaml => docker-bot.yaml} | 2 +- .github/workflows/{docker/build.yaml => docker-build.yaml} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename .github/workflows/{docker/backend.yaml => docker-backend.yaml} (78%) rename .github/workflows/{docker/bot.yaml => docker-bot.yaml} (76%) rename .github/workflows/{docker/build.yaml => docker-build.yaml} (100%) diff --git a/.github/workflows/docker/backend.yaml b/.github/workflows/docker-backend.yaml similarity index 78% rename from .github/workflows/docker/backend.yaml rename to .github/workflows/docker-backend.yaml index b054e819..12f6665c 100644 --- a/.github/workflows/docker/backend.yaml +++ b/.github/workflows/docker-backend.yaml @@ -7,7 +7,7 @@ on: jobs: build: - uses: ./.github/workflows/docker/build.yaml + uses: ./.github/workflows/docker-build.yaml with: image-name: backend folder: backend diff --git a/.github/workflows/docker/bot.yaml b/.github/workflows/docker-bot.yaml similarity index 76% rename from .github/workflows/docker/bot.yaml rename to .github/workflows/docker-bot.yaml index a0ec3a5f..4b1a0d97 100644 --- a/.github/workflows/docker/bot.yaml +++ b/.github/workflows/docker-bot.yaml @@ -7,7 +7,7 @@ on: jobs: build: - uses: ./.github/workflows/docker/build.yaml + uses: ./.github/workflows/docker-build.yaml with: image-name: bot folder: bot diff --git a/.github/workflows/docker/build.yaml b/.github/workflows/docker-build.yaml similarity index 100% rename from .github/workflows/docker/build.yaml rename to .github/workflows/docker-build.yaml From 2634f27008ab821f16ddc0633b36b6d796cb7ec2 Mon Sep 17 00:00:00 2001 From: Stephan Auerhahn Date: Tue, 13 Dec 2022 18:37:21 -0800 Subject: [PATCH 04/21] newlines --- .github/workflows/docker-backend.yaml | 2 +- .github/workflows/docker-bot.yaml | 2 +- .github/workflows/docker-build.yaml | 2 +- bot/Dockerfile | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docker-backend.yaml b/.github/workflows/docker-backend.yaml index 12f6665c..976a5602 100644 --- a/.github/workflows/docker-backend.yaml +++ b/.github/workflows/docker-backend.yaml @@ -11,4 +11,4 @@ jobs: with: image-name: backend folder: backend - build-args: "" \ No newline at end of file + build-args: "" diff --git a/.github/workflows/docker-bot.yaml b/.github/workflows/docker-bot.yaml index 4b1a0d97..2b34ebeb 100644 --- a/.github/workflows/docker-bot.yaml +++ b/.github/workflows/docker-bot.yaml @@ -11,4 +11,4 @@ jobs: with: image-name: bot folder: bot - build-args: "" \ No newline at end of file + build-args: "" diff --git a/.github/workflows/docker-build.yaml b/.github/workflows/docker-build.yaml index ee32d374..9d5c0106 100644 --- a/.github/workflows/docker-build.yaml +++ b/.github/workflows/docker-build.yaml @@ -50,4 +50,4 @@ jobs: tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha - cache-to: type=gha,mode=max \ No newline at end of file + cache-to: type=gha,mode=max diff --git a/bot/Dockerfile b/bot/Dockerfile index 9cff3dc2..ab215b5b 100644 --- a/bot/Dockerfile +++ b/bot/Dockerfile @@ -4,4 +4,4 @@ ADD requirements.txt /app/requirements.txt WORKDIR /app RUN pip install -r requirements.txt ADD . /app -CMD ["python", "bot.py"] \ No newline at end of file +CMD ["python", "bot.py"] From c3f406925285f2cfb46f4584d2f5a9000cc160f8 Mon Sep 17 00:00:00 2001 From: Stephan Auerhahn Date: Tue, 13 Dec 2022 18:39:03 -0800 Subject: [PATCH 05/21] quotes --- .github/workflows/docker-backend.yaml | 2 +- .github/workflows/docker-bot.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker-backend.yaml b/.github/workflows/docker-backend.yaml index 976a5602..026002e8 100644 --- a/.github/workflows/docker-backend.yaml +++ b/.github/workflows/docker-backend.yaml @@ -3,7 +3,7 @@ name: (Backend) Publish Docker Image on: push: paths: - - 'backend/**' + - "backend/**" jobs: build: diff --git a/.github/workflows/docker-bot.yaml b/.github/workflows/docker-bot.yaml index 2b34ebeb..d97bf757 100644 --- a/.github/workflows/docker-bot.yaml +++ b/.github/workflows/docker-bot.yaml @@ -3,7 +3,7 @@ name: (Bot) Publish Docker Image on: push: paths: - - 'bot/**' + - "bot/**" jobs: build: From c7d4fbb96f9af2694212c9fe35ee169a79ed274b Mon Sep 17 00:00:00 2001 From: Stephan Auerhahn Date: Wed, 14 Dec 2022 16:26:00 -0800 Subject: [PATCH 06/21] restrict docker builds to versioned tags --- .github/workflows/docker-backend.yaml | 2 ++ .github/workflows/docker-bot.yaml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/docker-backend.yaml b/.github/workflows/docker-backend.yaml index 026002e8..027d8e03 100644 --- a/.github/workflows/docker-backend.yaml +++ b/.github/workflows/docker-backend.yaml @@ -4,6 +4,8 @@ on: push: paths: - "backend/**" + tags: + - v* jobs: build: diff --git a/.github/workflows/docker-bot.yaml b/.github/workflows/docker-bot.yaml index d97bf757..589499a0 100644 --- a/.github/workflows/docker-bot.yaml +++ b/.github/workflows/docker-bot.yaml @@ -4,6 +4,8 @@ on: push: paths: - "bot/**" + tags: + - v* jobs: build: From c1ae50d6935a2b4cc27309ca141e92a871f18e8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20K=C3=B6pf?= Date: Sat, 17 Dec 2022 11:27:14 +0100 Subject: [PATCH 07/21] restore backend.dockerfile --- backend/backend.dockerfile | 15 +++++++++++++++ backend/scripts/docker-build-backend.sh | 7 +++++++ backend/scripts/docker-run-backend.sh | 5 +++++ backend/scripts/run-local.sh | 6 ++++++ 4 files changed, 33 insertions(+) create mode 100644 backend/backend.dockerfile create mode 100755 backend/scripts/docker-build-backend.sh create mode 100755 backend/scripts/docker-run-backend.sh diff --git a/backend/backend.dockerfile b/backend/backend.dockerfile new file mode 100644 index 00000000..53cb0381 --- /dev/null +++ b/backend/backend.dockerfile @@ -0,0 +1,15 @@ +FROM python:3.10 + +WORKDIR /backend + +COPY ./requirements.txt /backend/requirements.txt + +RUN pip install --no-cache-dir --upgrade -r /backend/requirements.txt + +COPY ./alembic.ini /backend/alembic.ini +COPY ./alembic /backend/alembic +COPY ./app /backend/app + +ENV PYTHONPATH=/backend/app + +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] diff --git a/backend/scripts/docker-build-backend.sh b/backend/scripts/docker-build-backend.sh new file mode 100755 index 00000000..ea8d1ca9 --- /dev/null +++ b/backend/scripts/docker-build-backend.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +parent_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) +pushd $parent_path + +docker build ../../backend -f ../backend.dockerfile -t ocgpt-backend + +popd diff --git a/backend/scripts/docker-run-backend.sh b/backend/scripts/docker-run-backend.sh new file mode 100755 index 00000000..eba4b225 --- /dev/null +++ b/backend/scripts/docker-run-backend.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +# This script launches a ocgpt-backend docker container stand-alone. + +docker run -it --rm -p 127.0.0.1:8000:80/tcp --env POSTGRES_SERVER=host.docker.internal:5432 --env ALLOW_ANY_API_KEY=True --add-host host.docker.internal:host-gateway ocgpt-backend diff --git a/backend/scripts/run-local.sh b/backend/scripts/run-local.sh index bf45e870..871dd85e 100755 --- a/backend/scripts/run-local.sh +++ b/backend/scripts/run-local.sh @@ -1,5 +1,11 @@ #!/usr/bin/env bash +parent_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) + +# switch to backend directory +pushd "$parent_path/../" export ALLOW_ANY_API_KEY=True uvicorn app.main:app --reload + +popd From edf123e451f7b869e0b019f8be6b19b9b3b7e476 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20K=C3=B6pf?= Date: Sat, 17 Dec 2022 12:06:24 +0100 Subject: [PATCH 08/21] add 'laion-ai/ocgp-' prefix to backend docker image names --- backend/scripts/docker-build-backend.sh | 2 +- backend/scripts/docker-run-backend.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/scripts/docker-build-backend.sh b/backend/scripts/docker-build-backend.sh index ea8d1ca9..802c5e63 100755 --- a/backend/scripts/docker-build-backend.sh +++ b/backend/scripts/docker-build-backend.sh @@ -2,6 +2,6 @@ parent_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) pushd $parent_path -docker build ../../backend -f ../backend.dockerfile -t ocgpt-backend +docker build ../../backend -f ../backend.dockerfile -t laion-ai/ocgpt-backend popd diff --git a/backend/scripts/docker-run-backend.sh b/backend/scripts/docker-run-backend.sh index eba4b225..40160864 100755 --- a/backend/scripts/docker-run-backend.sh +++ b/backend/scripts/docker-run-backend.sh @@ -2,4 +2,4 @@ # This script launches a ocgpt-backend docker container stand-alone. -docker run -it --rm -p 127.0.0.1:8000:80/tcp --env POSTGRES_SERVER=host.docker.internal:5432 --env ALLOW_ANY_API_KEY=True --add-host host.docker.internal:host-gateway ocgpt-backend +docker run -it --rm -p 127.0.0.1:8000:80/tcp --env POSTGRES_SERVER=host.docker.internal:5432 --env ALLOW_ANY_API_KEY=True --add-host host.docker.internal:host-gateway laion-ai/ocgpt-backend From b45a974287e38f25082b3529338e7d30b2866ae6 Mon Sep 17 00:00:00 2001 From: Yannic Kilcher Date: Sat, 17 Dec 2022 22:15:01 +0100 Subject: [PATCH 09/21] improved dockerfiles and developer setup --- README.md | 7 +++-- backend/Dockerfile | 11 ++++++++ backend/alembic/env.py | 4 ++- backend/app/main.py | 4 +-- backend/app/{ => ocgpt}/__init__.py | 0 backend/app/{ => ocgpt}/api/__init__.py | 0 backend/app/{ => ocgpt}/api/deps.py | 6 ++--- backend/app/{ => ocgpt}/api/v1/__init__.py | 0 backend/app/{ => ocgpt}/api/v1/api.py | 2 +- backend/app/{ => ocgpt}/api/v1/tasks.py | 8 +++--- backend/app/{ => ocgpt}/config.py | 6 +++-- backend/app/{ => ocgpt}/crud/__init__.py | 0 backend/app/{ => ocgpt}/crud/base.py | 0 backend/app/{ => ocgpt}/database.py | 2 +- backend/app/{ => ocgpt}/models/__init__.py | 0 backend/app/{ => ocgpt}/models/api_client.py | 0 backend/app/{ => ocgpt}/models/db_payload.py | 4 +-- .../{ => ocgpt}/models/payload_column_type.py | 0 backend/app/{ => ocgpt}/models/person.py | 0 .../app/{ => ocgpt}/models/person_stats.py | 0 backend/app/{ => ocgpt}/models/post.py | 0 .../app/{ => ocgpt}/models/post_reaction.py | 0 .../app/{ => ocgpt}/models/work_package.py | 0 backend/app/{ => ocgpt}/prompt_repository.py | 8 +++--- backend/app/{ => ocgpt}/schemas/__init__.py | 0 backend/app/{ => ocgpt}/schemas/protocol.py | 0 backend/app/tests/__init__.py | 0 backend/backend.dockerfile | 15 ----------- backend/scripts/backend-development/README.md | 5 ++++ .../docker-compose.yaml | 0 .../{ => backend-development}/run-local.sh | 4 +-- backend/scripts/docker-build-backend.sh | 7 ----- backend/scripts/docker-run-backend.sh | 5 ---- .../scripts/frontend-development/README.md | 5 ++++ .../frontend-development/docker-compose.yaml | 26 +++++++++++++++++++ text-frontend/__main__.py | 2 +- website/.env.example | 2 +- 37 files changed, 78 insertions(+), 55 deletions(-) create mode 100644 backend/Dockerfile rename backend/app/{ => ocgpt}/__init__.py (100%) rename backend/app/{ => ocgpt}/api/__init__.py (100%) rename backend/app/{ => ocgpt}/api/deps.py (94%) rename backend/app/{ => ocgpt}/api/v1/__init__.py (100%) rename backend/app/{ => ocgpt}/api/v1/api.py (83%) rename backend/app/{ => ocgpt}/api/v1/tasks.py (98%) rename backend/app/{ => ocgpt}/config.py (89%) rename backend/app/{ => ocgpt}/crud/__init__.py (100%) rename backend/app/{ => ocgpt}/crud/base.py (100%) rename backend/app/{ => ocgpt}/database.py (84%) rename backend/app/{ => ocgpt}/models/__init__.py (100%) rename backend/app/{ => ocgpt}/models/api_client.py (100%) rename backend/app/{ => ocgpt}/models/db_payload.py (94%) rename backend/app/{ => ocgpt}/models/payload_column_type.py (100%) rename backend/app/{ => ocgpt}/models/person.py (100%) rename backend/app/{ => ocgpt}/models/person_stats.py (100%) rename backend/app/{ => ocgpt}/models/post.py (100%) rename backend/app/{ => ocgpt}/models/post_reaction.py (100%) rename backend/app/{ => ocgpt}/models/work_package.py (100%) rename backend/app/{ => ocgpt}/prompt_repository.py (98%) rename backend/app/{ => ocgpt}/schemas/__init__.py (100%) rename backend/app/{ => ocgpt}/schemas/protocol.py (100%) delete mode 100644 backend/app/tests/__init__.py delete mode 100644 backend/backend.dockerfile create mode 100644 backend/scripts/backend-development/README.md rename backend/scripts/{ => backend-development}/docker-compose.yaml (100%) rename backend/scripts/{ => backend-development}/run-local.sh (64%) delete mode 100755 backend/scripts/docker-build-backend.sh delete mode 100755 backend/scripts/docker-run-backend.sh create mode 100644 backend/scripts/frontend-development/README.md create mode 100644 backend/scripts/frontend-development/docker-compose.yaml diff --git a/README.md b/README.md index 30aa36c0..fb542892 100644 --- a/README.md +++ b/README.md @@ -20,13 +20,12 @@ All open source projects begins with people like you. Open source is the belief Work is organized in the [project board](https://github.com/orgs/LAION-AI/projects/3). -### Python Backend - -For a local developer setup, look into the `backend` folder to pull up a local database and backend. +- To get started with development, if you want to work on the backend, have a look at `backend/scripts/backend-development/README.md`. +- If you want to work on the frontend, have a look at `backend/scripts/frontend-development/README.md`. There is also a minimal implementation of a frontend in the `text-frontend` folder. -We are using Python 3.10 +We are using Python 3.10 for the backend. Check out the [High-Level Protocol Architecture](https://www.notion.so/High-Level-Protocol-Architecture-6f1fd3551da74213b560ead369f132dc) diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 00000000..fe26db34 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,11 @@ +FROM tiangolo/uvicorn-gunicorn-fastapi:python3.10 + +COPY ./requirements.txt /app/requirements.txt + +RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt + +ENV PORT 8080 + +COPY ./alembic.ini /alembic.ini +COPY ./alembic /alembic +COPY ./app /app diff --git a/backend/alembic/env.py b/backend/alembic/env.py index ae870d84..634b79a5 100644 --- a/backend/alembic/env.py +++ b/backend/alembic/env.py @@ -3,7 +3,7 @@ from logging.config import fileConfig import sqlmodel from alembic import context -from app import models # noqa: F401 +from ocgpt import models # noqa: F401 from sqlalchemy import engine_from_config, pool # this is the Alembic Config object, which provides @@ -68,6 +68,8 @@ def run_migrations_online() -> None: context.configure(connection=connection, target_metadata=target_metadata) with context.begin_transaction(): + context.get_context()._ensure_version_table() + connection.execute("LOCK TABLE alembic_version IN ACCESS EXCLUSIVE MODE") context.run_migrations() diff --git a/backend/app/main.py b/backend/app/main.py index 27edbc88..adb38d93 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -4,9 +4,9 @@ from pathlib import Path import alembic.command import alembic.config import fastapi -from app.api.v1.api import api_router -from app.config import settings from loguru import logger +from ocgpt.api.v1.api import api_router +from ocgpt.config import settings from starlette.middleware.cors import CORSMiddleware app = fastapi.FastAPI(title=settings.PROJECT_NAME, openapi_url=f"{settings.API_V1_STR}/openapi.json") diff --git a/backend/app/__init__.py b/backend/app/ocgpt/__init__.py similarity index 100% rename from backend/app/__init__.py rename to backend/app/ocgpt/__init__.py diff --git a/backend/app/api/__init__.py b/backend/app/ocgpt/api/__init__.py similarity index 100% rename from backend/app/api/__init__.py rename to backend/app/ocgpt/api/__init__.py diff --git a/backend/app/api/deps.py b/backend/app/ocgpt/api/deps.py similarity index 94% rename from backend/app/api/deps.py rename to backend/app/ocgpt/api/deps.py index 0790cd4d..88c054ae 100644 --- a/backend/app/api/deps.py +++ b/backend/app/ocgpt/api/deps.py @@ -3,12 +3,12 @@ from secrets import token_hex from typing import Generator from uuid import UUID -from app.config import settings -from app.database import engine -from app.models import ApiClient from fastapi import HTTPException, Security from fastapi.security.api_key import APIKey, APIKeyHeader, APIKeyQuery from loguru import logger +from ocgpt.config import settings +from ocgpt.database import engine +from ocgpt.models import ApiClient from sqlmodel import Session from starlette.status import HTTP_403_FORBIDDEN diff --git a/backend/app/api/v1/__init__.py b/backend/app/ocgpt/api/v1/__init__.py similarity index 100% rename from backend/app/api/v1/__init__.py rename to backend/app/ocgpt/api/v1/__init__.py diff --git a/backend/app/api/v1/api.py b/backend/app/ocgpt/api/v1/api.py similarity index 83% rename from backend/app/api/v1/api.py rename to backend/app/ocgpt/api/v1/api.py index afcb9677..1e304656 100644 --- a/backend/app/api/v1/api.py +++ b/backend/app/ocgpt/api/v1/api.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from app.api.v1 import tasks from fastapi import APIRouter +from ocgpt.api.v1 import tasks api_router = APIRouter() api_router.include_router(tasks.router, prefix="/tasks", tags=["tasks"]) diff --git a/backend/app/api/v1/tasks.py b/backend/app/ocgpt/api/v1/tasks.py similarity index 98% rename from backend/app/api/v1/tasks.py rename to backend/app/ocgpt/api/v1/tasks.py index 086f3a4a..ef12276a 100644 --- a/backend/app/api/v1/tasks.py +++ b/backend/app/ocgpt/api/v1/tasks.py @@ -3,13 +3,13 @@ import random from typing import Any from uuid import UUID -from app.api import deps -from app.models.db_payload import TaskPayload -from app.prompt_repository import PromptRepository -from app.schemas import protocol as protocol_schema from fastapi import APIRouter, Depends, HTTPException from fastapi.security.api_key import APIKey from loguru import logger +from ocgpt.api import deps +from ocgpt.models.db_payload import TaskPayload +from ocgpt.prompt_repository import PromptRepository +from ocgpt.schemas import protocol as protocol_schema from sqlmodel import Session from starlette.status import HTTP_400_BAD_REQUEST diff --git a/backend/app/config.py b/backend/app/ocgpt/config.py similarity index 89% rename from backend/app/config.py rename to backend/app/ocgpt/config.py index 0780caf9..71bade6c 100644 --- a/backend/app/config.py +++ b/backend/app/ocgpt/config.py @@ -8,7 +8,8 @@ class Settings(BaseSettings): PROJECT_NAME: str = "open-chatGPT backend" API_V1_STR: str = "/api/v1" - POSTGRES_SERVER: str = "localhost:5432" + POSTGRES_HOST: str = "localhost" + POSTGRES_PORT: str = "5432" POSTGRES_USER: str = "postgres" POSTGRES_PASSWORD: str = "postgres" POSTGRES_DB: str = "postgres" @@ -24,7 +25,8 @@ class Settings(BaseSettings): scheme="postgresql", user=values.get("POSTGRES_USER"), password=values.get("POSTGRES_PASSWORD"), - host=values.get("POSTGRES_SERVER"), + host=values.get("POSTGRES_HOST"), + port=values.get("POSTGRES_PORT"), path=f"/{values.get('POSTGRES_DB') or ''}", ) diff --git a/backend/app/crud/__init__.py b/backend/app/ocgpt/crud/__init__.py similarity index 100% rename from backend/app/crud/__init__.py rename to backend/app/ocgpt/crud/__init__.py diff --git a/backend/app/crud/base.py b/backend/app/ocgpt/crud/base.py similarity index 100% rename from backend/app/crud/base.py rename to backend/app/ocgpt/crud/base.py diff --git a/backend/app/database.py b/backend/app/ocgpt/database.py similarity index 84% rename from backend/app/database.py rename to backend/app/ocgpt/database.py index 89b636cd..96ada5d6 100644 --- a/backend/app/database.py +++ b/backend/app/ocgpt/database.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from app.config import settings +from ocgpt.config import settings from sqlmodel import create_engine if settings.DATABASE_URI is None: diff --git a/backend/app/models/__init__.py b/backend/app/ocgpt/models/__init__.py similarity index 100% rename from backend/app/models/__init__.py rename to backend/app/ocgpt/models/__init__.py diff --git a/backend/app/models/api_client.py b/backend/app/ocgpt/models/api_client.py similarity index 100% rename from backend/app/models/api_client.py rename to backend/app/ocgpt/models/api_client.py diff --git a/backend/app/models/db_payload.py b/backend/app/ocgpt/models/db_payload.py similarity index 94% rename from backend/app/models/db_payload.py rename to backend/app/ocgpt/models/db_payload.py index 52eadb67..730178e3 100644 --- a/backend/app/models/db_payload.py +++ b/backend/app/ocgpt/models/db_payload.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- from typing import Literal -from app.models.payload_column_type import payload_type -from app.schemas import protocol as protocol_schema +from ocgpt.models.payload_column_type import payload_type +from ocgpt.schemas import protocol as protocol_schema from pydantic import BaseModel diff --git a/backend/app/models/payload_column_type.py b/backend/app/ocgpt/models/payload_column_type.py similarity index 100% rename from backend/app/models/payload_column_type.py rename to backend/app/ocgpt/models/payload_column_type.py diff --git a/backend/app/models/person.py b/backend/app/ocgpt/models/person.py similarity index 100% rename from backend/app/models/person.py rename to backend/app/ocgpt/models/person.py diff --git a/backend/app/models/person_stats.py b/backend/app/ocgpt/models/person_stats.py similarity index 100% rename from backend/app/models/person_stats.py rename to backend/app/ocgpt/models/person_stats.py diff --git a/backend/app/models/post.py b/backend/app/ocgpt/models/post.py similarity index 100% rename from backend/app/models/post.py rename to backend/app/ocgpt/models/post.py diff --git a/backend/app/models/post_reaction.py b/backend/app/ocgpt/models/post_reaction.py similarity index 100% rename from backend/app/models/post_reaction.py rename to backend/app/ocgpt/models/post_reaction.py diff --git a/backend/app/models/work_package.py b/backend/app/ocgpt/models/work_package.py similarity index 100% rename from backend/app/models/work_package.py rename to backend/app/ocgpt/models/work_package.py diff --git a/backend/app/prompt_repository.py b/backend/app/ocgpt/prompt_repository.py similarity index 98% rename from backend/app/prompt_repository.py rename to backend/app/ocgpt/prompt_repository.py index 43706d00..7b39d0b8 100644 --- a/backend/app/prompt_repository.py +++ b/backend/app/ocgpt/prompt_repository.py @@ -3,11 +3,11 @@ from datetime import datetime from typing import Optional from uuid import UUID, uuid4 -import app.models.db_payload as db_payload -from app.models import ApiClient, Person, Post, PostReaction, WorkPackage -from app.models.payload_column_type import PayloadContainer -from app.schemas import protocol as protocol_schema +import ocgpt.models.db_payload as db_payload from loguru import logger +from ocgpt.models import ApiClient, Person, Post, PostReaction, WorkPackage +from ocgpt.models.payload_column_type import PayloadContainer +from ocgpt.schemas import protocol as protocol_schema from sqlmodel import Session diff --git a/backend/app/schemas/__init__.py b/backend/app/ocgpt/schemas/__init__.py similarity index 100% rename from backend/app/schemas/__init__.py rename to backend/app/ocgpt/schemas/__init__.py diff --git a/backend/app/schemas/protocol.py b/backend/app/ocgpt/schemas/protocol.py similarity index 100% rename from backend/app/schemas/protocol.py rename to backend/app/ocgpt/schemas/protocol.py diff --git a/backend/app/tests/__init__.py b/backend/app/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/backend.dockerfile b/backend/backend.dockerfile deleted file mode 100644 index 53cb0381..00000000 --- a/backend/backend.dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM python:3.10 - -WORKDIR /backend - -COPY ./requirements.txt /backend/requirements.txt - -RUN pip install --no-cache-dir --upgrade -r /backend/requirements.txt - -COPY ./alembic.ini /backend/alembic.ini -COPY ./alembic /backend/alembic -COPY ./app /backend/app - -ENV PYTHONPATH=/backend/app - -CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] diff --git a/backend/scripts/backend-development/README.md b/backend/scripts/backend-development/README.md new file mode 100644 index 00000000..36a1b964 --- /dev/null +++ b/backend/scripts/backend-development/README.md @@ -0,0 +1,5 @@ +# Backend Development Setup + +Run `docker compose up` to start a database. The default settings are already configured to connect to the database at `localhost:5432`. + +Then, run the backend using the `run-local.sh` script. This will start the backend server at `http://localhost:8080`. diff --git a/backend/scripts/docker-compose.yaml b/backend/scripts/backend-development/docker-compose.yaml similarity index 100% rename from backend/scripts/docker-compose.yaml rename to backend/scripts/backend-development/docker-compose.yaml diff --git a/backend/scripts/run-local.sh b/backend/scripts/backend-development/run-local.sh similarity index 64% rename from backend/scripts/run-local.sh rename to backend/scripts/backend-development/run-local.sh index 871dd85e..187674f3 100755 --- a/backend/scripts/run-local.sh +++ b/backend/scripts/backend-development/run-local.sh @@ -2,10 +2,10 @@ parent_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) # switch to backend directory -pushd "$parent_path/../" +pushd "$parent_path/../../app" export ALLOW_ANY_API_KEY=True -uvicorn app.main:app --reload +uvicorn main:app --reload --port 8080 --host 0.0.0.0 popd diff --git a/backend/scripts/docker-build-backend.sh b/backend/scripts/docker-build-backend.sh deleted file mode 100755 index 802c5e63..00000000 --- a/backend/scripts/docker-build-backend.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash -parent_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) -pushd $parent_path - -docker build ../../backend -f ../backend.dockerfile -t laion-ai/ocgpt-backend - -popd diff --git a/backend/scripts/docker-run-backend.sh b/backend/scripts/docker-run-backend.sh deleted file mode 100755 index 40160864..00000000 --- a/backend/scripts/docker-run-backend.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -# This script launches a ocgpt-backend docker container stand-alone. - -docker run -it --rm -p 127.0.0.1:8000:80/tcp --env POSTGRES_SERVER=host.docker.internal:5432 --env ALLOW_ANY_API_KEY=True --add-host host.docker.internal:host-gateway laion-ai/ocgpt-backend diff --git a/backend/scripts/frontend-development/README.md b/backend/scripts/frontend-development/README.md new file mode 100644 index 00000000..e13e37c0 --- /dev/null +++ b/backend/scripts/frontend-development/README.md @@ -0,0 +1,5 @@ +# Frontend Development Setup + +Run `docker compose up --build` to start a database and the backend server. + +Then, point your frontend at `http://localhost:8080` to start developing. During development, any API key will be accepted. diff --git a/backend/scripts/frontend-development/docker-compose.yaml b/backend/scripts/frontend-development/docker-compose.yaml new file mode 100644 index 00000000..42f36fb4 --- /dev/null +++ b/backend/scripts/frontend-development/docker-compose.yaml @@ -0,0 +1,26 @@ +version: "3.7" + +services: + db: + extends: + file: ../backend-development/docker-compose.yaml + service: db + healthcheck: + test: ["CMD", "pg_isready", "-U", "postgres"] + interval: 2s + timeout: 2s + retries: 10 + adminer: + extends: + file: ../backend-development/docker-compose.yaml + service: adminer + backend: + build: ../../. + environment: + - POSTGRES_HOST=db + - ALLOW_ANY_API_KEY=True + depends_on: + db: + condition: service_healthy + ports: + - "8080:8080" diff --git a/text-frontend/__main__.py b/text-frontend/__main__.py index 16fc4812..ee34799d 100644 --- a/text-frontend/__main__.py +++ b/text-frontend/__main__.py @@ -25,7 +25,7 @@ def _render_message(message: dict) -> str: @app.command() -def main(backend_url: str = "http://127.0.0.1:8000", api_key: str = "DUMMY_KEY"): +def main(backend_url: str = "http://127.0.0.1:8080", api_key: str = "DUMMY_KEY"): """Simple REPL frontend.""" def _post(path: str, json: dict) -> dict: diff --git a/website/.env.example b/website/.env.example index f0db36e7..1d7721c8 100644 --- a/website/.env.example +++ b/website/.env.example @@ -1,4 +1,4 @@ -FASTAPI_URL=http://xamla.com:8000 +FASTAPI_URL=http://xamla.com:8080 FASTAPI_KEY=magic_key DISCORD_CLIENT_ID=your_discord_bot_client_id From 0d9034e4bb27ad7ac5e3cd09dd0c2a4fbd7a2cb4 Mon Sep 17 00:00:00 2001 From: Yannic Kilcher Date: Sat, 17 Dec 2022 22:31:21 +0100 Subject: [PATCH 10/21] changed scripts location --- README.md | 4 ++-- backend/README.md | 6 +----- {backend/scripts => scripts}/backend-development/README.md | 1 + .../backend-development/docker-compose.yaml | 0 .../scripts => scripts}/backend-development/run-local.sh | 2 +- {backend/scripts => scripts}/frontend-development/README.md | 0 .../frontend-development/docker-compose.yaml | 2 +- 7 files changed, 6 insertions(+), 9 deletions(-) rename {backend/scripts => scripts}/backend-development/README.md (67%) rename {backend/scripts => scripts}/backend-development/docker-compose.yaml (100%) rename {backend/scripts => scripts}/backend-development/run-local.sh (83%) rename {backend/scripts => scripts}/frontend-development/README.md (100%) rename {backend/scripts => scripts}/frontend-development/docker-compose.yaml (95%) diff --git a/README.md b/README.md index fb542892..25104969 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,8 @@ All open source projects begins with people like you. Open source is the belief Work is organized in the [project board](https://github.com/orgs/LAION-AI/projects/3). -- To get started with development, if you want to work on the backend, have a look at `backend/scripts/backend-development/README.md`. -- If you want to work on the frontend, have a look at `backend/scripts/frontend-development/README.md`. +- To get started with development, if you want to work on the backend, have a look at `scripts/backend-development/README.md`. +- If you want to work on the frontend, have a look at `scripts/frontend-development/README.md`. There is also a minimal implementation of a frontend in the `text-frontend` folder. diff --git a/backend/README.md b/backend/README.md index 25bd6d46..a559b15b 100644 --- a/backend/README.md +++ b/backend/README.md @@ -14,8 +14,4 @@ BACKEND_CORS_ORIGINS=["http://localhost", "http://localhost:4200", "http://local ## Running the REST Server locally for development -First, install the requirements in `requirements.txt`. -Then, run two terminals (note the working directory for each): - -- Terminal 1, to go `backend/scripts` and run `docker-compose up`. This will start postgres. -- Terminal 2, to go `backend` and run `scripts/run-local.sh`. This will start the REST server. +Have a look into the main `README.md` file for more information on how to set up the backend for development. diff --git a/backend/scripts/backend-development/README.md b/scripts/backend-development/README.md similarity index 67% rename from backend/scripts/backend-development/README.md rename to scripts/backend-development/README.md index 36a1b964..80706c79 100644 --- a/backend/scripts/backend-development/README.md +++ b/scripts/backend-development/README.md @@ -2,4 +2,5 @@ Run `docker compose up` to start a database. The default settings are already configured to connect to the database at `localhost:5432`. +Make sure you have all requirements installed. You can do this by running `pip install -r requirements.txt` inside the `backend` folder. Then, run the backend using the `run-local.sh` script. This will start the backend server at `http://localhost:8080`. diff --git a/backend/scripts/backend-development/docker-compose.yaml b/scripts/backend-development/docker-compose.yaml similarity index 100% rename from backend/scripts/backend-development/docker-compose.yaml rename to scripts/backend-development/docker-compose.yaml diff --git a/backend/scripts/backend-development/run-local.sh b/scripts/backend-development/run-local.sh similarity index 83% rename from backend/scripts/backend-development/run-local.sh rename to scripts/backend-development/run-local.sh index 187674f3..064612c3 100755 --- a/backend/scripts/backend-development/run-local.sh +++ b/scripts/backend-development/run-local.sh @@ -2,7 +2,7 @@ parent_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) # switch to backend directory -pushd "$parent_path/../../app" +pushd "$parent_path/../../backend/app" export ALLOW_ANY_API_KEY=True diff --git a/backend/scripts/frontend-development/README.md b/scripts/frontend-development/README.md similarity index 100% rename from backend/scripts/frontend-development/README.md rename to scripts/frontend-development/README.md diff --git a/backend/scripts/frontend-development/docker-compose.yaml b/scripts/frontend-development/docker-compose.yaml similarity index 95% rename from backend/scripts/frontend-development/docker-compose.yaml rename to scripts/frontend-development/docker-compose.yaml index 42f36fb4..bcde8923 100644 --- a/backend/scripts/frontend-development/docker-compose.yaml +++ b/scripts/frontend-development/docker-compose.yaml @@ -15,7 +15,7 @@ services: file: ../backend-development/docker-compose.yaml service: adminer backend: - build: ../../. + build: ../../backend/. environment: - POSTGRES_HOST=db - ALLOW_ANY_API_KEY=True From a3ccaa4dc55874a39adc33101401aad2cb017a60 Mon Sep 17 00:00:00 2001 From: Yannic Kilcher Date: Sat, 17 Dec 2022 22:35:36 +0100 Subject: [PATCH 11/21] added tag to docker compose --- scripts/frontend-development/docker-compose.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/frontend-development/docker-compose.yaml b/scripts/frontend-development/docker-compose.yaml index bcde8923..ce3e7d9b 100644 --- a/scripts/frontend-development/docker-compose.yaml +++ b/scripts/frontend-development/docker-compose.yaml @@ -16,6 +16,7 @@ services: service: adminer backend: build: ../../backend/. + image: laion-ai/ocgpt-backend environment: - POSTGRES_HOST=db - ALLOW_ANY_API_KEY=True From fbf4801296b0ca396e1ffd31cadd7041eef2b225 Mon Sep 17 00:00:00 2001 From: Yannic Kilcher Date: Sat, 17 Dec 2022 22:36:42 +0100 Subject: [PATCH 12/21] changed image name --- scripts/frontend-development/docker-compose.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/frontend-development/docker-compose.yaml b/scripts/frontend-development/docker-compose.yaml index ce3e7d9b..8597726d 100644 --- a/scripts/frontend-development/docker-compose.yaml +++ b/scripts/frontend-development/docker-compose.yaml @@ -16,7 +16,7 @@ services: service: adminer backend: build: ../../backend/. - image: laion-ai/ocgpt-backend + image: ocgpt-backend environment: - POSTGRES_HOST=db - ALLOW_ANY_API_KEY=True From 819ecaa8b3e671755fcdd102e8ee9eefa274a315 Mon Sep 17 00:00:00 2001 From: Yannic Kilcher Date: Sat, 17 Dec 2022 22:49:55 +0100 Subject: [PATCH 13/21] updated release workflow --- .github/workflows/docker-backend.yaml | 16 ---------------- .github/workflows/docker-bot.yaml | 16 ---------------- .github/workflows/release.yaml | 13 +++++++++++++ 3 files changed, 13 insertions(+), 32 deletions(-) delete mode 100644 .github/workflows/docker-backend.yaml delete mode 100644 .github/workflows/docker-bot.yaml create mode 100644 .github/workflows/release.yaml diff --git a/.github/workflows/docker-backend.yaml b/.github/workflows/docker-backend.yaml deleted file mode 100644 index 027d8e03..00000000 --- a/.github/workflows/docker-backend.yaml +++ /dev/null @@ -1,16 +0,0 @@ -name: (Backend) Publish Docker Image - -on: - push: - paths: - - "backend/**" - tags: - - v* - -jobs: - build: - uses: ./.github/workflows/docker-build.yaml - with: - image-name: backend - folder: backend - build-args: "" diff --git a/.github/workflows/docker-bot.yaml b/.github/workflows/docker-bot.yaml deleted file mode 100644 index 589499a0..00000000 --- a/.github/workflows/docker-bot.yaml +++ /dev/null @@ -1,16 +0,0 @@ -name: (Bot) Publish Docker Image - -on: - push: - paths: - - "bot/**" - tags: - - v* - -jobs: - build: - uses: ./.github/workflows/docker-build.yaml - with: - image-name: bot - folder: bot - build-args: "" diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 00000000..eb9bea65 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,13 @@ +name: Release + +on: + release: + types: [released] + +jobs: + build-backend: + uses: ./.github/workflows/docker-build.yaml + with: + image-name: ocgpt-backend + folder: backend + build-args: "" From 2a56438a5505d2191b798a5c63c2c7d939e45bf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20K=C3=B6pf?= Date: Sat, 17 Dec 2022 23:01:21 +0100 Subject: [PATCH 14/21] add auth_method column to person table --- .../6368515778c5_add_auth_method_to_person.py | 30 +++++++++++++++++++ backend/app/ocgpt/models/person.py | 1 + backend/app/ocgpt/prompt_repository.py | 8 ++++- 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 backend/alembic/versions/6368515778c5_add_auth_method_to_person.py diff --git a/backend/alembic/versions/6368515778c5_add_auth_method_to_person.py b/backend/alembic/versions/6368515778c5_add_auth_method_to_person.py new file mode 100644 index 00000000..d93afeba --- /dev/null +++ b/backend/alembic/versions/6368515778c5_add_auth_method_to_person.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +"""add auth_method to person + +Revision ID: 6368515778c5 +Revises: cd7de470586e +Create Date: 2022-12-17 17:57:33.022549 + +""" +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "6368515778c5" +down_revision = "cd7de470586e" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.add_column("person", sa.Column("auth_method", sa.String(length=128), nullable=True)) + op.execute("UPDATE person SET auth_method = 'local'") + op.alter_column("person", "auth_method", nullable=False) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("person", "auth_method") + # ### end Alembic commands ### diff --git a/backend/app/ocgpt/models/person.py b/backend/app/ocgpt/models/person.py index e66db5e3..57f134a4 100644 --- a/backend/app/ocgpt/models/person.py +++ b/backend/app/ocgpt/models/person.py @@ -18,6 +18,7 @@ class Person(SQLModel, table=True): ), ) username: str = Field(nullable=False, max_length=128) + auth_method: str = Field(nullable=False, max_length=128, default="local") display_name: str = Field(nullable=False, max_length=256) created_date: Optional[datetime] = Field( sa_column=sa.Column(sa.DateTime(), nullable=False, server_default=sa.func.current_timestamp()) diff --git a/backend/app/ocgpt/prompt_repository.py b/backend/app/ocgpt/prompt_repository.py index 7b39d0b8..8c621df1 100644 --- a/backend/app/ocgpt/prompt_repository.py +++ b/backend/app/ocgpt/prompt_repository.py @@ -22,7 +22,13 @@ class PromptRepository: if not user: return None person: Person = ( - self.db.query(Person).filter(Person.api_client_id == self.api_client.id, Person.username == user.id).first() + self.db.query(Person) + .filter( + Person.api_client_id == self.api_client.id, + Person.username == user.id, + Person.auth_method == user.auth_method, + ) + .first() ) if person is None: # user is unknown, create new record From e397f32bfaaad42d78009442a43c47d750623431 Mon Sep 17 00:00:00 2001 From: Yannic Kilcher Date: Sat, 17 Dec 2022 23:23:12 +0100 Subject: [PATCH 15/21] added ref tag --- .github/workflows/docker-build.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/docker-build.yaml b/.github/workflows/docker-build.yaml index 9d5c0106..2a6f3bbf 100644 --- a/.github/workflows/docker-build.yaml +++ b/.github/workflows/docker-build.yaml @@ -41,6 +41,7 @@ jobs: images: ${{ env.REGISTRY }}/${{ inputs.image-name }} tags: | type=sha,prefix=${{ env.TAG_PREFIX }},format=short + type=ref,event=tag - name: Build and push Docker image uses: docker/build-push-action@v3.2.0 with: From afb5ec36696b8b8fc3b50181ee6dbfb38978efff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20K=C3=B6pf?= Date: Sat, 17 Dec 2022 23:31:35 +0100 Subject: [PATCH 16/21] change alembic file_template --- backend/alembic.ini | 2 +- ...vision.py => 2022_12_15_0000-23e5fea252dd_first_revision.py} | 0 ...cture.py => 2022_12_16_0000-cd7de470586e_v1_db_structure.py} | 0 ...> 2022_12_17_2230-6368515778c5_add_auth_method_to_person.py} | 0 4 files changed, 1 insertion(+), 1 deletion(-) rename backend/alembic/versions/{23e5fea252dd_first_revision.py => 2022_12_15_0000-23e5fea252dd_first_revision.py} (100%) rename backend/alembic/versions/{cd7de470586e_v1_db_structure.py => 2022_12_16_0000-cd7de470586e_v1_db_structure.py} (100%) rename backend/alembic/versions/{6368515778c5_add_auth_method_to_person.py => 2022_12_17_2230-6368515778c5_add_auth_method_to_person.py} (100%) diff --git a/backend/alembic.ini b/backend/alembic.ini index 36a8c3ae..44829313 100644 --- a/backend/alembic.ini +++ b/backend/alembic.ini @@ -8,7 +8,7 @@ script_location = %(here)s/alembic # Uncomment the line below if you want the files to be prepended with date and time # see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file # for all available tokens -# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s +file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s # sys.path path, will be prepended to sys.path if present. # defaults to the current working directory. diff --git a/backend/alembic/versions/23e5fea252dd_first_revision.py b/backend/alembic/versions/2022_12_15_0000-23e5fea252dd_first_revision.py similarity index 100% rename from backend/alembic/versions/23e5fea252dd_first_revision.py rename to backend/alembic/versions/2022_12_15_0000-23e5fea252dd_first_revision.py diff --git a/backend/alembic/versions/cd7de470586e_v1_db_structure.py b/backend/alembic/versions/2022_12_16_0000-cd7de470586e_v1_db_structure.py similarity index 100% rename from backend/alembic/versions/cd7de470586e_v1_db_structure.py rename to backend/alembic/versions/2022_12_16_0000-cd7de470586e_v1_db_structure.py diff --git a/backend/alembic/versions/6368515778c5_add_auth_method_to_person.py b/backend/alembic/versions/2022_12_17_2230-6368515778c5_add_auth_method_to_person.py similarity index 100% rename from backend/alembic/versions/6368515778c5_add_auth_method_to_person.py rename to backend/alembic/versions/2022_12_17_2230-6368515778c5_add_auth_method_to_person.py From 1acdc669730297e251918d7175f7192997d45159 Mon Sep 17 00:00:00 2001 From: Yannic Kilcher Date: Sat, 17 Dec 2022 23:40:40 +0100 Subject: [PATCH 17/21] moved alembic --- backend/Dockerfile | 2 -- backend/{ => app}/alembic.ini | 0 backend/{ => app}/alembic/README | 0 backend/{ => app}/alembic/env.py | 0 backend/{ => app}/alembic/script.py.mako | 0 .../versions/2022_12_15_0000-23e5fea252dd_first_revision.py | 0 .../versions/2022_12_16_0000-cd7de470586e_v1_db_structure.py | 0 .../2022_12_17_2230-6368515778c5_add_auth_method_to_person.py | 0 backend/app/main.py | 2 +- 9 files changed, 1 insertion(+), 3 deletions(-) rename backend/{ => app}/alembic.ini (100%) rename backend/{ => app}/alembic/README (100%) rename backend/{ => app}/alembic/env.py (100%) rename backend/{ => app}/alembic/script.py.mako (100%) rename backend/{ => app}/alembic/versions/2022_12_15_0000-23e5fea252dd_first_revision.py (100%) rename backend/{ => app}/alembic/versions/2022_12_16_0000-cd7de470586e_v1_db_structure.py (100%) rename backend/{ => app}/alembic/versions/2022_12_17_2230-6368515778c5_add_auth_method_to_person.py (100%) diff --git a/backend/Dockerfile b/backend/Dockerfile index fe26db34..8074ef3a 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -6,6 +6,4 @@ RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt ENV PORT 8080 -COPY ./alembic.ini /alembic.ini -COPY ./alembic /alembic COPY ./app /app diff --git a/backend/alembic.ini b/backend/app/alembic.ini similarity index 100% rename from backend/alembic.ini rename to backend/app/alembic.ini diff --git a/backend/alembic/README b/backend/app/alembic/README similarity index 100% rename from backend/alembic/README rename to backend/app/alembic/README diff --git a/backend/alembic/env.py b/backend/app/alembic/env.py similarity index 100% rename from backend/alembic/env.py rename to backend/app/alembic/env.py diff --git a/backend/alembic/script.py.mako b/backend/app/alembic/script.py.mako similarity index 100% rename from backend/alembic/script.py.mako rename to backend/app/alembic/script.py.mako diff --git a/backend/alembic/versions/2022_12_15_0000-23e5fea252dd_first_revision.py b/backend/app/alembic/versions/2022_12_15_0000-23e5fea252dd_first_revision.py similarity index 100% rename from backend/alembic/versions/2022_12_15_0000-23e5fea252dd_first_revision.py rename to backend/app/alembic/versions/2022_12_15_0000-23e5fea252dd_first_revision.py diff --git a/backend/alembic/versions/2022_12_16_0000-cd7de470586e_v1_db_structure.py b/backend/app/alembic/versions/2022_12_16_0000-cd7de470586e_v1_db_structure.py similarity index 100% rename from backend/alembic/versions/2022_12_16_0000-cd7de470586e_v1_db_structure.py rename to backend/app/alembic/versions/2022_12_16_0000-cd7de470586e_v1_db_structure.py diff --git a/backend/alembic/versions/2022_12_17_2230-6368515778c5_add_auth_method_to_person.py b/backend/app/alembic/versions/2022_12_17_2230-6368515778c5_add_auth_method_to_person.py similarity index 100% rename from backend/alembic/versions/2022_12_17_2230-6368515778c5_add_auth_method_to_person.py rename to backend/app/alembic/versions/2022_12_17_2230-6368515778c5_add_auth_method_to_person.py diff --git a/backend/app/main.py b/backend/app/main.py index adb38d93..02f0198a 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -27,7 +27,7 @@ if settings.UPDATE_ALEMBIC: def alembic_upgrade(): logger.info("Attempting to upgrade alembic on startup") try: - alembic_ini_path = Path(__file__).parent.parent / "alembic.ini" + alembic_ini_path = Path(__file__).parent / "alembic.ini" alembic_cfg = alembic.config.Config(str(alembic_ini_path)) alembic_cfg.set_main_option("sqlalchemy.url", settings.DATABASE_URI) alembic.command.upgrade(alembic_cfg, "head") From a6c957ccfdb500aa48d68beaeb6419b1f81579cb Mon Sep 17 00:00:00 2001 From: Yannic Kilcher Date: Sat, 17 Dec 2022 23:55:55 +0100 Subject: [PATCH 18/21] renamed to open assistant --- backend/app/alembic/env.py | 2 +- backend/app/main.py | 4 +- backend/app/oasst/api/deps.py | 57 +++++ backend/app/oasst/api/v1/api.py | 6 + backend/app/oasst/api/v1/tasks.py | 259 ++++++++++++++++++++ backend/app/oasst/database.py | 8 + backend/app/oasst/models/db_payload.py | 94 ++++++++ backend/app/oasst/prompt_repository.py | 311 +++++++++++++++++++++++++ 8 files changed, 738 insertions(+), 3 deletions(-) create mode 100644 backend/app/oasst/api/deps.py create mode 100644 backend/app/oasst/api/v1/api.py create mode 100644 backend/app/oasst/api/v1/tasks.py create mode 100644 backend/app/oasst/database.py create mode 100644 backend/app/oasst/models/db_payload.py create mode 100644 backend/app/oasst/prompt_repository.py diff --git a/backend/app/alembic/env.py b/backend/app/alembic/env.py index 634b79a5..6d2ec0c3 100644 --- a/backend/app/alembic/env.py +++ b/backend/app/alembic/env.py @@ -3,7 +3,7 @@ from logging.config import fileConfig import sqlmodel from alembic import context -from ocgpt import models # noqa: F401 +from oasst import models # noqa: F401 from sqlalchemy import engine_from_config, pool # this is the Alembic Config object, which provides diff --git a/backend/app/main.py b/backend/app/main.py index 02f0198a..f78f608c 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -5,8 +5,8 @@ import alembic.command import alembic.config import fastapi from loguru import logger -from ocgpt.api.v1.api import api_router -from ocgpt.config import settings +from oasst.api.v1.api import api_router +from oasst.config import settings from starlette.middleware.cors import CORSMiddleware app = fastapi.FastAPI(title=settings.PROJECT_NAME, openapi_url=f"{settings.API_V1_STR}/openapi.json") diff --git a/backend/app/oasst/api/deps.py b/backend/app/oasst/api/deps.py new file mode 100644 index 00000000..bfa54931 --- /dev/null +++ b/backend/app/oasst/api/deps.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +from secrets import token_hex +from typing import Generator +from uuid import UUID + +from fastapi import HTTPException, Security +from fastapi.security.api_key import APIKey, APIKeyHeader, APIKeyQuery +from loguru import logger +from oasst.config import settings +from oasst.database import engine +from oasst.models import ApiClient +from sqlmodel import Session +from starlette.status import HTTP_403_FORBIDDEN + + +def get_db() -> Generator: + with Session(engine) as db: + yield db + + +api_key_query = APIKeyQuery(name="api_key", auto_error=False) +api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False) + + +async def get_api_key( + api_key_query: str = Security(api_key_query), + api_key_header: str = Security(api_key_header), +): + if api_key_query: + return api_key_query + else: + return api_key_header + + +def api_auth( + api_key: APIKey, + db: Session, +) -> ApiClient: + + if api_key is not None: + if settings.ALLOW_ANY_API_KEY: + # make sure that a dummy api key exits in db (foreign key references) + ANY_API_KEY_ID = UUID("00000000-1111-2222-3333-444444444444") + api_client: ApiClient = db.query(ApiClient).filter(ApiClient.id == ANY_API_KEY_ID).first() + if api_client is None: + token = token_hex(32) + logger.info(f"ANY_API_KEY missing, inserting api_key: {token}") + api_client = ApiClient(id=ANY_API_KEY_ID, api_key=token, description="ANY_API_KEY, random token") + db.add(api_client) + db.commit() + return api_client + + api_client = db.query(ApiClient).filter(ApiClient.api_key == api_key).first() + if api_client is not None and api_client.enabled: + return api_client + + raise HTTPException(status_code=HTTP_403_FORBIDDEN, detail="Could not validate credentials") diff --git a/backend/app/oasst/api/v1/api.py b/backend/app/oasst/api/v1/api.py new file mode 100644 index 00000000..3d568cb9 --- /dev/null +++ b/backend/app/oasst/api/v1/api.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +from fastapi import APIRouter +from oasst.api.v1 import tasks + +api_router = APIRouter() +api_router.include_router(tasks.router, prefix="/tasks", tags=["tasks"]) diff --git a/backend/app/oasst/api/v1/tasks.py b/backend/app/oasst/api/v1/tasks.py new file mode 100644 index 00000000..41f01f3c --- /dev/null +++ b/backend/app/oasst/api/v1/tasks.py @@ -0,0 +1,259 @@ +# -*- coding: utf-8 -*- +import random +from typing import Any +from uuid import UUID + +from fastapi import APIRouter, Depends, HTTPException +from fastapi.security.api_key import APIKey +from loguru import logger +from oasst.api import deps +from oasst.models.db_payload import TaskPayload +from oasst.prompt_repository import PromptRepository +from oasst.schemas import protocol as protocol_schema +from sqlmodel import Session +from starlette.status import HTTP_400_BAD_REQUEST + +router = APIRouter() + + +def generate_task(request: protocol_schema.TaskRequest) -> protocol_schema.Task: + match (request.type): + case protocol_schema.TaskRequestType.random: + logger.info("Frontend requested a random task.") + while request.type == protocol_schema.TaskRequestType.random: + request.type = random.choice(list(protocol_schema.TaskRequestType)).value + return generate_task(request) + case protocol_schema.TaskRequestType.summarize_story: + logger.info("Generating a SummarizeStoryTask.") + task = protocol_schema.SummarizeStoryTask( + story="This is a story. A very long story. So long, it needs to be summarized.", + ) + case protocol_schema.TaskRequestType.rate_summary: + logger.info("Generating a RateSummaryTask.") + task = protocol_schema.RateSummaryTask( + full_text="This is a story. A very long story. So long, it needs to be summarized.", + summary="This is a summary.", + scale=protocol_schema.RatingScale(min=1, max=5), + ) + case protocol_schema.TaskRequestType.initial_prompt: + logger.info("Generating an InitialPromptTask.") + task = protocol_schema.InitialPromptTask( + hint="Ask the assistant about a current event." # this is optional + ) + case protocol_schema.TaskRequestType.user_reply: + logger.info("Generating a UserReplyTask.") + task = protocol_schema.UserReplyTask( + conversation=protocol_schema.Conversation( + messages=[ + protocol_schema.ConversationMessage( + text="Hey, assistant, what's going on in the world?", + is_assistant=False, + ), + protocol_schema.ConversationMessage( + text="I'm not sure I understood correctly, could you rephrase that?", + is_assistant=True, + ), + ], + ) + ) + case protocol_schema.TaskRequestType.assistant_reply: + logger.info("Generating a AssistantReplyTask.") + task = protocol_schema.AssistantReplyTask( + conversation=protocol_schema.Conversation( + messages=[ + protocol_schema.ConversationMessage( + text="Hey, assistant, write me an English essay about water.", + is_assistant=False, + ), + ], + ) + ) + case protocol_schema.TaskRequestType.rank_initial_prompts: + logger.info("Generating a RankInitialPromptsTask.") + task = protocol_schema.RankInitialPromptsTask( + prompts=[ + "Please write a story about a time you were happy.", + "Please write a story about a time you were sad.", + ] + ) + case protocol_schema.TaskRequestType.rank_user_replies: + logger.info("Generating a RankUserRepliesTask.") + task = protocol_schema.RankUserRepliesTask( + conversation=protocol_schema.Conversation( + messages=[ + protocol_schema.ConversationMessage( + text="Hey, assistant, what's going on in the world?", + is_assistant=False, + ), + protocol_schema.ConversationMessage( + text="I'm not sure I understood correctly, could you rephrase that?", + is_assistant=True, + ), + ], + ), + replies=[ + "Oh come oooooon!", + "What are the news?", + ], + ) + + case protocol_schema.TaskRequestType.rank_assistant_replies: + logger.info("Generating a RankAssistantRepliesTask.") + task = protocol_schema.RankAssistantRepliesTask( + conversation=protocol_schema.Conversation( + messages=[ + protocol_schema.ConversationMessage( + text="Hey, assistant, what's going on in the world?", + is_assistant=False, + ), + ], + ), + replies=[ + "I'm not sure I understood correctly, could you rephrase that?", + "The world is fine. All good.", + "Crap is hitting the fan. Start farming.", + ], + ) + case _: + raise HTTPException( + status_code=HTTP_400_BAD_REQUEST, + detail="Invalid request type.", + ) + + logger.info(f"Generated {task=}.") + + return task + + +@router.post("/", response_model=protocol_schema.AnyTask) # work with Union once more types are added +def request_task( + *, + db: Session = Depends(deps.get_db), + api_key: APIKey = Depends(deps.get_api_key), + request: protocol_schema.TaskRequest, +) -> Any: + """ + Create new task. + """ + api_client = deps.api_auth(api_key, db) + + try: + task = generate_task(request) + + pr = PromptRepository(db, api_client, request.user) + pr.store_task(task) + + except Exception: + logger.exception("Failed to generate task.") + raise HTTPException( + status_code=HTTP_400_BAD_REQUEST, + ) + return task + + +@router.post("/{task_id}/ack") +def acknowledge_task( + *, + db: Session = Depends(deps.get_db), + api_key: APIKey = Depends(deps.get_api_key), + task_id: UUID, + ack_request: protocol_schema.TaskAck, +) -> Any: + """ + The frontend acknowledges a task. + """ + + api_client = deps.api_auth(api_key, db) + + try: + pr = PromptRepository(db, api_client, user=None) + + # here we store the post id in the database for the task + pr.bind_frontend_post_id(task_id=task_id, post_id=ack_request.post_id) + logger.info(f"Frontend acknowledges task {task_id=}, {ack_request=}.") + + except Exception: + logger.exception("Failed to acknowledge task.") + raise HTTPException( + status_code=HTTP_400_BAD_REQUEST, + ) + return {} + + +@router.post("/{task_id}/nack") +def acknowledge_task_failure( + *, + db: Session = Depends(deps.get_db), + api_key: APIKey = Depends(deps.get_api_key), + task_id: UUID, + nack_request: protocol_schema.TaskNAck, +) -> Any: + """ + The frontend reports failure to implement a task. + """ + deps.api_auth(api_key, db) + + logger.info(f"Frontend reports failure to implement task {task_id=}, {nack_request=}.") + # here we would store the post id in the database for the task + return {} + + +@router.post("/interaction") +def post_interaction( + *, + db: Session = Depends(deps.get_db), + api_key: APIKey = Depends(deps.get_api_key), + interaction: protocol_schema.AnyInteraction, +) -> Any: + """ + The frontend reports an interaction. + """ + api_client = deps.api_auth(api_key, db) + + try: + pr = PromptRepository(db, api_client, user=interaction.user) + + match type(interaction): + case protocol_schema.TextReplyToPost: + logger.info( + f"Frontend reports text reply to {interaction.post_id=} with {interaction.text=} by {interaction.user=}." + ) + + work_package = pr.fetch_workpackage_by_postid(interaction.post_id) + work_payload: TaskPayload = work_package.payload.payload + logger.info(f"found task work package in db: {work_payload}") + + # here we store the text reply in the database + # ToDo: role user or agent? + pr.store_text_reply(interaction, role="unknown") + + return protocol_schema.TaskDone() + case protocol_schema.PostRating: + logger.info( + f"Frontend reports rating of {interaction.post_id=} with {interaction.rating=} by {interaction.user=}." + ) + + # here we store the rating in the database + pr.store_rating(interaction) + + return protocol_schema.TaskDone() + case protocol_schema.PostRanking: + logger.info( + f"Frontend reports ranking of {interaction.post_id=} with {interaction.ranking=} by {interaction.user=}." + ) + + # TODO: check if the ranking is valid + pr.store_ranking(interaction) + # here we would store the ranking in the database + return protocol_schema.TaskDone() + case _: + raise HTTPException( + status_code=HTTP_400_BAD_REQUEST, + detail="Invalid response type.", + ) + + except Exception: + logger.exception("Interaction request failed.") + raise HTTPException( + status_code=HTTP_400_BAD_REQUEST, + ) diff --git a/backend/app/oasst/database.py b/backend/app/oasst/database.py new file mode 100644 index 00000000..ca729f4e --- /dev/null +++ b/backend/app/oasst/database.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +from oasst.config import settings +from sqlmodel import create_engine + +if settings.DATABASE_URI is None: + raise ValueError("DATABASE_URI is not set") + +engine = create_engine(settings.DATABASE_URI) diff --git a/backend/app/oasst/models/db_payload.py b/backend/app/oasst/models/db_payload.py new file mode 100644 index 00000000..b01cecce --- /dev/null +++ b/backend/app/oasst/models/db_payload.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- +from typing import Literal + +from oasst.models.payload_column_type import payload_type +from oasst.schemas import protocol as protocol_schema +from pydantic import BaseModel + + +@payload_type +class TaskPayload(BaseModel): + type: str + + +@payload_type +class SummarizationStoryPayload(TaskPayload): + type: Literal["summarize_story"] = "summarize_story" + story: str + + +@payload_type +class RateSummaryPayload(TaskPayload): + type: Literal["rate_summary"] = "rate_summary" + full_text: str + summary: str + scale: protocol_schema.RatingScale + + +@payload_type +class InitialPromptPayload(TaskPayload): + type: Literal["initial_prompt"] = "initial_prompt" + hint: str + + +@payload_type +class UserReplyPayload(TaskPayload): + type: Literal["user_reply"] = "user_reply" + conversation: protocol_schema.Conversation + hint: str | None + + +@payload_type +class AssistantReplyPayload(TaskPayload): + type: Literal["assistant_reply"] = "assistant_reply" + conversation: protocol_schema.Conversation + + +@payload_type +class PostPayload(BaseModel): + text: str + + +@payload_type +class ReactionPayload(BaseModel): + type: str + + +@payload_type +class RatingReactionPayload(ReactionPayload): + type: Literal["post_rating"] = "post_rating" + rating: str + + +@payload_type +class RankingReactionPayload(ReactionPayload): + type: Literal["post_ranking"] = "post_ranking" + ranking: list[int] + + +@payload_type +class RankConversationRepliesPayload(TaskPayload): + conversation: protocol_schema.Conversation # the conversation so far + replies: list[str] + + +@payload_type +class RankInitialPromptsPayload(TaskPayload): + """A task to rank a set of initial prompts.""" + + type: Literal["rank_initial_prompts"] = "rank_initial_prompts" + prompts: list[str] + + +@payload_type +class RankUserRepliesPayload(RankConversationRepliesPayload): + """A task to rank a set of user replies to a conversation.""" + + type: Literal["rank_user_replies"] = "rank_user_replies" + + +@payload_type +class RankAssistantRepliesPayload(RankConversationRepliesPayload): + """A task to rank a set of assistant replies to a conversation.""" + + type: Literal["rank_assistant_replies"] = "rank_assistant_replies" diff --git a/backend/app/oasst/prompt_repository.py b/backend/app/oasst/prompt_repository.py new file mode 100644 index 00000000..35f1c9b3 --- /dev/null +++ b/backend/app/oasst/prompt_repository.py @@ -0,0 +1,311 @@ +# -*- coding: utf-8 -*- +from datetime import datetime +from typing import Optional +from uuid import UUID, uuid4 + +import oasst.models.db_payload as db_payload +from loguru import logger +from oasst.models import ApiClient, Person, Post, PostReaction, WorkPackage +from oasst.models.payload_column_type import PayloadContainer +from oasst.schemas import protocol as protocol_schema +from sqlmodel import Session + + +class PromptRepository: + def __init__(self, db: Session, api_client: ApiClient, user: Optional[protocol_schema.User]): + self.db = db + self.api_client = api_client + self.person = self.lookup_person(user) + self.person_id = self.person.id if self.person else None + + def lookup_person(self, user: protocol_schema.User) -> Person: + if not user: + return None + person: Person = ( + self.db.query(Person) + .filter( + Person.api_client_id == self.api_client.id, + Person.username == user.id, + Person.auth_method == user.auth_method, + ) + .first() + ) + if person is None: + # user is unknown, create new record + person = Person(username=user.id, display_name=user.display_name, api_client_id=self.api_client.id) + self.db.add(person) + self.db.commit() + self.db.refresh(person) + elif user.display_name and user.display_name != person.display_name: + # we found the user but the display name changed + person.display_name = user.display_name + self.db.add(person) + self.db.commit() + return person + + def validate_post_id(self, post_id: str) -> None: + if not isinstance(post_id, str): + raise TypeError(f"post_id must be string, not {type(post_id)}") + if not post_id: + raise ValueError("post_id must not be empty") + + def bind_frontend_post_id(self, task_id: UUID, post_id: str): + self.validate_post_id(post_id) + + # find work package + work_pack: WorkPackage = ( + self.db.query(WorkPackage) + .filter(WorkPackage.id == task_id, WorkPackage.api_client_id == self.api_client.id) + .first() + ) + if work_pack is None: + raise KeyError(f"WorkPackage for task {task_id} not found") + if work_pack.expiry_date is not None and datetime.utcnow() > work_pack.expiry_date: + raise RuntimeError("WorkPackage already expired.") + + # ToDo: check race-condition, transaction + + # check if task thread exits + thread_root = ( + self.db.query(Post) + .filter( + Post.workpackage_id == work_pack.id, + Post.frontend_post_id == post_id, + Post.parent_id is None, + Post.api_client_id == self.api_client.id, + ) + .one_or_none() + ) + if thread_root is None: + thread_id = uuid4() + thread_root = self.insert_post( + post_id=thread_id, + thread_id=thread_id, + frontend_post_id=post_id, + parent_id=None, + role="system", + workpackage_id=work_pack.id, + payload=None, + payload_type="bind", + ) + return thread_root + + def fetch_post_by_frontend_post_id(self, frontend_post_id: str, fail_if_missing: bool = True) -> Post: + self.validate_post_id(frontend_post_id) + post: Post = ( + self.db.query(Post) + .filter(Post.api_client_id == self.api_client.id, Post.frontend_post_id == frontend_post_id) + .one_or_none() + ) + if fail_if_missing and post is None: + raise KeyError(f"Post with post_id {frontend_post_id} not found.") + return post + + def fetch_workpackage_by_postid(self, post_id: str) -> WorkPackage: + self.validate_post_id(post_id) + post = self.fetch_post_by_frontend_post_id(post_id, fail_if_missing=True) + work_pack = self.db.query(WorkPackage).filter(WorkPackage.id == post.workpackage_id).one() + return work_pack + + def store_text_reply(self, reply: protocol_schema.TextReplyToPost, role: str) -> Post: + self.validate_post_id(reply.post_id) + self.validate_post_id(reply.user_post_id) + + # find post with post-id + parent_post: Post = ( + self.db.query(Post) + .filter( + Post.api_client_id == self.api_client.id, + Post.frontend_post_id == reply.post_id, + # Post.person_id == self.person_id + ) + .one_or_none() + ) + + if parent_post is None: + raise KeyError(f"Post for post_id {reply.post_id} not found.") + + # create reply post + user_post_id = uuid4() + user_post = self.insert_post( + post_id=user_post_id, + frontend_post_id=reply.user_post_id, + parent_id=parent_post.id, + thread_id=parent_post.thread_id, + workpackage_id=parent_post.workpackage_id, + role=role, + payload=db_payload.PostPayload(text=reply.text), + ) + return user_post + + def store_rating(self, rating: protocol_schema.PostRating) -> PostReaction: + post = self.fetch_post_by_frontend_post_id(rating.post_id, fail_if_missing=True) + + work_package = self.fetch_workpackage_by_postid(rating.post_id) + work_payload: db_payload.RateSummaryPayload = work_package.payload.payload + if type(work_payload) != db_payload.RateSummaryPayload: + raise ValueError( + f"work_package payload type mismatch: {type(work_payload)=} != {db_payload.RateSummaryPayload}" + ) + + if rating.rating < work_payload.scale.min or rating.rating > work_payload.scale.max: + raise ValueError(f"Invalid rating value: {rating.rating=} not in {work_payload.scale=}") + + # store reaction to post + reaction_payload = db_payload.RatingReactionPayload(rating=rating.rating) + reaction = self.insert_reaction(post.id, reaction_payload) + logger.info(f"Ranking {rating.rating} stored for work_package {work_package.id}.") + return reaction + + def store_ranking(self, ranking: protocol_schema.PostRanking) -> PostReaction: + post = self.fetch_post_by_frontend_post_id(ranking.post_id, fail_if_missing=True) + + # fetch work_package + work_package = self.fetch_workpackage_by_postid(ranking.post_id) + work_payload: db_payload.RankConversationRepliesPayload | db_payload.RankInitialPromptsPayload = ( + work_package.payload.payload + ) + + match type(work_payload): + + case db_payload.RankUserRepliesPayload | db_payload.RankAssistantRepliesPayload: + # validate ranking + num_replies = len(work_payload.replies) + if sorted(ranking.ranking) != list(range(num_replies)): + raise ValueError( + f"Invalid ranking submitted. Each reply index must appear exactly once ({num_replies=})." + ) + + # store reaction to post + reaction_payload = db_payload.RankingReactionPayload(ranking=ranking.ranking) + reaction = self.insert_reaction(post.id, reaction_payload) + + logger.info(f"Ranking {ranking.ranking} stored for work_package {work_package.id}.") + + return reaction + + case db_payload.RankInitialPromptsPayload: + # validate ranking + if sorted(ranking.ranking) != list(range(num_prompts := len(work_payload.prompts))): + raise ValueError( + f"Invalid ranking submitted. Each reply index must appear exactly once ({num_prompts=})." + ) + + # store reaction to post + reaction_payload = db_payload.RankingReactionPayload(ranking=ranking.ranking) + reaction = self.insert_reaction(post.id, reaction_payload) + + logger.info(f"Ranking {ranking.ranking} stored for work_package {work_package.id}.") + + return reaction + + case _: + raise ValueError( + f"work_package payload type mismatch: {type(work_payload)=} != {db_payload.RankConversationRepliesPayload}" + ) + + def store_task(self, task: protocol_schema.Task) -> WorkPackage: + payload: db_payload.TaskPayload + match type(task): + case protocol_schema.SummarizeStoryTask: + payload = db_payload.SummarizationStoryPayload(story=task.story) + + case protocol_schema.RateSummaryTask: + payload = db_payload.RateSummaryPayload( + full_text=task.full_text, summary=task.summary, scale=task.scale + ) + + case protocol_schema.InitialPromptTask: + payload = db_payload.InitialPromptPayload(hint=task.hint) + + case protocol_schema.UserReplyTask: + payload = db_payload.UserReplyPayload(conversation=task.conversation, hint=task.hint) + + case protocol_schema.AssistantReplyTask: + payload = db_payload.AssistantReplyPayload(type=task.type, conversation=task.conversation) + + case protocol_schema.RankInitialPromptsTask: + payload = db_payload.RankInitialPromptsPayload(tpye=task.type, prompts=task.prompts) + + case protocol_schema.RankUserRepliesTask: + payload = db_payload.RankUserRepliesPayload( + tpye=task.type, conversation=task.conversation, replies=task.replies + ) + + case protocol_schema.RankAssistantRepliesTask: + payload = db_payload.RankAssistantRepliesPayload( + tpye=task.type, conversation=task.conversation, replies=task.replies + ) + + case _: + raise ValueError(f"Invalid task type: {type(task)=}") + + wp = self.insert_work_package(payload=payload, id=task.id) + assert wp.id == task.id + return wp + + def insert_work_package(self, payload: db_payload.TaskPayload, id: UUID = None) -> WorkPackage: + c = PayloadContainer(payload=payload) + wp = WorkPackage( + id=id, + person_id=self.person_id, + payload_type=type(payload).__name__, + payload=c, + api_client_id=self.api_client.id, + ) + self.db.add(wp) + self.db.commit() + self.db.refresh(wp) + return wp + + def insert_post( + self, + *, + post_id: UUID, + frontend_post_id: str, + parent_id: UUID, + thread_id: UUID, + workpackage_id: UUID, + role: str, + payload: db_payload.PostPayload, + payload_type: str = None, + ) -> Post: + if payload_type is None: + if payload is None: + payload_type = "null" + else: + payload_type = type(payload).__name__ + + post = Post( + id=post_id, + parent_id=parent_id, + thread_id=thread_id, + workpackage_id=workpackage_id, + person_id=self.person_id, + role=role, + frontend_post_id=frontend_post_id, + api_client_id=self.api_client.id, + payload_type=payload_type, + payload=PayloadContainer(payload=payload), + ) + self.db.add(post) + self.db.commit() + self.db.refresh(post) + return post + + def insert_reaction(self, post_id: UUID, payload: db_payload.ReactionPayload) -> PostReaction: + if self.person_id is None: + raise ValueError("User required") + + container = PayloadContainer(payload=payload) + reaction = PostReaction( + post_id=post_id, + person_id=self.person_id, + payload=container, + api_client_id=self.api_client.id, + payload_type=type(payload).__name__, + ) + self.db.add(reaction) + self.db.commit() + self.db.refresh(reaction) + return reaction From 13551ae3e4b6bce110e46c917dfde95f6aea70d7 Mon Sep 17 00:00:00 2001 From: Yannic Kilcher Date: Sat, 17 Dec 2022 23:56:05 +0100 Subject: [PATCH 19/21] renamed to open assistant --- .github/workflows/release.yaml | 2 +- README.md | 8 +- backend/README.md | 4 +- backend/app/{ocgpt => oasst}/__init__.py | 0 backend/app/{ocgpt => oasst}/api/__init__.py | 0 .../app/{ocgpt => oasst}/api/v1/__init__.py | 0 backend/app/{ocgpt => oasst}/config.py | 2 +- backend/app/{ocgpt => oasst}/crud/__init__.py | 0 backend/app/{ocgpt => oasst}/crud/base.py | 0 .../app/{ocgpt => oasst}/models/__init__.py | 0 .../app/{ocgpt => oasst}/models/api_client.py | 0 .../models/payload_column_type.py | 0 backend/app/{ocgpt => oasst}/models/person.py | 0 .../{ocgpt => oasst}/models/person_stats.py | 0 backend/app/{ocgpt => oasst}/models/post.py | 0 .../{ocgpt => oasst}/models/post_reaction.py | 0 .../{ocgpt => oasst}/models/work_package.py | 0 .../app/{ocgpt => oasst}/schemas/__init__.py | 0 .../app/{ocgpt => oasst}/schemas/protocol.py | 0 backend/app/ocgpt/api/deps.py | 57 ---- backend/app/ocgpt/api/v1/api.py | 6 - backend/app/ocgpt/api/v1/tasks.py | 259 --------------- backend/app/ocgpt/database.py | 8 - backend/app/ocgpt/models/db_payload.py | 94 ------ backend/app/ocgpt/prompt_repository.py | 311 ------------------ bot/README.md | 4 +- bot/bot.py | 4 +- bot/setup.py | 4 +- .../frontend-development/docker-compose.yaml | 2 +- website/package.json | 2 +- website/pages/index.js | 8 +- website/public/index.html | 4 +- 32 files changed, 22 insertions(+), 757 deletions(-) rename backend/app/{ocgpt => oasst}/__init__.py (100%) rename backend/app/{ocgpt => oasst}/api/__init__.py (100%) rename backend/app/{ocgpt => oasst}/api/v1/__init__.py (100%) rename backend/app/{ocgpt => oasst}/config.py (96%) rename backend/app/{ocgpt => oasst}/crud/__init__.py (100%) rename backend/app/{ocgpt => oasst}/crud/base.py (100%) rename backend/app/{ocgpt => oasst}/models/__init__.py (100%) rename backend/app/{ocgpt => oasst}/models/api_client.py (100%) rename backend/app/{ocgpt => oasst}/models/payload_column_type.py (100%) rename backend/app/{ocgpt => oasst}/models/person.py (100%) rename backend/app/{ocgpt => oasst}/models/person_stats.py (100%) rename backend/app/{ocgpt => oasst}/models/post.py (100%) rename backend/app/{ocgpt => oasst}/models/post_reaction.py (100%) rename backend/app/{ocgpt => oasst}/models/work_package.py (100%) rename backend/app/{ocgpt => oasst}/schemas/__init__.py (100%) rename backend/app/{ocgpt => oasst}/schemas/protocol.py (100%) delete mode 100644 backend/app/ocgpt/api/deps.py delete mode 100644 backend/app/ocgpt/api/v1/api.py delete mode 100644 backend/app/ocgpt/api/v1/tasks.py delete mode 100644 backend/app/ocgpt/database.py delete mode 100644 backend/app/ocgpt/models/db_payload.py delete mode 100644 backend/app/ocgpt/prompt_repository.py diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index eb9bea65..a7dd89c8 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -8,6 +8,6 @@ jobs: build-backend: uses: ./.github/workflows/docker-build.yaml with: - image-name: ocgpt-backend + image-name: oasst-backend folder: backend build-args: "" diff --git a/README.md b/README.md index 25104969..72a4aac6 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# Open-Chat-GPT +# Open-Assistant -Open chat gpt is a project meant to give everyone access to a great chat based large language model. +Open Assistant is a project meant to give everyone access to a great chat based large language model. -We believe that by doing this we will create a revolution in innovation in language. In the same way that stable-diffusion helped the world make art and images in new ways we hope open chat gpt can help improve the world by improving language itself. +We believe that by doing this we will create a revolution in innovation in language. In the same way that stable-diffusion helped the world make art and images in new ways we hope Open Assistant can help improve the world by improving language itself. ## How can you help? @@ -14,7 +14,7 @@ All open source projects begins with people like you. Open source is the belief [Join the LAION Discord Server!](https://discord.gg/RQFtmAmk) -[Visit the Notion](https://ykilcher.com/open-chat-gpt) +[Visit the Notion](https://ykilcher.com/open-assistant) ## Developer Setup diff --git a/backend/README.md b/backend/README.md index a559b15b..1e41e72c 100644 --- a/backend/README.md +++ b/backend/README.md @@ -1,4 +1,4 @@ -# Open-Chat-GPT REST Backend +# Open-Assistant REST Backend ## REST Server Configuration @@ -8,7 +8,7 @@ Example contents of a `.env` file for the backend: ``` DATABASE_URI="postgresql://:@/" -BACKEND_CORS_ORIGINS=["http://localhost", "http://localhost:4200", "http://localhost:3000", "http://localhost:8080", "https://localhost", "https://localhost:4200", "https://localhost:3000", "https://localhost:8080", "http://dev.ocgpt.laion.ai", "https://stag.ocgpt.laion.ai", "https://ocgpt.laion.ai"] +BACKEND_CORS_ORIGINS=["http://localhost", "http://localhost:4200", "http://localhost:3000", "http://localhost:8080", "https://localhost", "https://localhost:4200", "https://localhost:3000", "https://localhost:8080", "http://dev.oasst.laion.ai", "https://stag.oasst.laion.ai", "https://oasst.laion.ai"] ``` diff --git a/backend/app/ocgpt/__init__.py b/backend/app/oasst/__init__.py similarity index 100% rename from backend/app/ocgpt/__init__.py rename to backend/app/oasst/__init__.py diff --git a/backend/app/ocgpt/api/__init__.py b/backend/app/oasst/api/__init__.py similarity index 100% rename from backend/app/ocgpt/api/__init__.py rename to backend/app/oasst/api/__init__.py diff --git a/backend/app/ocgpt/api/v1/__init__.py b/backend/app/oasst/api/v1/__init__.py similarity index 100% rename from backend/app/ocgpt/api/v1/__init__.py rename to backend/app/oasst/api/v1/__init__.py diff --git a/backend/app/ocgpt/config.py b/backend/app/oasst/config.py similarity index 96% rename from backend/app/ocgpt/config.py rename to backend/app/oasst/config.py index 71bade6c..0a1049f7 100644 --- a/backend/app/ocgpt/config.py +++ b/backend/app/oasst/config.py @@ -5,7 +5,7 @@ from pydantic import AnyHttpUrl, BaseSettings, PostgresDsn, validator class Settings(BaseSettings): - PROJECT_NAME: str = "open-chatGPT backend" + PROJECT_NAME: str = "open-assistant backend" API_V1_STR: str = "/api/v1" POSTGRES_HOST: str = "localhost" diff --git a/backend/app/ocgpt/crud/__init__.py b/backend/app/oasst/crud/__init__.py similarity index 100% rename from backend/app/ocgpt/crud/__init__.py rename to backend/app/oasst/crud/__init__.py diff --git a/backend/app/ocgpt/crud/base.py b/backend/app/oasst/crud/base.py similarity index 100% rename from backend/app/ocgpt/crud/base.py rename to backend/app/oasst/crud/base.py diff --git a/backend/app/ocgpt/models/__init__.py b/backend/app/oasst/models/__init__.py similarity index 100% rename from backend/app/ocgpt/models/__init__.py rename to backend/app/oasst/models/__init__.py diff --git a/backend/app/ocgpt/models/api_client.py b/backend/app/oasst/models/api_client.py similarity index 100% rename from backend/app/ocgpt/models/api_client.py rename to backend/app/oasst/models/api_client.py diff --git a/backend/app/ocgpt/models/payload_column_type.py b/backend/app/oasst/models/payload_column_type.py similarity index 100% rename from backend/app/ocgpt/models/payload_column_type.py rename to backend/app/oasst/models/payload_column_type.py diff --git a/backend/app/ocgpt/models/person.py b/backend/app/oasst/models/person.py similarity index 100% rename from backend/app/ocgpt/models/person.py rename to backend/app/oasst/models/person.py diff --git a/backend/app/ocgpt/models/person_stats.py b/backend/app/oasst/models/person_stats.py similarity index 100% rename from backend/app/ocgpt/models/person_stats.py rename to backend/app/oasst/models/person_stats.py diff --git a/backend/app/ocgpt/models/post.py b/backend/app/oasst/models/post.py similarity index 100% rename from backend/app/ocgpt/models/post.py rename to backend/app/oasst/models/post.py diff --git a/backend/app/ocgpt/models/post_reaction.py b/backend/app/oasst/models/post_reaction.py similarity index 100% rename from backend/app/ocgpt/models/post_reaction.py rename to backend/app/oasst/models/post_reaction.py diff --git a/backend/app/ocgpt/models/work_package.py b/backend/app/oasst/models/work_package.py similarity index 100% rename from backend/app/ocgpt/models/work_package.py rename to backend/app/oasst/models/work_package.py diff --git a/backend/app/ocgpt/schemas/__init__.py b/backend/app/oasst/schemas/__init__.py similarity index 100% rename from backend/app/ocgpt/schemas/__init__.py rename to backend/app/oasst/schemas/__init__.py diff --git a/backend/app/ocgpt/schemas/protocol.py b/backend/app/oasst/schemas/protocol.py similarity index 100% rename from backend/app/ocgpt/schemas/protocol.py rename to backend/app/oasst/schemas/protocol.py diff --git a/backend/app/ocgpt/api/deps.py b/backend/app/ocgpt/api/deps.py deleted file mode 100644 index 88c054ae..00000000 --- a/backend/app/ocgpt/api/deps.py +++ /dev/null @@ -1,57 +0,0 @@ -# -*- coding: utf-8 -*- -from secrets import token_hex -from typing import Generator -from uuid import UUID - -from fastapi import HTTPException, Security -from fastapi.security.api_key import APIKey, APIKeyHeader, APIKeyQuery -from loguru import logger -from ocgpt.config import settings -from ocgpt.database import engine -from ocgpt.models import ApiClient -from sqlmodel import Session -from starlette.status import HTTP_403_FORBIDDEN - - -def get_db() -> Generator: - with Session(engine) as db: - yield db - - -api_key_query = APIKeyQuery(name="api_key", auto_error=False) -api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False) - - -async def get_api_key( - api_key_query: str = Security(api_key_query), - api_key_header: str = Security(api_key_header), -): - if api_key_query: - return api_key_query - else: - return api_key_header - - -def api_auth( - api_key: APIKey, - db: Session, -) -> ApiClient: - - if api_key is not None: - if settings.ALLOW_ANY_API_KEY: - # make sure that a dummy api key exits in db (foreign key references) - ANY_API_KEY_ID = UUID("00000000-1111-2222-3333-444444444444") - api_client: ApiClient = db.query(ApiClient).filter(ApiClient.id == ANY_API_KEY_ID).first() - if api_client is None: - token = token_hex(32) - logger.info(f"ANY_API_KEY missing, inserting api_key: {token}") - api_client = ApiClient(id=ANY_API_KEY_ID, api_key=token, description="ANY_API_KEY, random token") - db.add(api_client) - db.commit() - return api_client - - api_client = db.query(ApiClient).filter(ApiClient.api_key == api_key).first() - if api_client is not None and api_client.enabled: - return api_client - - raise HTTPException(status_code=HTTP_403_FORBIDDEN, detail="Could not validate credentials") diff --git a/backend/app/ocgpt/api/v1/api.py b/backend/app/ocgpt/api/v1/api.py deleted file mode 100644 index 1e304656..00000000 --- a/backend/app/ocgpt/api/v1/api.py +++ /dev/null @@ -1,6 +0,0 @@ -# -*- coding: utf-8 -*- -from fastapi import APIRouter -from ocgpt.api.v1 import tasks - -api_router = APIRouter() -api_router.include_router(tasks.router, prefix="/tasks", tags=["tasks"]) diff --git a/backend/app/ocgpt/api/v1/tasks.py b/backend/app/ocgpt/api/v1/tasks.py deleted file mode 100644 index ef12276a..00000000 --- a/backend/app/ocgpt/api/v1/tasks.py +++ /dev/null @@ -1,259 +0,0 @@ -# -*- coding: utf-8 -*- -import random -from typing import Any -from uuid import UUID - -from fastapi import APIRouter, Depends, HTTPException -from fastapi.security.api_key import APIKey -from loguru import logger -from ocgpt.api import deps -from ocgpt.models.db_payload import TaskPayload -from ocgpt.prompt_repository import PromptRepository -from ocgpt.schemas import protocol as protocol_schema -from sqlmodel import Session -from starlette.status import HTTP_400_BAD_REQUEST - -router = APIRouter() - - -def generate_task(request: protocol_schema.TaskRequest) -> protocol_schema.Task: - match (request.type): - case protocol_schema.TaskRequestType.random: - logger.info("Frontend requested a random task.") - while request.type == protocol_schema.TaskRequestType.random: - request.type = random.choice(list(protocol_schema.TaskRequestType)).value - return generate_task(request) - case protocol_schema.TaskRequestType.summarize_story: - logger.info("Generating a SummarizeStoryTask.") - task = protocol_schema.SummarizeStoryTask( - story="This is a story. A very long story. So long, it needs to be summarized.", - ) - case protocol_schema.TaskRequestType.rate_summary: - logger.info("Generating a RateSummaryTask.") - task = protocol_schema.RateSummaryTask( - full_text="This is a story. A very long story. So long, it needs to be summarized.", - summary="This is a summary.", - scale=protocol_schema.RatingScale(min=1, max=5), - ) - case protocol_schema.TaskRequestType.initial_prompt: - logger.info("Generating an InitialPromptTask.") - task = protocol_schema.InitialPromptTask( - hint="Ask the assistant about a current event." # this is optional - ) - case protocol_schema.TaskRequestType.user_reply: - logger.info("Generating a UserReplyTask.") - task = protocol_schema.UserReplyTask( - conversation=protocol_schema.Conversation( - messages=[ - protocol_schema.ConversationMessage( - text="Hey, assistant, what's going on in the world?", - is_assistant=False, - ), - protocol_schema.ConversationMessage( - text="I'm not sure I understood correctly, could you rephrase that?", - is_assistant=True, - ), - ], - ) - ) - case protocol_schema.TaskRequestType.assistant_reply: - logger.info("Generating a AssistantReplyTask.") - task = protocol_schema.AssistantReplyTask( - conversation=protocol_schema.Conversation( - messages=[ - protocol_schema.ConversationMessage( - text="Hey, assistant, write me an English essay about water.", - is_assistant=False, - ), - ], - ) - ) - case protocol_schema.TaskRequestType.rank_initial_prompts: - logger.info("Generating a RankInitialPromptsTask.") - task = protocol_schema.RankInitialPromptsTask( - prompts=[ - "Please write a story about a time you were happy.", - "Please write a story about a time you were sad.", - ] - ) - case protocol_schema.TaskRequestType.rank_user_replies: - logger.info("Generating a RankUserRepliesTask.") - task = protocol_schema.RankUserRepliesTask( - conversation=protocol_schema.Conversation( - messages=[ - protocol_schema.ConversationMessage( - text="Hey, assistant, what's going on in the world?", - is_assistant=False, - ), - protocol_schema.ConversationMessage( - text="I'm not sure I understood correctly, could you rephrase that?", - is_assistant=True, - ), - ], - ), - replies=[ - "Oh come oooooon!", - "What are the news?", - ], - ) - - case protocol_schema.TaskRequestType.rank_assistant_replies: - logger.info("Generating a RankAssistantRepliesTask.") - task = protocol_schema.RankAssistantRepliesTask( - conversation=protocol_schema.Conversation( - messages=[ - protocol_schema.ConversationMessage( - text="Hey, assistant, what's going on in the world?", - is_assistant=False, - ), - ], - ), - replies=[ - "I'm not sure I understood correctly, could you rephrase that?", - "The world is fine. All good.", - "Crap is hitting the fan. Start farming.", - ], - ) - case _: - raise HTTPException( - status_code=HTTP_400_BAD_REQUEST, - detail="Invalid request type.", - ) - - logger.info(f"Generated {task=}.") - - return task - - -@router.post("/", response_model=protocol_schema.AnyTask) # work with Union once more types are added -def request_task( - *, - db: Session = Depends(deps.get_db), - api_key: APIKey = Depends(deps.get_api_key), - request: protocol_schema.TaskRequest, -) -> Any: - """ - Create new task. - """ - api_client = deps.api_auth(api_key, db) - - try: - task = generate_task(request) - - pr = PromptRepository(db, api_client, request.user) - pr.store_task(task) - - except Exception: - logger.exception("Failed to generate task.") - raise HTTPException( - status_code=HTTP_400_BAD_REQUEST, - ) - return task - - -@router.post("/{task_id}/ack") -def acknowledge_task( - *, - db: Session = Depends(deps.get_db), - api_key: APIKey = Depends(deps.get_api_key), - task_id: UUID, - ack_request: protocol_schema.TaskAck, -) -> Any: - """ - The frontend acknowledges a task. - """ - - api_client = deps.api_auth(api_key, db) - - try: - pr = PromptRepository(db, api_client, user=None) - - # here we store the post id in the database for the task - pr.bind_frontend_post_id(task_id=task_id, post_id=ack_request.post_id) - logger.info(f"Frontend acknowledges task {task_id=}, {ack_request=}.") - - except Exception: - logger.exception("Failed to acknowledge task.") - raise HTTPException( - status_code=HTTP_400_BAD_REQUEST, - ) - return {} - - -@router.post("/{task_id}/nack") -def acknowledge_task_failure( - *, - db: Session = Depends(deps.get_db), - api_key: APIKey = Depends(deps.get_api_key), - task_id: UUID, - nack_request: protocol_schema.TaskNAck, -) -> Any: - """ - The frontend reports failure to implement a task. - """ - deps.api_auth(api_key, db) - - logger.info(f"Frontend reports failure to implement task {task_id=}, {nack_request=}.") - # here we would store the post id in the database for the task - return {} - - -@router.post("/interaction") -def post_interaction( - *, - db: Session = Depends(deps.get_db), - api_key: APIKey = Depends(deps.get_api_key), - interaction: protocol_schema.AnyInteraction, -) -> Any: - """ - The frontend reports an interaction. - """ - api_client = deps.api_auth(api_key, db) - - try: - pr = PromptRepository(db, api_client, user=interaction.user) - - match type(interaction): - case protocol_schema.TextReplyToPost: - logger.info( - f"Frontend reports text reply to {interaction.post_id=} with {interaction.text=} by {interaction.user=}." - ) - - work_package = pr.fetch_workpackage_by_postid(interaction.post_id) - work_payload: TaskPayload = work_package.payload.payload - logger.info(f"found task work package in db: {work_payload}") - - # here we store the text reply in the database - # ToDo: role user or agent? - pr.store_text_reply(interaction, role="unknown") - - return protocol_schema.TaskDone() - case protocol_schema.PostRating: - logger.info( - f"Frontend reports rating of {interaction.post_id=} with {interaction.rating=} by {interaction.user=}." - ) - - # here we store the rating in the database - pr.store_rating(interaction) - - return protocol_schema.TaskDone() - case protocol_schema.PostRanking: - logger.info( - f"Frontend reports ranking of {interaction.post_id=} with {interaction.ranking=} by {interaction.user=}." - ) - - # TODO: check if the ranking is valid - pr.store_ranking(interaction) - # here we would store the ranking in the database - return protocol_schema.TaskDone() - case _: - raise HTTPException( - status_code=HTTP_400_BAD_REQUEST, - detail="Invalid response type.", - ) - - except Exception: - logger.exception("Interaction request failed.") - raise HTTPException( - status_code=HTTP_400_BAD_REQUEST, - ) diff --git a/backend/app/ocgpt/database.py b/backend/app/ocgpt/database.py deleted file mode 100644 index 96ada5d6..00000000 --- a/backend/app/ocgpt/database.py +++ /dev/null @@ -1,8 +0,0 @@ -# -*- coding: utf-8 -*- -from ocgpt.config import settings -from sqlmodel import create_engine - -if settings.DATABASE_URI is None: - raise ValueError("DATABASE_URI is not set") - -engine = create_engine(settings.DATABASE_URI) diff --git a/backend/app/ocgpt/models/db_payload.py b/backend/app/ocgpt/models/db_payload.py deleted file mode 100644 index 730178e3..00000000 --- a/backend/app/ocgpt/models/db_payload.py +++ /dev/null @@ -1,94 +0,0 @@ -# -*- coding: utf-8 -*- -from typing import Literal - -from ocgpt.models.payload_column_type import payload_type -from ocgpt.schemas import protocol as protocol_schema -from pydantic import BaseModel - - -@payload_type -class TaskPayload(BaseModel): - type: str - - -@payload_type -class SummarizationStoryPayload(TaskPayload): - type: Literal["summarize_story"] = "summarize_story" - story: str - - -@payload_type -class RateSummaryPayload(TaskPayload): - type: Literal["rate_summary"] = "rate_summary" - full_text: str - summary: str - scale: protocol_schema.RatingScale - - -@payload_type -class InitialPromptPayload(TaskPayload): - type: Literal["initial_prompt"] = "initial_prompt" - hint: str - - -@payload_type -class UserReplyPayload(TaskPayload): - type: Literal["user_reply"] = "user_reply" - conversation: protocol_schema.Conversation - hint: str | None - - -@payload_type -class AssistantReplyPayload(TaskPayload): - type: Literal["assistant_reply"] = "assistant_reply" - conversation: protocol_schema.Conversation - - -@payload_type -class PostPayload(BaseModel): - text: str - - -@payload_type -class ReactionPayload(BaseModel): - type: str - - -@payload_type -class RatingReactionPayload(ReactionPayload): - type: Literal["post_rating"] = "post_rating" - rating: str - - -@payload_type -class RankingReactionPayload(ReactionPayload): - type: Literal["post_ranking"] = "post_ranking" - ranking: list[int] - - -@payload_type -class RankConversationRepliesPayload(TaskPayload): - conversation: protocol_schema.Conversation # the conversation so far - replies: list[str] - - -@payload_type -class RankInitialPromptsPayload(TaskPayload): - """A task to rank a set of initial prompts.""" - - type: Literal["rank_initial_prompts"] = "rank_initial_prompts" - prompts: list[str] - - -@payload_type -class RankUserRepliesPayload(RankConversationRepliesPayload): - """A task to rank a set of user replies to a conversation.""" - - type: Literal["rank_user_replies"] = "rank_user_replies" - - -@payload_type -class RankAssistantRepliesPayload(RankConversationRepliesPayload): - """A task to rank a set of assistant replies to a conversation.""" - - type: Literal["rank_assistant_replies"] = "rank_assistant_replies" diff --git a/backend/app/ocgpt/prompt_repository.py b/backend/app/ocgpt/prompt_repository.py deleted file mode 100644 index 8c621df1..00000000 --- a/backend/app/ocgpt/prompt_repository.py +++ /dev/null @@ -1,311 +0,0 @@ -# -*- coding: utf-8 -*- -from datetime import datetime -from typing import Optional -from uuid import UUID, uuid4 - -import ocgpt.models.db_payload as db_payload -from loguru import logger -from ocgpt.models import ApiClient, Person, Post, PostReaction, WorkPackage -from ocgpt.models.payload_column_type import PayloadContainer -from ocgpt.schemas import protocol as protocol_schema -from sqlmodel import Session - - -class PromptRepository: - def __init__(self, db: Session, api_client: ApiClient, user: Optional[protocol_schema.User]): - self.db = db - self.api_client = api_client - self.person = self.lookup_person(user) - self.person_id = self.person.id if self.person else None - - def lookup_person(self, user: protocol_schema.User) -> Person: - if not user: - return None - person: Person = ( - self.db.query(Person) - .filter( - Person.api_client_id == self.api_client.id, - Person.username == user.id, - Person.auth_method == user.auth_method, - ) - .first() - ) - if person is None: - # user is unknown, create new record - person = Person(username=user.id, display_name=user.display_name, api_client_id=self.api_client.id) - self.db.add(person) - self.db.commit() - self.db.refresh(person) - elif user.display_name and user.display_name != person.display_name: - # we found the user but the display name changed - person.display_name = user.display_name - self.db.add(person) - self.db.commit() - return person - - def validate_post_id(self, post_id: str) -> None: - if not isinstance(post_id, str): - raise TypeError(f"post_id must be string, not {type(post_id)}") - if not post_id: - raise ValueError("post_id must not be empty") - - def bind_frontend_post_id(self, task_id: UUID, post_id: str): - self.validate_post_id(post_id) - - # find work package - work_pack: WorkPackage = ( - self.db.query(WorkPackage) - .filter(WorkPackage.id == task_id, WorkPackage.api_client_id == self.api_client.id) - .first() - ) - if work_pack is None: - raise KeyError(f"WorkPackage for task {task_id} not found") - if work_pack.expiry_date is not None and datetime.utcnow() > work_pack.expiry_date: - raise RuntimeError("WorkPackage already expired.") - - # ToDo: check race-condition, transaction - - # check if task thread exits - thread_root = ( - self.db.query(Post) - .filter( - Post.workpackage_id == work_pack.id, - Post.frontend_post_id == post_id, - Post.parent_id is None, - Post.api_client_id == self.api_client.id, - ) - .one_or_none() - ) - if thread_root is None: - thread_id = uuid4() - thread_root = self.insert_post( - post_id=thread_id, - thread_id=thread_id, - frontend_post_id=post_id, - parent_id=None, - role="system", - workpackage_id=work_pack.id, - payload=None, - payload_type="bind", - ) - return thread_root - - def fetch_post_by_frontend_post_id(self, frontend_post_id: str, fail_if_missing: bool = True) -> Post: - self.validate_post_id(frontend_post_id) - post: Post = ( - self.db.query(Post) - .filter(Post.api_client_id == self.api_client.id, Post.frontend_post_id == frontend_post_id) - .one_or_none() - ) - if fail_if_missing and post is None: - raise KeyError(f"Post with post_id {frontend_post_id} not found.") - return post - - def fetch_workpackage_by_postid(self, post_id: str) -> WorkPackage: - self.validate_post_id(post_id) - post = self.fetch_post_by_frontend_post_id(post_id, fail_if_missing=True) - work_pack = self.db.query(WorkPackage).filter(WorkPackage.id == post.workpackage_id).one() - return work_pack - - def store_text_reply(self, reply: protocol_schema.TextReplyToPost, role: str) -> Post: - self.validate_post_id(reply.post_id) - self.validate_post_id(reply.user_post_id) - - # find post with post-id - parent_post: Post = ( - self.db.query(Post) - .filter( - Post.api_client_id == self.api_client.id, - Post.frontend_post_id == reply.post_id, - # Post.person_id == self.person_id - ) - .one_or_none() - ) - - if parent_post is None: - raise KeyError(f"Post for post_id {reply.post_id} not found.") - - # create reply post - user_post_id = uuid4() - user_post = self.insert_post( - post_id=user_post_id, - frontend_post_id=reply.user_post_id, - parent_id=parent_post.id, - thread_id=parent_post.thread_id, - workpackage_id=parent_post.workpackage_id, - role=role, - payload=db_payload.PostPayload(text=reply.text), - ) - return user_post - - def store_rating(self, rating: protocol_schema.PostRating) -> PostReaction: - post = self.fetch_post_by_frontend_post_id(rating.post_id, fail_if_missing=True) - - work_package = self.fetch_workpackage_by_postid(rating.post_id) - work_payload: db_payload.RateSummaryPayload = work_package.payload.payload - if type(work_payload) != db_payload.RateSummaryPayload: - raise ValueError( - f"work_package payload type mismatch: {type(work_payload)=} != {db_payload.RateSummaryPayload}" - ) - - if rating.rating < work_payload.scale.min or rating.rating > work_payload.scale.max: - raise ValueError(f"Invalid rating value: {rating.rating=} not in {work_payload.scale=}") - - # store reaction to post - reaction_payload = db_payload.RatingReactionPayload(rating=rating.rating) - reaction = self.insert_reaction(post.id, reaction_payload) - logger.info(f"Ranking {rating.rating} stored for work_package {work_package.id}.") - return reaction - - def store_ranking(self, ranking: protocol_schema.PostRanking) -> PostReaction: - post = self.fetch_post_by_frontend_post_id(ranking.post_id, fail_if_missing=True) - - # fetch work_package - work_package = self.fetch_workpackage_by_postid(ranking.post_id) - work_payload: db_payload.RankConversationRepliesPayload | db_payload.RankInitialPromptsPayload = ( - work_package.payload.payload - ) - - match type(work_payload): - - case db_payload.RankUserRepliesPayload | db_payload.RankAssistantRepliesPayload: - # validate ranking - num_replies = len(work_payload.replies) - if sorted(ranking.ranking) != list(range(num_replies)): - raise ValueError( - f"Invalid ranking submitted. Each reply index must appear exactly once ({num_replies=})." - ) - - # store reaction to post - reaction_payload = db_payload.RankingReactionPayload(ranking=ranking.ranking) - reaction = self.insert_reaction(post.id, reaction_payload) - - logger.info(f"Ranking {ranking.ranking} stored for work_package {work_package.id}.") - - return reaction - - case db_payload.RankInitialPromptsPayload: - # validate ranking - if sorted(ranking.ranking) != list(range(num_prompts := len(work_payload.prompts))): - raise ValueError( - f"Invalid ranking submitted. Each reply index must appear exactly once ({num_prompts=})." - ) - - # store reaction to post - reaction_payload = db_payload.RankingReactionPayload(ranking=ranking.ranking) - reaction = self.insert_reaction(post.id, reaction_payload) - - logger.info(f"Ranking {ranking.ranking} stored for work_package {work_package.id}.") - - return reaction - - case _: - raise ValueError( - f"work_package payload type mismatch: {type(work_payload)=} != {db_payload.RankConversationRepliesPayload}" - ) - - def store_task(self, task: protocol_schema.Task) -> WorkPackage: - payload: db_payload.TaskPayload - match type(task): - case protocol_schema.SummarizeStoryTask: - payload = db_payload.SummarizationStoryPayload(story=task.story) - - case protocol_schema.RateSummaryTask: - payload = db_payload.RateSummaryPayload( - full_text=task.full_text, summary=task.summary, scale=task.scale - ) - - case protocol_schema.InitialPromptTask: - payload = db_payload.InitialPromptPayload(hint=task.hint) - - case protocol_schema.UserReplyTask: - payload = db_payload.UserReplyPayload(conversation=task.conversation, hint=task.hint) - - case protocol_schema.AssistantReplyTask: - payload = db_payload.AssistantReplyPayload(type=task.type, conversation=task.conversation) - - case protocol_schema.RankInitialPromptsTask: - payload = db_payload.RankInitialPromptsPayload(tpye=task.type, prompts=task.prompts) - - case protocol_schema.RankUserRepliesTask: - payload = db_payload.RankUserRepliesPayload( - tpye=task.type, conversation=task.conversation, replies=task.replies - ) - - case protocol_schema.RankAssistantRepliesTask: - payload = db_payload.RankAssistantRepliesPayload( - tpye=task.type, conversation=task.conversation, replies=task.replies - ) - - case _: - raise ValueError(f"Invalid task type: {type(task)=}") - - wp = self.insert_work_package(payload=payload, id=task.id) - assert wp.id == task.id - return wp - - def insert_work_package(self, payload: db_payload.TaskPayload, id: UUID = None) -> WorkPackage: - c = PayloadContainer(payload=payload) - wp = WorkPackage( - id=id, - person_id=self.person_id, - payload_type=type(payload).__name__, - payload=c, - api_client_id=self.api_client.id, - ) - self.db.add(wp) - self.db.commit() - self.db.refresh(wp) - return wp - - def insert_post( - self, - *, - post_id: UUID, - frontend_post_id: str, - parent_id: UUID, - thread_id: UUID, - workpackage_id: UUID, - role: str, - payload: db_payload.PostPayload, - payload_type: str = None, - ) -> Post: - if payload_type is None: - if payload is None: - payload_type = "null" - else: - payload_type = type(payload).__name__ - - post = Post( - id=post_id, - parent_id=parent_id, - thread_id=thread_id, - workpackage_id=workpackage_id, - person_id=self.person_id, - role=role, - frontend_post_id=frontend_post_id, - api_client_id=self.api_client.id, - payload_type=payload_type, - payload=PayloadContainer(payload=payload), - ) - self.db.add(post) - self.db.commit() - self.db.refresh(post) - return post - - def insert_reaction(self, post_id: UUID, payload: db_payload.ReactionPayload) -> PostReaction: - if self.person_id is None: - raise ValueError("User required") - - container = PayloadContainer(payload=payload) - reaction = PostReaction( - post_id=post_id, - person_id=self.person_id, - payload=container, - api_client_id=self.api_client.id, - payload_type=type(payload).__name__, - ) - self.db.add(reaction) - self.db.commit() - self.db.refresh(reaction) - return reaction diff --git a/bot/README.md b/bot/README.md index 935cfb6c..fcc3bc33 100644 --- a/bot/README.md +++ b/bot/README.md @@ -1,6 +1,6 @@ -# open-chat-gpt +# open-assistant -This is the github repo for the open-chat-gpt project. +This is the github repo for the open-assistant project. We are currently building a discord bot in order to make everyone contribute with great prompts and answers. Join us! https://discord.gg/ZUfPw6jP diff --git a/bot/bot.py b/bot/bot.py index aff74b26..c2da5100 100644 --- a/bot/bot.py +++ b/bot/bot.py @@ -33,7 +33,7 @@ guild_ids = [TEST_GUILD, TEST_GUILD_LAION] # Initiate the client and command tree to create slash commands. -class OpenChatGPTClient(discord.Client): +class OpenAssistantClient(discord.Client): def __init__(self, *, intents: discord.Intents): super().__init__(intents=intents) self.tree = app_commands.CommandTree(self) @@ -59,7 +59,7 @@ class OpenChatGPTClient(discord.Client): # List the set of intents needed for commands to operate properly. intents = discord.Intents.default() intents.message_content = True -client = OpenChatGPTClient(intents=intents) +client = OpenAssistantClient(intents=intents) class LikeButton(discord.ui.Button): diff --git a/bot/setup.py b/bot/setup.py index 25df4d9a..06f7326e 100644 --- a/bot/setup.py +++ b/bot/setup.py @@ -12,11 +12,11 @@ if __name__ == "__main__": REQUIREMENTS = _read_reqs("requirements.txt") setup( - name="open-chat-gpt", + name="open-assistant", packages=find_packages(), version="0.0.1", license="Apache 2.0", - description="A Discord Bot for collecting and ranking prompts to train an Open ChatGPT", + description="A Discord Bot for collecting and ranking prompts to train an Open Assistant", keywords=["machine learning", "natural language processing", "discord"], install_requires=REQUIREMENTS, classifiers=[ diff --git a/scripts/frontend-development/docker-compose.yaml b/scripts/frontend-development/docker-compose.yaml index 8597726d..ad48b33a 100644 --- a/scripts/frontend-development/docker-compose.yaml +++ b/scripts/frontend-development/docker-compose.yaml @@ -16,7 +16,7 @@ services: service: adminer backend: build: ../../backend/. - image: ocgpt-backend + image: oasst-backend environment: - POSTGRES_HOST=db - ALLOW_ANY_API_KEY=True diff --git a/website/package.json b/website/package.json index aa06bb05..dd4a6bb3 100644 --- a/website/package.json +++ b/website/package.json @@ -1,6 +1,6 @@ { "name": "website", - "homepage": "http://projects.laion.ai.github.io/Open-Chat-GPT", + "homepage": "http://projects.laion.ai.github.io/Open-Assistant", "version": "0.1.0", "private": true, "scripts": { diff --git a/website/pages/index.js b/website/pages/index.js index d150d6c7..022ba1dc 100644 --- a/website/pages/index.js +++ b/website/pages/index.js @@ -20,9 +20,9 @@ export default function Home() {
{/* logo */} -

Open Chat Gpt

+

Open Assistant

- Open chat gpt is a project meant to give everyone access to a great + Open Assistant is a project meant to give everyone access to a great chat based large language model.

@@ -30,7 +30,7 @@ export default function Home() {

We believe that by doing this we will create a revolution in innovation in language. In the same way that stable-diffusion helped - the world make art and images in new ways we hope open chat gpt can + the world make art and images in new ways we hope Open Assistant can help improve the world by improving language itself.

@@ -62,7 +62,7 @@ export default function Home() {
{/* logo */} -

Open Chat Gpt

+

Open Assistant

You are logged in

diff --git a/website/public/index.html b/website/public/index.html index bc6ba7d3..e9d7030d 100644 --- a/website/public/index.html +++ b/website/public/index.html @@ -5,7 +5,7 @@ - + - Open Chat GPT + Open Assistant From 6b21b2359c15af3eea74d47496514c3404725d67 Mon Sep 17 00:00:00 2001 From: Yannic Kilcher Date: Sun, 18 Dec 2022 00:05:18 +0100 Subject: [PATCH 20/21] workflow fix --- .github/workflows/docker-build.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/docker-build.yaml b/.github/workflows/docker-build.yaml index 2a6f3bbf..5697d5b0 100644 --- a/.github/workflows/docker-build.yaml +++ b/.github/workflows/docker-build.yaml @@ -17,6 +17,9 @@ jobs: build: name: Build Images runs-on: ubuntu-latest + permissions: + contents: read + packages: write steps: - uses: actions/checkout@v3 - name: Set up Docker Buildx From 088effc7a9add380b3fd12f3a7f8fcb5e651d924 Mon Sep 17 00:00:00 2001 From: Yannic Kilcher Date: Sun, 18 Dec 2022 00:19:50 +0100 Subject: [PATCH 21/21] readme fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 72a4aac6..57899a61 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ All open source projects begins with people like you. Open source is the belief Work is organized in the [project board](https://github.com/orgs/LAION-AI/projects/3). - To get started with development, if you want to work on the backend, have a look at `scripts/backend-development/README.md`. -- If you want to work on the frontend, have a look at `scripts/frontend-development/README.md`. +- If you want to work on any frontend, have a look at `scripts/frontend-development/README.md` to make a backend available. There is also a minimal implementation of a frontend in the `text-frontend` folder.