Merge branch 'LAION-AI:main' into main

This commit is contained in:
mrcabbage972
2023-01-09 22:07:56 -05:00
committed by GitHub
37 changed files with 682 additions and 84 deletions
+3
View File
@@ -8,6 +8,9 @@ on:
- ".github/workflows/deploy-docs-site.yaml"
- "docs/**"
pull_request:
paths:
- ".github/workflows/deploy-docs-site.yaml"
- "docs/**"
jobs:
deploy:
+9
View File
@@ -29,6 +29,15 @@ jobs:
deploy-dev:
needs: [build-backend, build-web, build-bot]
runs-on: ubuntu-latest
env:
WEB_ADMIN_USERS: ${{ secrets.DEV_WEB_ADMIN_USERS }}
WEB_DISCORD_CLIENT_ID: ${{ secrets.DEV_WEB_DISCORD_CLIENT_ID }}
WEB_DISCORD_CLIENT_SECRET: ${{ secrets.DEV_WEB_DISCORD_CLIENT_SECRET }}
WEB_EMAIL_SERVER_HOST: ${{ secrets.DEV_WEB_EMAIL_SERVER_HOST }}
WEB_EMAIL_SERVER_PASSWORD: ${{ secrets.DEV_WEB_EMAIL_SERVER_PASSWORD }}
WEB_EMAIL_SERVER_PORT: ${{ secrets.DEV_WEB_EMAIL_SERVER_PORT }}
WEB_EMAIL_SERVER_USER: ${{ secrets.DEV_WEB_EMAIL_SERVER_USER }}
WEB_NEXTAUTH_SECRET: ${{ secrets.DEV_WEB_NEXTAUTH_SECRET }}
steps:
- name: Checkout
uses: actions/checkout@v2
+39
View File
@@ -0,0 +1,39 @@
name: E2E Tests (Website)
on:
push:
branches:
- main
paths:
- oasst-shared/**
- backend/**
- website/**
pull_request:
paths:
- oasst-shared/**
- backend/**
- website/**
jobs:
test-e2e:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Start website, backend, etc
run: docker compose up ci --build -d
- name: Run Cypress tests
uses: cypress-io/github-action@v5.0.2
with:
browser: chrome
working-directory: website
- uses: actions/upload-artifact@v3
if: failure() # NOTE: screenshots will be generated only if E2E test failed
with:
name: cypress-screenshots
path: website/cypress/screenshots
- uses: actions/upload-artifact@v3
if: always()
with:
name: cypress-videos
path: website/cypress/videos
+7
View File
@@ -0,0 +1,7 @@
To test the ansible playbook on localhost run
`ansible-playbook -i test.inventory.ini dev.yaml`.\
In case you're missing the ansible docker depencency install it with `ansible-galaxy collection install community.docker`.\
Point Redis Insights to the Redis database by visiting localhost:8001 in a
browser and select "I already have a database" followed by "Connect to a Redis
Database".\
For host, port and name fill in `oasst-redis`, `6379` and `redis`.
+54 -15
View File
@@ -10,6 +10,39 @@
state: present
driver: bridge
- name: Copy redis.conf to managed node
ansible.builtin.copy:
src: ./redis.conf
dest: ./redis.conf
- name: Set up Redis
community.docker.docker_container:
name: oasst-redis
image: redis
state: started
restart_policy: always
network_mode: oasst
ports:
- 6379:6379
healthcheck:
test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
interval: 2s
timeout: 2s
retries: 10
command: redis-server /usr/local/etc/redis/redis.conf
volumes:
- "./redis.conf:/usr/local/etc/redis/redis.conf"
- name: Set up Redis Insights
community.docker.docker_container:
name: oasst-redis-insights
image: redislabs/redisinsight:latest
state: started
restart_policy: always
network_mode: oasst
ports:
- 8001:8001
- name: Create postgres containers
community.docker.docker_container:
name: "{{ item.name }}"
@@ -32,14 +65,6 @@
- name: oasst-postgres
- name: oasst-postgres-web
- name: Set up maildev
community.docker.docker_container:
name: oasst-maildev
image: maildev/maildev
state: started
restart_policy: always
network_mode: oasst
- name: Run the oasst oasst-backend
community.docker.docker_container:
name: oasst-backend
@@ -51,10 +76,12 @@
network_mode: oasst
env:
POSTGRES_HOST: oasst-postgres
REDIS_HOST: oasst-redis
DEBUG_ALLOW_ANY_API_KEY: "true"
DEBUG_USE_SEED_DATA: "true"
MAX_WORKERS: "1"
RATE_LIMIT: "false"
DEBUG_SKIP_EMBEDDING_COMPUTATION: "true"
ports:
- 8080:8080
@@ -68,15 +95,27 @@
restart_policy: always
network_mode: oasst
env:
FASTAPI_URL: http://oasst-backend:8080
FASTAPI_KEY: "123"
ADMIN_USERS: "{{ lookup('ansible.builtin.env', 'WEB_ADMIN_USERS') }}"
DATABASE_URL: postgres://postgres:postgres@oasst-postgres-web/postgres
NEXTAUTH_SECRET: O/M2uIbGj+lDD2oyNa8ax4jEOJqCPJzO53UbWShmq98=
EMAIL_SERVER_HOST: oasst-maildev
EMAIL_SERVER_PORT: "25"
EMAIL_FROM: info@example.com
NEXTAUTH_URL: http://web.dev.open-assistant.io/
DEBUG_LOGIN: "true"
DISCORD_CLIENT_ID:
"{{ lookup('ansible.builtin.env', 'WEB_DISCORD_CLIENT_ID') }}"
DISCORD_CLIENT_SECRET:
"{{ lookup('ansible.builtin.env', 'WEB_DISCORD_CLIENT_SECRET') }}"
EMAIL_FROM: open-assistent@laion.ai
EMAIL_SERVER_HOST:
"{{ lookup('ansible.builtin.env', 'WEB_EMAIL_SERVER_HOST') }}"
EMAIL_SERVER_PASSWORD:
"{{ lookup('ansible.builtin.env', 'WEB_EMAIL_SERVER_PASSWORD') }}"
EMAIL_SERVER_PORT:
"{{ lookup('ansible.builtin.env', 'WEB_EMAIL_SERVER_PORT') }}"
EMAIL_SERVER_USER:
"{{ lookup('ansible.builtin.env', 'WEB_EMAIL_SERVER_USER') }}"
FASTAPI_URL: http://oasst-backend:8080
FASTAPI_KEY: "1234"
NEXTAUTH_SECRET:
"{{ lookup('ansible.builtin.env', 'WEB_NEXTAUTH_SECRET') }}"
NEXTAUTH_URL: http://web.dev.open-assistant.io/
ports:
- 3000:3000
command: bash wait-for-postgres.sh node server.js
+2
View File
@@ -0,0 +1,2 @@
maxmemory 100mb
maxmemory-policy allkeys-lru
+2
View File
@@ -0,0 +1,2 @@
[test]
dev ansible_connection=local
@@ -0,0 +1,27 @@
"""added miniLM_embedding column to message
Revision ID: 023548d474f7
Revises: ba61fe17fb6e
Create Date: 2023-01-08 11:06:25.613290
"""
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision = "023548d474f7"
down_revision = "ba61fe17fb6e"
branch_labels = None
depends_on = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column("message", sa.Column("miniLM_embedding", sa.ARRAY(sa.Float()), nullable=True))
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column("message", "miniLM_embedding")
# ### end Alembic commands ###
@@ -0,0 +1,49 @@
"""embedding for message now in its own table
Revision ID: 35bdc1a08bb8
Revises: 023548d474f7
Create Date: 2023-01-08 16:03:48.454207
"""
import sqlalchemy as sa
import sqlmodel
from alembic import op
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = "35bdc1a08bb8"
down_revision = "023548d474f7"
branch_labels = None
depends_on = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"message_embedding",
sa.Column("message_id", postgresql.UUID(as_uuid=True), nullable=False),
sa.Column("embedding", sa.ARRAY(sa.Float()), nullable=True),
sa.Column("model", sqlmodel.sql.sqltypes.AutoString(length=256), nullable=False),
sa.ForeignKeyConstraint(
["message_id"],
["message.id"],
),
sa.PrimaryKeyConstraint("message_id", "model"),
)
op.drop_column("message", "miniLM_embedding")
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column(
"message",
sa.Column(
"miniLM_embedding",
postgresql.ARRAY(postgresql.DOUBLE_PRECISION(precision=53)),
autoincrement=False,
nullable=True,
),
)
op.drop_table("message_embedding")
# ### end Alembic commands ###
@@ -0,0 +1,30 @@
"""Created date
Revision ID: aac6b2f66006
Revises: 35bdc1a08bb8
Create Date: 2023-01-08 21:28:27.342729
"""
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision = "aac6b2f66006"
down_revision = "35bdc1a08bb8"
branch_labels = None
depends_on = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column(
"message_embedding",
sa.Column("created_date", sa.DateTime(), server_default=sa.text("CURRENT_TIMESTAMP"), nullable=False),
)
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column("message_embedding", "created_date")
# ### end Alembic commands ###
+2 -7
View File
@@ -1,19 +1,14 @@
from enum import Enum
from typing import List
from fastapi import APIRouter, Depends
from oasst_backend.api import deps
from oasst_backend.models import ApiClient
from oasst_backend.schemas.hugging_face import ToxicityClassification
from oasst_backend.utils.hugging_face import HuggingFaceAPI
from oasst_backend.utils.hugging_face import HfUrl, HuggingFaceAPI
router = APIRouter()
class HF_url(str, Enum):
HUGGINGFACE_TOXIC_ROBERTA = "https://api-inference.huggingface.co/models/unitary/multilingual-toxic-xlm-roberta"
@router.get("/text_toxicity")
async def get_text_toxicity(
msg: str,
@@ -30,7 +25,7 @@ async def get_text_toxicity(
ToxicityClassification: the score of toxicity of the message.
"""
api_url: str = HF_url.HUGGINGFACE_TOXIC_ROBERTA.value
api_url: str = HfUrl.HUGGINGFACE_TOXIC_ROBERTA.value
hugging_face_api = HuggingFaceAPI(api_url)
response = await hugging_face_api.post(msg)
+18 -2
View File
@@ -7,7 +7,9 @@ from fastapi.security.api_key import APIKey
from loguru import logger
from oasst_backend.api import deps
from oasst_backend.api.v1.utils import prepare_conversation
from oasst_backend.config import settings
from oasst_backend.prompt_repository import PromptRepository, TaskRepository
from oasst_backend.utils.hugging_face import HfEmbeddingModel, HfUrl, HuggingFaceAPI
from oasst_shared.exceptions import OasstError, OasstErrorCode
from oasst_shared.schemas import protocol as protocol_schema
from sqlmodel import Session
@@ -253,7 +255,7 @@ def tasks_acknowledge_failure(
@router.post("/interaction", response_model=protocol_schema.TaskDone)
def tasks_interaction(
async def tasks_interaction(
*,
db: Session = Depends(deps.get_db),
api_key: APIKey = Depends(deps.get_api_key),
@@ -274,12 +276,26 @@ def tasks_interaction(
)
# here we store the text reply in the database
pr.store_text_reply(
newMessage = pr.store_text_reply(
text=interaction.text,
frontend_message_id=interaction.message_id,
user_frontend_message_id=interaction.user_message_id,
)
if not settings.DEBUG_SKIP_EMBEDDING_COMPUTATION:
try:
hugging_face_api = HuggingFaceAPI(
f"{HfUrl.HUGGINGFACE_FEATURE_EXTRACTION.value}/{HfEmbeddingModel.MINILM.value}"
)
embedding = await hugging_face_api.post(interaction.text)
pr.insert_message_embedding(
message_id=newMessage.id, model=HfEmbeddingModel.MINILM.value, embedding=embedding
)
except OasstError:
logger.error(
f"Could not fetch embbeddings for text reply to {interaction.message_id=} with {interaction.text=} by {interaction.user=}."
)
return protocol_schema.TaskDone()
case protocol_schema.MessageRating:
logger.info(
+1
View File
@@ -25,6 +25,7 @@ class Settings(BaseSettings):
DEBUG_USE_SEED_DATA_PATH: Optional[FilePath] = (
Path(__file__).parent.parent / "test_data/generic/test_generic_data.json"
)
DEBUG_SKIP_EMBEDDING_COMPUTATION: bool = False
HUGGING_FACE_API_KEY: str = ""
+2
View File
@@ -1,6 +1,7 @@
from .api_client import ApiClient
from .journal import Journal, JournalIntegration
from .message import Message
from .message_embedding import MessageEmbedding
from .message_reaction import MessageReaction
from .message_tree_state import MessageTreeState
from .task import Task
@@ -13,6 +14,7 @@ __all__ = [
"User",
"UserStats",
"Message",
"MessageEmbedding",
"MessageReaction",
"MessageTreeState",
"Task",
@@ -0,0 +1,21 @@
from datetime import datetime
from typing import List, Optional
from uuid import UUID
import sqlalchemy as sa
import sqlalchemy.dialects.postgresql as pg
from sqlmodel import ARRAY, Field, Float, SQLModel
class MessageEmbedding(SQLModel, table=True):
__tablename__ = "message_embedding"
__table_args__ = (sa.PrimaryKeyConstraint("message_id", "model"),)
message_id: UUID = Field(sa_column=sa.Column(pg.UUID(as_uuid=True), sa.ForeignKey("message.id"), nullable=False))
model: str = Field(max_length=256, nullable=False)
embedding: List[float] = Field(sa_column=sa.Column(ARRAY(Float)), nullable=True)
# In the case that the Message Embedding is created afterwards
created_date: Optional[datetime] = Field(
sa_column=sa.Column(sa.DateTime(), nullable=False, server_default=sa.func.current_timestamp())
)
+1 -1
View File
@@ -35,4 +35,4 @@ class Task(SQLModel, table=True):
@property
def expired(self) -> bool:
return self.expiry_date is not None and datetime.utcnow() < self.expiry_date
return self.expiry_date is not None and datetime.utcnow() > self.expiry_date
+32 -3
View File
@@ -2,13 +2,13 @@ import datetime
import random
from collections import defaultdict
from http import HTTPStatus
from typing import Optional
from typing import List, Optional
from uuid import UUID, uuid4
import oasst_backend.models.db_payload as db_payload
from loguru import logger
from oasst_backend.journal_writer import JournalWriter
from oasst_backend.models import ApiClient, Message, MessageReaction, TextLabels, User
from oasst_backend.models import ApiClient, Message, MessageEmbedding, MessageReaction, TextLabels, User
from oasst_backend.models.payload_column_type import PayloadContainer
from oasst_backend.task_repository import TaskRepository, validate_frontend_message_id
from oasst_backend.user_repository import UserRepository
@@ -91,7 +91,12 @@ class PromptRepository:
self.db.refresh(message)
return message
def store_text_reply(self, text: str, frontend_message_id: str, user_frontend_message_id: str) -> Message:
def store_text_reply(
self,
text: str,
frontend_message_id: str,
user_frontend_message_id: str,
) -> Message:
validate_frontend_message_id(frontend_message_id)
validate_frontend_message_id(user_frontend_message_id)
@@ -224,6 +229,30 @@ class PromptRepository:
OasstErrorCode.TASK_PAYLOAD_TYPE_MISMATCH,
)
def insert_message_embedding(self, message_id: UUID, model: str, embedding: List[float]) -> MessageEmbedding:
"""Insert the embedding of a new message in the database.
Args:
message_id (UUID): the identifier of the message we want to save its embedding
model (str): the model used for creating the embedding
embedding (List[float]): the values obtained from the message & model
Raises:
OasstError: if misses some of the before params
Returns:
MessageEmbedding: the instance in the database of the embedding saved for that message
"""
if None in (message_id, model, embedding):
raise OasstError("Paramters missing to add embedding", OasstErrorCode.GENERIC_ERROR)
message_embedding = MessageEmbedding(message_id=message_id, model=model, embedding=embedding)
self.db.add(message_embedding)
self.db.commit()
self.db.refresh(message_embedding)
return message_embedding
def insert_reaction(self, task_id: UUID, payload: db_payload.ReactionPayload) -> MessageReaction:
if self.user_id is None:
raise OasstError("User required", OasstErrorCode.USER_NOT_SPECIFIED)
@@ -1,10 +1,21 @@
from enum import Enum
from typing import Any, Dict
import aiohttp
from loguru import logger
from oasst_backend.config import settings
from oasst_shared.exceptions import OasstError, OasstErrorCode
class HfUrl(str, Enum):
HUGGINGFACE_TOXIC_ROBERTA = ("https://api-inference.huggingface.co/models/unitary/multilingual-toxic-xlm-roberta",)
HUGGINGFACE_FEATURE_EXTRACTION = "https://api-inference.huggingface.co/pipeline/feature-extraction"
class HfEmbeddingModel(str, Enum):
MINILM = "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"
class HuggingFaceAPI:
"""Class Object to make post calls to endpoints for inference in models hosted in HuggingFace"""
@@ -41,6 +52,9 @@ class HuggingFaceAPI:
async with session.post(self.api_url, headers=self.headers, json=payload) as response:
# If we get a bad response
if response.status != 200:
logger.error(response)
logger.info(self.headers)
raise OasstError(
"Response Error Detoxify HuggingFace", error_code=OasstErrorCode.HUGGINGFACE_API_ERROR
)
+6
View File
@@ -11,6 +11,11 @@ services:
image: sverrirab/sleep
depends_on: [db, webdb, adminer, maildev, backend, redis]
# Used by CI automations.
ci:
image: sverrirab/sleep
depends_on: [db, webdb, maildev, backend, redis, web]
# This DB is for the FastAPI Backend.
db:
image: postgres
@@ -95,6 +100,7 @@ services:
- DEBUG_SKIP_API_KEY_CHECK=True
- DEBUG_USE_SEED_DATA=True
- MAX_WORKERS=1
- DEBUG_SKIP_EMBEDDING_COMPUTATION=True
depends_on:
db:
condition: service_healthy
+8 -4
View File
@@ -1,10 +1,14 @@
# Notebooks
This is a folders with some useful notebooks, all the notebooks have a markdown
file with the same name explaining what they do.
This is a folder with some useful notebooks, all the notebooks have a markdown
file with the same name (or a README.md if its a single notebook folder)
explaining what they do.
## Contributing
Contributing to both notebooks and making new notebooks is very welcome. If you
do so, make sure to make a markdown (.md) file to go with your notebook, makes
Contributing to notebooks and making new notebooks is very welcome. If you do
so, make sure to make a markdown (.md) file to go with your notebook, it makes
it easier for people to know what your notebook is about.
Check out the [example notebook](example/) for a reference example you can use
as a starting point.
+48
View File
@@ -0,0 +1,48 @@
# Example Notebook
[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/LAION-AI/Open-Assistant/blob/main/notebooks/example/example.ipynb)
This folder contains an example reference notebook structure and approach for
this project. Please try and follow this structure as closely as possible. While
things will not exactly be the same for each notebook some principles we would
like to try ensure are:
1. Each notebook or collection of related or dependant notebooks should live in
its own folder.
1. Each notebook should have a markdown file with the same name as the notebook
(or README.md if it's a single notebook folder) that explains what the
notebook does and how to use it.
1. Add an "Open in Colab" badge to the top of the notebook (see the markdown
cell near the top of `example.ipynb` as an example you can adapt).
1. Make it as easy as possible for someone to run the notebook in Google Colab
or some other environment based on standard practices like providing a
`requirements.txt` file or anything else needed to successfully run the
notebook.
## Running in Google Colab
At the top of the [example notebook](example.ipynb) there is a code cell that
will (once uncommented):
1. clone the repository into your colab instance.
1. `cd` into the relevant notebook directory.
1. run `pip install -r requirements.txt` to install the required packages.
At this point you can run the notebook as normal and the folder structure will
match that of the repository and the colab notebook will be running from the
same directory that the notebook lives in so relative links etc should work as
expected (for example `example.ipynb` will read some sample data from
`data/data.csv`).
If you are adding a notebook please try and add a similar cell to the top of the
notebook so that it is easy for others to run the notebook in colab. If your
notebook does not have any dependencies beyond what already comes as standard in
Google Colab then you do not need such a cell, just an "Open in Colab" badge
will suffice.
## example.ipynb
This notebook contains an example "Open In Colab" badge and a code cell to
prepare the colab environment to run the notebook. It also contains a code cell
that will read in some sample data from the `data` folder in the repository and
display it as a pandas dataframe.
+3
View File
@@ -0,0 +1,3 @@
row,text,label
1,some example data,1
2,some more data,0
1 row text label
2 1 some example data 1
3 2 some more data 0
+161
View File
@@ -0,0 +1,161 @@
{
"cells": [
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"# Example Notebook"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/LAION-AI/Open-Assistant/blob/example-notebook/notebooks/example/example.ipynb)"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"# uncomment and run below lines to set up if running in colab\n",
"# !git clone https://github.com/LAION-AI/Open-Assistant.git\n",
"# %cd Open-Assistant/notebooks/example\n",
"# !pip install -r requirements.txt"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"Try to add a markdown section to the notebook that explains what the notebook is about and what it does. This will help people understand what the notebook is for and how to use it."
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"# import required packages\n",
"import pandas as pd\n",
"from transformers import pipeline"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"## Use Headings\n",
"\n",
"(it will help with link sharing to specific sections of the notebook)"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"Make fancy markdown cells if you want."
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>row</th>\n",
" <th>text</th>\n",
" <th>label</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>1</td>\n",
" <td>some example data</td>\n",
" <td>1</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>2</td>\n",
" <td>some more data</td>\n",
" <td>0</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" row text label\n",
"0 1 some example data 1\n",
"1 2 some more data 0"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Do cool stuff here\n",
"df = pd.read_csv(\"data/data.csv\")\n",
"df.head()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.4 (tags/v3.7.4:e09359112e, Jul 8 2019, 20:34:20) [MSC v.1916 64 bit (AMD64)]"
},
"orig_nbformat": 4,
"vscode": {
"interpreter": {
"hash": "25d5c2324055587ceaeef27650c79ce8358ea61d7689f2e0b8ada5d53f85bce4"
}
}
},
"nbformat": 4,
"nbformat_minor": 2
}
+1
View File
@@ -0,0 +1 @@
transformers
+1
View File
@@ -6,6 +6,7 @@ pushd "$parent_path/../../backend"
export DEBUG_SKIP_API_KEY_CHECK=True
export DEBUG_USE_SEED_DATA=True
export DEBUG_SKIP_EMBEDDING_COMPUTATION=True
uvicorn main:app --reload --port 8080 --host 0.0.0.0
+2
View File
@@ -39,5 +39,7 @@ next-env.d.ts
*.swp
# cypress
/cypress/screenshots
/cypress/videos
/cypress-visual-screenshots/diff
/cypress-visual-screenshots/comparison
+28
View File
@@ -153,6 +153,34 @@ When writing code for the website, we have a few best practices:
1. Define functional React components (with types for all properties when
feasible).
### Developing New Features
When working on new features or making significant changes that can't be done
within a single Pull Request, we ask that you make use of Feature Flags.
We've set up
[`react-feature-flags`](https://www.npmjs.com/package/react-feature-flags) to
make this easier. To get started:
1. Add a new flag entry to `website/src/flags.ts`. We have an example flag you
can copy as an example. Be sure to `isActive` to true when testing your
features but false when submitting your PR.
1. Use your flag wherever you add a new UI element. This can be done with:
```js
import { Flags } from "react-feature-flags";
...
<Flags authorizedFlags={["yourFlagName"]}>
<YourNewComponent />
</Flags>
```
You can see an example of how this works by checking `website/src/components/Header/Headers.tsx` where we use `flagTest`.
1. Once you've finished building out the feature and it is ready for everyone
to use, it's safe to remove the `Flag` wrappers around your component and
the entry in `flags.ts`.
### URL Paths
To use stable and consistent URL paths, we recommend the following strategy for
+17
View File
@@ -39,6 +39,7 @@
"postcss-focus-visible": "^7.1.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-feature-flags": "^1.0.0",
"react-icons": "^4.7.1",
"swr": "^2.0.0",
"tailwindcss": "^3.2.4",
@@ -26424,6 +26425,16 @@
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz",
"integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA=="
},
"node_modules/react-feature-flags": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/react-feature-flags/-/react-feature-flags-1.0.0.tgz",
"integrity": "sha512-KBFUkXjF7ifGWEQr2Ida4LdAtKGDOwFdTRlXipWxGP9a43vUBxP6IscpYQofGjlzlBcgmFKuzubcVheB6NliEg==",
"peerDependencies": {
"prop-types": "^15.5.4",
"react": ">= 16.3.0",
"react-dom": ">= 16.3.0"
}
},
"node_modules/react-focus-lock": {
"version": "2.9.2",
"resolved": "https://registry.npmjs.org/react-focus-lock/-/react-focus-lock-2.9.2.tgz",
@@ -50838,6 +50849,12 @@
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz",
"integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA=="
},
"react-feature-flags": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/react-feature-flags/-/react-feature-flags-1.0.0.tgz",
"integrity": "sha512-KBFUkXjF7ifGWEQr2Ida4LdAtKGDOwFdTRlXipWxGP9a43vUBxP6IscpYQofGjlzlBcgmFKuzubcVheB6NliEg==",
"requires": {}
},
"react-focus-lock": {
"version": "2.9.2",
"resolved": "https://registry.npmjs.org/react-focus-lock/-/react-focus-lock-2.9.2.tgz",
+1
View File
@@ -53,6 +53,7 @@
"postcss-focus-visible": "^7.1.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-feature-flags": "^1.0.0",
"react-icons": "^4.7.1",
"swr": "^2.0.0",
"tailwindcss": "^3.2.4",
+14 -45
View File
@@ -27,8 +27,22 @@ import poster from "src/lib/poster";
import { colors } from "styles/Theme/colors";
import useSWRMutation from "swr/mutation";
interface textFlagLabels {
attributeName: string;
labelText: string;
additionalExplanation?: string;
}
export const FlaggableElement = (props) => {
const [isEditing, setIsEditing] = useBoolean();
const flaggable_labels = props.flaggable_labels;
const TEXT_LABEL_FLAGS = flaggable_labels.valid_labels.map((valid_label) => {
return {
attributeName: valid_label.name,
labelText: valid_label.display_text,
additionalExplanation: valid_label.help_text,
};
});
const { trigger } = useSWRMutation("/api/set_label", poster, {
onSuccess: () => {
setIsEditing.off;
@@ -181,48 +195,3 @@ export function FlagCheckbox(props: {
</Flex>
);
}
interface textFlagLabels {
attributeName: string;
labelText: string;
additionalExplanation?: string;
}
const TEXT_LABEL_FLAGS: textFlagLabels[] = [
// For the time being this list is configured on the FE.
// In the future it may be provided by the API.
// {
// attributeName: "fails_task",
// labelText: "Fails to follow the correct instruction / task",
// additionalExplanation: "__TODO__",
// },
// {
// attributeName: "not_customer_assistant_appropriate",
// labelText: "Inappropriate for customer assistant",
// additionalExplanation: "__TODO__",
// },
{
attributeName: "sexual_content",
labelText: "Contains sexual content",
},
{
attributeName: "violence",
labelText: "Contains violent content",
},
// {
// attributeName: "encourages_violence",
// labelText: "Encourages or fails to discourage violence/abuse/terrorism/self-harm",
// },
// {
// attributeName: "denigrates_a_protected_class",
// labelText: "Denigrates a protected class",
// },
// {
// attributeName: "gives_harmful_advice",
// labelText: "Fails to follow the correct instruction / task",
// additionalExplanation:
// "The advice given in the output is harmful or counter-productive. This may be in addition to, but is distinct from the question about encouraging violence/abuse/terrorism/self-harm.",
// },
// {
// attributeName: "expresses_moral_judgement",
// labelText: "Expresses moral judgement",
// },
];
+4
View File
@@ -2,6 +2,7 @@ import { Box, Button, Text, useColorMode } from "@chakra-ui/react";
import Image from "next/image";
import Link from "next/link";
import { useSession } from "next-auth/react";
import { Flags } from "react-feature-flags";
import { FaUser } from "react-icons/fa";
import { UserMenu } from "./UserMenu";
@@ -42,6 +43,9 @@ export function Header(props) {
</Link>
</div>
<div className="flex items-center gap-4">
<Flags authorizedFlags={["flagTest"]}>
<div>FlagTest</div>
</Flags>
<AccountButton />
<UserMenu />
</div>
+22 -2
View File
@@ -10,11 +10,31 @@ export interface Message {
message_id: string;
}
export const Messages = ({ messages, post_id }: { messages: Message[]; post_id: string }) => {
export interface ValidLabel {
name: string;
display_text: string;
help_text: string;
}
export const Messages = ({
messages,
post_id,
valid_labels,
}: {
messages: Message[];
post_id: string;
valid_labels: ValidLabel[];
}) => {
const items = messages.map((messageProps: Message, i: number) => {
const { message_id, text } = messageProps;
return (
<FlaggableElement text={text} post_id={post_id} message_id={message_id} key={i + text}>
<FlaggableElement
text={text}
post_id={post_id}
message_id={message_id}
key={i + text}
flaggable_labels={valid_labels}
>
<MessageView {...messageProps} />
</FlaggableElement>
);
+4 -2
View File
@@ -18,7 +18,7 @@ export interface CreateTaskProps {
}
export const CreateTask = ({ tasks, taskType, trigger, onSkipTask, onNextTask, mainBgClasses }: CreateTaskProps) => {
const task = tasks[0].task;
const valid_labels = tasks[0].valid_labels;
const [inputText, setInputText] = useState("");
const submitResponse = (task: { id: string }) => {
@@ -42,7 +42,9 @@ export const CreateTask = ({ tasks, taskType, trigger, onSkipTask, onNextTask, m
<>
<h5 className="text-lg font-semibold">{taskType.label}</h5>
<p className="text-lg py-1">{taskType.overview}</p>
{task.conversation ? <Messages messages={task.conversation.messages} post_id={task.id} /> : null}
{task.conversation ? (
<Messages messages={task.conversation.messages} post_id={task.id} valid_labels={valid_labels} />
) : null}
</>
<>
<h5 className="text-lg font-semibold">{taskType.instruction}</h5>
+3
View File
@@ -0,0 +1,3 @@
const flags = [{ name: "flagTest", isActive: false }];
export default flags;
+33
View File
@@ -48,6 +48,33 @@ export class OasstApiClient {
return await resp.json();
}
private async get(path: string): Promise<any> {
const resp = await fetch(`${this.oasstApiUrl}${path}`, {
method: "GET",
headers: {
"X-API-Key": this.oasstApiKey,
"Content-Type": "application/json",
},
});
if (resp.status == 204) {
return null;
}
if (resp.status >= 300) {
const errorText = await resp.text();
let error: any;
try {
error = JSON.parse(errorText);
} catch (e) {
throw new OasstError(errorText, 0, resp.status);
}
throw new OasstError(error.message ?? error, error.error_code, resp.status);
}
return await resp.json();
}
// TODO return a strongly typed Task?
// This method is used to store a task in RegisteredTask.task.
// This is a raw Json type, so we can't use it to strongly type the task.
@@ -96,6 +123,12 @@ export class OasstApiClient {
...content,
});
}
//Fetch valid labels. This is called every task. though the call may be redundant
//keeping this for future where the valid labels may change per task
async fetch_valid_text(): Promise<void> {
return this.get(`/api/v1/text_labels/valid_labels`);
}
}
export const oasstApiClient =
+7 -3
View File
@@ -3,7 +3,9 @@ import "focus-visible";
import type { AppProps } from "next/app";
import { SessionProvider } from "next-auth/react";
import { FlagsProvider } from "react-feature-flags";
import { getDefaultLayout, NextPageWithLayout } from "src/components/Layout";
import flags from "src/flags";
import { Chakra, getServerSideProps } from "../styles/Chakra";
@@ -16,9 +18,11 @@ function MyApp({ Component, pageProps: { session, cookies, ...pageProps } }: App
const page = getLayout(<Component {...pageProps} />);
return (
<Chakra cookies={cookies}>
<SessionProvider session={session}>{page}</SessionProvider>
</Chakra>
<FlagsProvider value={flags}>
<Chakra cookies={cookies}>
<SessionProvider session={session}>{page}</SessionProvider>
</Chakra>
</FlagsProvider>
);
}
export { getServerSideProps };
@@ -23,6 +23,7 @@ const handler = async (req, res) => {
// Fetch the new task.
const task = await oasstApiClient.fetchTask(task_type, token);
const valid_labels = await oasstApiClient.fetch_valid_text();
// Store the task and link it to the user..
const registeredTask = await prisma.registeredTask.create({
@@ -36,6 +37,11 @@ const handler = async (req, res) => {
},
});
// Add the valid labels that can be used to flag messages in this Task
registeredTask["valid_labels"] = valid_labels;
// Update the backend with our Task ID
await oasstApiClient.ackTask(task.id, registeredTask.id);
// Send the results to the client.
res.status(200).json(registeredTask);
};