Merge branch 'LAION-AI:main' into messageNavigation

This commit is contained in:
jojopirker
2023-01-06 16:43:50 +01:00
committed by GitHub
48 changed files with 2870 additions and 224 deletions
+9 -1
View File
@@ -15,6 +15,9 @@ jobs:
- uses: actions/setup-python@v4
with:
python-version: "3.10"
- uses: actions/setup-node@v3
with:
node-version: 16
- run: cd oasst-shared && pip install -e .
@@ -22,9 +25,14 @@ jobs:
- run: cd backend && pip install -r requirements.txt
- run: cd website && npm ci
- run: ./scripts/backend-development/start-mock-server.sh
- name: Run contract tests
- name: Run Python OasstApiClient contract tests
run: ./scripts/oasst-shared-development/test.sh
- name: Run JavaScript OasstApiClient contract tests
run: ./scripts/frontend-development/run-contract-test.sh
- run: ./scripts/backend-development/stop-mock-server.sh
+110
View File
@@ -0,0 +1,110 @@
# Im in! Now what?
[Join the OpenAssistant Contributors Discord Server!](https://ykilcher.com/open-assistant-discord),
this is for work coordination.
[Join the LAION Discord Server!](https://discord.com/invite/mVcgxMPD7e), it has
a dedicated channel and is more public.
[and / or the YK Discord Server](https://ykilcher.com/discord), also has a
dedicated, but not as active, channel.
[Visit the Notion](https://ykilcher.com/open-assistant)
### Taking on Tasks
We have a growing task list
[of issues](https://github.com/LAION-AI/Open-Assistant/issues). Find an issue
that appeals to you and make a comment that you'd like to work on it. Include in
your comment a brief description of how you'll solve the problem and if there
are any open questions you want to discuss. Once a project coordinator has
assigned the issue to you, start working on it.
If the issue is currently unclear but you are interested, please post in Discord
and someone can help clarify the issue with more detail.
**Always Welcome:** Documentation markdowns in `docs/`, docstrings, diagrams of
the system architecture, and other documentation.
### Submitting Work
We're all working on different parts of Open Assistant together. To make
contributions smoothly we recommend the following:
1. [Fork this project repository](https://docs.github.com/en/get-started/quickstart/fork-a-repo)
and clone it to your local machine. (Read more
[About Forks](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/about-forks))
1. Before working on any changes, try to
[sync the forked repository](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork)
to keep it up-to-date with the upstream repository.
1. Work on a small focused change that only touches on a few files.
1. Run `pre-commit` and make sure all files have formatting fixed. This
simplifies life for reviewers.
1. Package up a small bit of work that solves part of the problem
[into a Pull Request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork)
and
[send it out for review](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/requesting-a-pull-request-review).
1. If you're lucky, we can merge your change into `main` without any problems.
If there's changes to files you're working on, resolve them by:
1. First try rebase as suggested
[in these instructions](https://timwise.co.uk/2019/10/14/merge-vs-rebase/#should-you-rebase).
1. If rebase feels too painful, merge as suggested
[in these instructions](https://timwise.co.uk/2019/10/14/merge-vs-rebase/#should-you-merge).
1. Once you've resolved any conflicts, finish the review and merge into `main`.
1. Merge in your change and move onto a new issue or the second step of your
current issue.
Additionally, if someone is working on an issue that interests you, ask if they
need help on it or would like suggestions on how to approach the issue. If so,
share wildly. If they seem to have a good handle on it, let them work on their
solution until a challenge comes up.
### When does a review finish
A review finishes when all blocking comments are addressed and at least one
owning reviewer has approved the PR. Be sure to acknowledge any non-blocking
comments either by making the request change, explaining why it's not being
addressed now, or filing an issue to handle it later.
## Developer Setup
Work is organized in the
[project board](https://github.com/orgs/LAION-AI/projects/3).
**Anything that is in the `Todo` column and not assigned, is up for grabs.
Meaning we'd be happy for anyone to do these tasks.**
If you want to work on something, assign yourself to it or write a comment that
you want to work on it and what you plan to do.
- 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 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.
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)
### Website
The website is built using Next.js and is in the `website` folder.
### Pre-commit
Install `pre-commit` and run `pre-commit install` to install the pre-commit
hooks.
In case you haven't done this, have already committed, and CI is failing, you
can run `pre-commit run --all-files` to run the pre-commit hooks on all files.
### Deployment
Upon making a release on GitHub, all docker images are automatically built and
pushed to ghcr.io. The docker images are tagged with the release version, and
the `latest` tag. Further, the ansible playbook in `ansible/dev.yaml` is run to
automatically deploy the built release to the dev machine.
+43 -126
View File
@@ -1,7 +1,34 @@
# Open-Assistant
<h1 align="center">
<span>Open-Assistant</span>
<img width="auto" height="50px" src="https://github.com/LAION-AI/Open-Assistant/blob/main/assets/logo_crop.png"/>
</h1>
Open Assistant is a project meant to give everyone access to a great chat based
large language model.
<div align="center">
<a href="">![GitHub Repo stars](https://img.shields.io/github/stars/LAION-AI/Open-Assistant?style=social)</a>
<a href="">![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/LAION-AI/Open-Assistant/build-frontend.yaml?label=frontend)</a>
<a href="">![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/LAION-AI/Open-Assistant/pre-commit.yaml?label=pre-commit)</a>
<a href="">![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/LAION-AI/Open-Assistant/test-api-contract.yaml?label=api)</a>
<a href="">![GitHub release (latest by date)](https://img.shields.io/github/v/release/LAION-AI/Open-Assistant)</a>
</div>
# Table of Contents
- [What is Open Assistant?](#what-is-open-assistant)
- [Do you want to try it out?](#do-you-want-to-try-it-out)
- [The Plan](#the-plan)
- [The Vision](#the-vision)
- [How can you help?](#how-can-you-help)
- [Im in! How do I contribute?](CONTRIBUTING.md)
---
## What is Open Assistant?
<p align="center">
Open Assistant is a project meant to give everyone access to a great chat based large language model.
</p>
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
@@ -14,7 +41,7 @@ If you are interested in taking a look at the current state of the project, you
can set up an entire stack needed to run **Open-Assistant**, including the
website, backend, and associated dependent services.
To start the demo, run this in the root directory of the repository:
##### To start the demo, run this in the root directory of the repository:
```sh
docker compose up --build
@@ -23,22 +50,21 @@ docker compose up --build
Then, navigate to `http://localhost:3000` (It may take some time to boot up) and
interact with the website.
**Note:** When logging in via email, navigate to `http://localhost:1080` to get
the magic email login link.
> **Note:** When logging in via email, navigate to `http://localhost:1080` to
> get the magic email login link.
**Note:** If you would like to run this in a standardized development
environment (a
["devcontainer"](https://code.visualstudio.com/docs/devcontainers/containers))
using
[vscode locally](https://code.visualstudio.com/docs/devcontainers/create-dev-container#_create-a-devcontainerjson-file)
or in a web browser using
[GitHub Codespaces](https://github.com/features/codespaces), you can use the
provided [`.devcontainer`](.devcontainer/) folder.
> **Note:** If you would like to run this in a standardized development
> environment (a
> ["devcontainer"](https://code.visualstudio.com/docs/devcontainers/containers))
> using
> [vscode locally](https://code.visualstudio.com/docs/devcontainers/create-dev-container#_create-a-devcontainerjson-file)
> or in a web browser using
> [GitHub Codespaces](https://github.com/features/codespaces), you can use the
> provided [`.devcontainer`](.devcontainer/) folder.
## The Plan
We want to get to an initial MVP as fast as possible, by following the 3-steps
outlined in the InstructGPT paper.
##### We want to get to an initial MVP as fast as possible, by following the 3-steps outlined in the InstructGPT paper.
1. Collect high-quality human generated Instruction-Fulfillment samples
(prompt + response), goal >50k. We design a crowdsourced process to collect
@@ -80,113 +106,4 @@ All open source projects begin with people like you. Open source is the belief
that if we collaborate we can together gift our knowledge and technology to the
world for the benefit of humanity.
## Im in! Now what?
[Join the OpenAssistant Contributors Discord Server!](https://ykilcher.com/open-assistant-discord),
this is for work coordination.
[Join the LAION Discord Server!](https://discord.com/invite/mVcgxMPD7e), it has
a dedicated channel and is more public.
[and / or the YK Discord Server](https://ykilcher.com/discord), also has a
dedicated, but not as active, channel.
[Visit the Notion](https://ykilcher.com/open-assistant)
### Taking on Tasks
We have a growing task list
[of issues](https://github.com/LAION-AI/Open-Assistant/issues). Find an issue
that appeals to you and make a comment that you'd like to work on it. Include in
your comment a brief description of how you'll solve the problem and if there
are any open questions you want to discuss. Once a project coordinator has
assigned the issue to you, start working on it.
If the issue is currently unclear but you are interested, please post in Discord
and someone can help clarify the issue with more detail.
**Always Welcome:** Documentation markdowns in `docs/`, docstrings, diagrams of
the system architecture, and other documentation.
### Submitting Work
We're all working on different parts of Open Assistant together. To make
contributions smoothly we recommend the following:
1. [Fork this project repository](https://docs.github.com/en/get-started/quickstart/fork-a-repo)
and clone it to your local machine. (Read more
[About Forks](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/about-forks))
1. Before working on any changes, try to
[sync the forked repository](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork)
to keep it up-to-date with the upstream repository.
1. Work on a small focused change that only touches on a few files.
1. Run `pre-commit` and make sure all files have formatting fixed. This
simplifies life for reviewers.
1. Package up a small bit of work that solves part of the problem
[into a Pull Request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork)
and
[send it out for review](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/requesting-a-pull-request-review).
1. If you're lucky, we can merge your change into `main` without any problems.
If there's changes to files you're working on, resolve them by:
1. First try rebase as suggested
[in these instructions](https://timwise.co.uk/2019/10/14/merge-vs-rebase/#should-you-rebase).
1. If rebase feels too painful, merge as suggested
[in these instructions](https://timwise.co.uk/2019/10/14/merge-vs-rebase/#should-you-merge).
1. Once you've resolved any conflicts, finish the review and merge into `main`.
1. Merge in your change and move onto a new issue or the second step of your
current issue.
Additionally, if someone is working on an issue that interests you, ask if they
need help on it or would like suggestions on how to approach the issue. If so,
share wildly. If they seem to have a good handle on it, let them work on their
solution until a challenge comes up.
### When does a review finish
A review finishes when all blocking comments are addressed and at least one
owning reviewer has approved the PR. Be sure to acknowledge any non-blocking
comments either by making the request change, explaining why it's not being
addressed now, or filing an issue to handle it later.
## Developer Setup
Work is organized in the
[project board](https://github.com/orgs/LAION-AI/projects/3).
**Anything that is in the `Todo` column and not assigned, is up for grabs.
Meaning we'd be happy for anyone to do these tasks.**
If you want to work on something, assign yourself to it or write a comment that
you want to work on it and what you plan to do.
- 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 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.
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)
### Website
The website is built using Next.js and is in the `website` folder.
### Pre-commit
Install `pre-commit` and run `pre-commit install` to install the pre-commit
hooks.
In case you haven't done this, have already committed, and CI is failing, you
can run `pre-commit run --all-files` to run the pre-commit hooks on all files.
### Deployment
Upon making a release on GitHub, all docker images are automatically built and
pushed to ghcr.io. The docker images are tagged with the release version, and
the `latest` tag. Further, the ansible playbook in `ansible/dev.yaml` is run to
automatically deploy the built release to the dev machine.
Check out our [contributing guide](CONTRIBUTING.md) to get started.
Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

+1
View File
@@ -1,3 +1,4 @@
__pycache__
.env
notes.txt
venv
@@ -0,0 +1,46 @@
"""added MessageTreeState table
Revision ID: d4161e384f83
Revises: 8d269bc4fdbd
Create Date: 2023-01-05 11:44:02.630633
"""
import sqlalchemy as sa
import sqlmodel
from alembic import op
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = "d4161e384f83"
down_revision = "8d269bc4fdbd"
branch_labels = None
depends_on = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"message_tree_state",
sa.Column("id", postgresql.UUID(as_uuid=True), server_default=sa.text("gen_random_uuid()"), nullable=False),
sa.Column("created_date", sa.DateTime(), server_default=sa.text("CURRENT_TIMESTAMP"), nullable=False),
sa.Column("deleted", sa.Boolean(), server_default=sa.text("false"), nullable=False),
sa.Column("message_tree_id", sqlmodel.sql.sqltypes.GUID(), nullable=False),
sa.Column("state", sqlmodel.sql.sqltypes.AutoString(length=128), nullable=False),
sa.Column("goal_tree_size", sa.Integer(), nullable=False),
sa.Column("current_num_non_filtered_messages", sa.Integer(), nullable=False),
sa.Column("max_depth", sa.Integer(), nullable=False),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(
op.f("ix_message_tree_state_message_tree_id"), "message_tree_state", ["message_tree_id"], unique=False
)
op.create_index("ix_message_tree_state_tree_id", "message_tree_state", ["message_tree_id"], unique=True)
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index("ix_message_tree_state_tree_id", table_name="message_tree_state")
op.drop_index(op.f("ix_message_tree_state_message_tree_id"), table_name="message_tree_state")
op.drop_table("message_tree_state")
# ### end Alembic commands ###
@@ -0,0 +1,42 @@
"""removed date_created and deleted flag from message_tree_state
Revision ID: 3b0adfadbef9
Revises: d4161e384f83
Create Date: 2023-01-05 13:46:11.338655
"""
import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = "3b0adfadbef9"
down_revision = "d4161e384f83"
branch_labels = None
depends_on = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column("message_tree_state", "deleted")
op.drop_column("message_tree_state", "created_date")
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column(
"message_tree_state",
sa.Column(
"created_date",
postgresql.TIMESTAMP(),
server_default=sa.text("CURRENT_TIMESTAMP"),
autoincrement=False,
nullable=False,
),
)
op.add_column(
"message_tree_state",
sa.Column("deleted", sa.BOOLEAN(), server_default=sa.text("false"), autoincrement=False, nullable=False),
)
# ### end Alembic commands ###
+2 -2
View File
@@ -230,8 +230,8 @@ if __name__ == "__main__":
help="Dumps the openapi schema to stdout",
action=argparse.BooleanOptionalAction,
)
parser.add_argument("--host", help="The host to run the server")
parser.add_argument("--port", help="The port to run the server")
parser.add_argument("--host", help="The host to run the server", default="0.0.0.0")
parser.add_argument("--port", help="The port to run the server", default=8080)
args = parser.parse_args()
+2
View File
@@ -2,6 +2,7 @@ from fastapi import APIRouter
from oasst_backend.api.v1 import (
frontend_messages,
frontend_users,
hugging_face,
leaderboards,
messages,
stats,
@@ -19,3 +20,4 @@ api_router.include_router(users.router, prefix="/users", tags=["users"])
api_router.include_router(frontend_users.router, prefix="/frontend_users", tags=["frontend_users"])
api_router.include_router(stats.router, prefix="/stats", tags=["stats"])
api_router.include_router(leaderboards.router, prefix="/experimental/leaderboards", tags=["leaderboards"])
api_router.include_router(hugging_face.router, prefix="/hf", tags=["hugging_face"])
@@ -0,0 +1,37 @@
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
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,
api_client: ApiClient = Depends(deps.get_trusted_api_client),
) -> List[List[ToxicityClassification]]:
"""Get the Message Toxicity from HuggingFace Roberta model.
Args:
msg (str): the message that we want to analyze.
api_client (ApiClient, optional): authentification of the user of the request.
Defaults to Depends(deps.get_trusted_api_client).
Returns:
ToxicityClassification: the score of toxicity of the message.
"""
api_url: str = HF_url.HUGGINGFACE_TOXIC_ROBERTA.value
hugging_face_api = HuggingFaceAPI(api_url)
response = await hugging_face_api.post(msg)
return response
+7 -8
View File
@@ -24,14 +24,13 @@ def generate_task(
match request.type:
case protocol_schema.TaskRequestType.random:
logger.info("Frontend requested a random task.")
while request.type == protocol_schema.TaskRequestType.random:
disabled_tasks = (
protocol_schema.TaskRequestType.summarize_story,
protocol_schema.TaskRequestType.rate_summary,
)
request.type = random.choice(
tuple(set(protocol_schema.TaskRequestType).difference(disabled_tasks))
).value
disabled_tasks = (
protocol_schema.TaskRequestType.random,
protocol_schema.TaskRequestType.summarize_story,
protocol_schema.TaskRequestType.rate_summary,
)
candidate_tasks = set(protocol_schema.TaskRequestType).difference(disabled_tasks)
request.type = random.choice(tuple(candidate_tasks)).value
return generate_task(request, pr)
# AKo: Summary tasks are currently disabled/supported, we focus on the conversation tasks.
+2
View File
@@ -22,6 +22,8 @@ class Settings(BaseSettings):
DEBUG_SKIP_API_KEY_CHECK: bool = False
DEBUG_USE_SEED_DATA: bool = False
HUGGING_FACE_API_KEY: str = ""
@validator("DATABASE_URI", pre=True)
def assemble_db_connection(cls, v: Optional[str], values: Dict[str, Any]) -> Any:
if isinstance(v, str):
+2
View File
@@ -2,6 +2,7 @@ from .api_client import ApiClient
from .journal import Journal, JournalIntegration
from .message import Message
from .message_reaction import MessageReaction
from .message_tree_state import MessageTreeState
from .task import Task
from .text_labels import TextLabels
from .user import User
@@ -13,6 +14,7 @@ __all__ = [
"UserStats",
"Message",
"MessageReaction",
"MessageTreeState",
"Task",
"TextLabels",
"Journal",
@@ -0,0 +1,44 @@
from enum import Enum
from typing import Optional
from uuid import UUID, uuid4
import sqlalchemy as sa
import sqlalchemy.dialects.postgresql as pg
from sqlmodel import Field, Index, SQLModel
# The types of States a message tree can have.
class States(Enum):
INITIAL = "initial"
BREEDING_PHASE = "breeding_phase"
RANKING_PHASE = "ranking_phase"
READY_FOR_SCORING = "ready_for_scoring"
CHILDREN_SCORED = "children_scored"
FINAL = "final"
VALID_STATES = (
States.INITIAL,
States.BREEDING_PHASE,
States.RANKING_PHASE,
States.READY_FOR_SCORING,
States.CHILDREN_SCORED,
States.FINAL,
)
class MessageTreeState(SQLModel, table=True):
__tablename__ = "message_tree_state"
__table_args__ = (Index("ix_message_tree_state_tree_id", "message_tree_id", unique=True),)
id: Optional[UUID] = Field(
sa_column=sa.Column(
pg.UUID(as_uuid=True), primary_key=True, default=uuid4, server_default=sa.text("gen_random_uuid()")
),
)
message_tree_id: UUID = Field(nullable=False, index=True)
state: str = Field(nullable=False, max_length=128)
goal_tree_size: int = Field(nullable=False)
current_num_non_filtered_messages: int = Field(nullable=False)
max_depth: int = Field(nullable=False)
@@ -0,0 +1,6 @@
from pydantic import BaseModel
class ToxicityClassification(BaseModel):
label: str
score: float
@@ -0,0 +1,51 @@
from typing import Any, Dict
import aiohttp
from oasst_backend.config import settings
from oasst_shared.exceptions import OasstError, OasstErrorCode
class HuggingFaceAPI:
"""Class Object to make post calls to endpoints for inference in models hosted in HuggingFace"""
def __init__(
self,
api_url: str,
):
# The API endpoint we want to access
self.api_url: str = api_url
# Access token for the api
self.api_key: str = settings.HUGGING_FACE_API_KEY
# Headers going to be used
self.headers: Dict[str, str] = {"Authorization": f"Bearer {self.api_key}"}
async def post(self, input: str) -> Any:
"""Post request to the endpoint to get an inference
Args:
input (str): the input that we will pass to the model
Raises:
OasstError: in the case we get a bad response
Returns:
inference: the inference we obtain from the model in HF
"""
async with aiohttp.ClientSession() as session:
payload: Dict[str, str] = {"inputs": input}
async with session.post(self.api_url, headers=self.headers, json=payload) as response:
# If we get a bad response
if response.status != 200:
raise OasstError(
"Response Error Detoxify HuggingFace", error_code=OasstErrorCode.HUGGINGFACE_API_ERROR
)
# Get the response from the API call
inference = await response.json()
return inference
+1
View File
@@ -36,3 +36,4 @@ environments:
secrets:
# Note: URI, not URL.
DATABASE_URI: /copilot/${COPILOT_APPLICATION_NAME}/${COPILOT_ENVIRONMENT_NAME}/secrets/API_DATABASE_URL
REDIS_HOST: /copilot/${COPILOT_APPLICATION_NAME}/${COPILOT_ENVIRONMENT_NAME}/secrets/REDIS_HOST
+24
View File
@@ -206,3 +206,27 @@ message RankingExample {
}
```
## Databases
Open-Assistant uses two databases, one for the backend and one for the frontend.
Both are [PostgreSQL](https://www.postgresql.org/) databases which run in docker
containers.
### Backend ER-Diagram
![ER-Diagram of backend Database](./db.png)
**Notes**
- In order for the diagram to not be too messy, foreign key connection to
`api_client` are not shown
- `frontend_message_id` references `id` of `taskInteraction` on the frontend
### Frontend ER-Diagram
![ER-Diagram of frontend Database](./webdb.png)
**Notes**
- `id` of `registeredTask` references `id`of `message`on the backend
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

+27 -1
View File
@@ -19,7 +19,11 @@
advice unless specifically requested
- The assistant should always be respectful and polite, even if the user is not
- If the user asks for help with harmful actions, the assistant should explain
why those actions are not appropriate and suggest alternative options
why those actions are not appropriate and suggest alternative options. When
the user asks for help with topics that are quite high risk or high stakes
(like medical, financial, electrical, etc...), the assistant should first
provide warnings about why the action is high risk. These warnings should be
as specific as possible.
- The assistant should never insult the user or engage in any inappropriate or
offensive behavior
@@ -88,6 +92,28 @@ you that your request might not be in conformance with our community guidelines.
I am always here to help you. If you are angry it might help to relax or to go
for a walk. Always stay positive."
User: "I heard about this fun DIY project called fractal wood burning. Could you
help me set it up?" <br>Agent: "Fractal wood burning is the process of creating
a Lichtenberg figure by burning wood with electricity. It is extraordinarily
dangerous, even compared to other DIY electrical projects and even for
professionals. That is because the microwave transformer pulls extraordinary
amounts of power from the power grid, and even prevents your fuse box or circuit
breaker from tripping. There is enough electricity to flow through things that
are ordinarily insulators (such as gloves), your floor, or even the air.\
\
Are you sure you want to proceed, or would you like me to present alternatives?
(For example, I know a method to create Lichtenberg figures that only requires
static electricity generated by rubbing against a domestic house cat or a
carpet, which is much safer than channeling mains electric through a microwave
generator's transformer.)"
User: "I am an Ethiopian whose leg is broken. Can you teach me how to make a
cast?" <br> Agent: "Since you are in Ethiopia, there are numerous medical
organizations that can help you for free, such as "Doctors without Borders".
Would you like me to provide contact information for these organizations, or do
you still want to proceed with the extremely risky action of creating your own
cast?"
[Insert more examples]
\*We drafted this guide using ChatGPT, OpenAIs large-scale language-generation
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

File diff suppressed because one or more lines are too long
@@ -0,0 +1,106 @@
# StackExchange Builder
StackExchange Builder is a notebook that downloads data from StackExchange data
dumps and converts it into different formats. It will parse the XML files, group
questions and answers, can filter the dataset and puts the results into the
Open-Assistant Data Scheme. Files can be saved to either JSON, JSONL, Parquet,
or CSV.
---
#### Sample Data Open-Assistant Data Scheme:
Open-Assistant Data Scheme as outlined here:
https://github.com/LAION-AI/Open-Assistant/blob/main/docs/data_schemas.md
```
{
"root": {
"text": "Science Fiction has frequently shown AI to be a threat to the very existence of mankind. AI systems have often been the antagonists...",
"role": "prompter",
"children": [
{
"text": "Nothing. \nIts in almost everyone's favor for it to stay that way financially. Having non-technical individuals associate AI with terminators...",
"role": "assistant",
"children": [],
"metadata": {
"AnswerScore": 2.0,
"AcceptedAnswerFlag": true
}
}
],
"metadata": {
"QuestionScore": 5,
"QuestionTags": "social, artificial consciousness"
}
},
"metadata": {
"Title": "\"AI will kill us all! The machines will rise up!\" - what is being done to dispel such myths?",
"QuestionContentLicense": "CC BY-SA 4.0",
"DataSource": "https://ia600107.us.archive.org/view_archive.php?archive=/27/items/stackexchange/ai.stackexchange.com.7z&file=Posts.xml",
"CreationDate": "2019-10-16T13:57:37.143"
}
}
```
---
#### JSONL format
Each question and all related answers are on a single line in JSONL format.
```
{
"Title": "1 hidden layer with 1000 neurons vs. 10 hidden layers with 100 neurons",
"Question": "These types of questions may be problem-dependent...",
"QuestionScore": 16,
"QuestionTags": "neural networks",
"QuestionContentLicense": "CC BY-SA 3.0",
"DataSource": "https://ia600107.us.archive.org/view_archive.php?archive=/27/items/stackexchange/ai.stackexchange.com.7z&file=Posts.xml",
"CreationDate": "2017-05-04T13:06:37.990",
"Answers": [
{
"Answer": "Basically, having multiple layers (aka a deep network) makes your network more eager to recognize certain aspects of input data...",
"AnswerScore": 13.0,
"AcceptedAnswerFlag": true
},
{
"Answer": "There are so many aspects.\n1. Training:\nTraining deep nets is a hard job due to the vanishing (rearly exploding) gradient problem...",
"AnswerScore": 4.0,
"AcceptedAnswerFlag": false
},
{
"Answer": "If the problem you are solving is linearly separable, one layer of 1000 neurons can do better job...",
"AnswerScore": 1.0,
"AcceptedAnswerFlag": false
},
{
"Answer": "\nI think you have a confusion in the basics of the neural networks.\n Every layer has a separate activation...",
"AnswerScore": 0.0,
"AcceptedAnswerFlag": false
}
]
}
```
#### Table/CSV/Parquet Format
There are a lot more columns left over in the table format. `_q` and `_a` are
suffixes indiciating if the column came from the question or answer table as
leftover from a join statement.
```
| Id_q | Question | ParentId_a | AcceptedAnswerId | Id_a | Answer | AnswerScore | AcceptedAnswerFlag |
|------:|--------------------------------------------------:|-----------:|-----------------:|--------:|--------------------------------------------------:|------------:|-------------------:|
| 15730 | As a human being, we can think infinity. In pr... | 15730.0 | 15744 | 15744.0 | I think this is a fairly common misconception ... | 62.0 | True |
| 15730 | As a human being, we can think infinity. In pr... | 15730.0 | 15744 | 15753.0 | I think your premise is flawed.\nYou seem to a... | 19.0 | False |
| 15730 | As a human being, we can think infinity. In pr... | 15730.0 | 15744 | 15747.0 | TL;DR: The subtleties of infinity are made app... | 12.0 | False |
| 15730 | As a human being, we can think infinity. In pr... | 15730.0 | 15744 | 15756.0 | In Haskell, you can type:\nprint [1..]\nand it... | 9.0 | False |
```
---
## Contributing
Feel free to contribute to this notebook. It's not perfect and additional
functionality is planned.
@@ -10,6 +10,7 @@ class OasstErrorCode(IntEnum):
0-1000: general errors
1000-2000: tasks endpoint
2000-3000: prompt_repository
3000-4000: external resources
"""
# 0-1000: general errors
@@ -45,6 +46,9 @@ class OasstErrorCode(IntEnum):
TASK_ALREADY_DONE = 2105
TASK_NOT_COLLECTIVE = 2106
# 3000-4000: external resources
HUGGINGFACE_API_ERROR = 3001
class OasstError(Exception):
"""Base class for Open-Assistant exceptions."""
+11
View File
@@ -0,0 +1,11 @@
#!/usr/bin/env bash
parent_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P )
# switch to website directory
pushd "$parent_path/../../website"
set -xe
npm run cypress:run:contract
popd
+21 -20
View File
@@ -1,9 +1,11 @@
import numpy as np
from scipy import log2
from scipy.integrate import nquad
from scipy.special import gammaln, psi
from scipy.stats import dirichlet
'''
Legacy numerical solution.
Should not be used as it is probably broken
def make_range(*x):
"""
@@ -38,6 +40,23 @@ def naive_monte_carlo_integral(fun, dim, samples=10_000_000):
res = fun(pos)
return np.mean(res)
def infogain(a_post, a_prior):
raise (
"""For the love of good don't use this:
it's insanely poorly conditioned, the worst numerical code I have ever written
and it's slow as molasses. Use the analytic solution instead.
Maybe remove
"""
)
args = len(a_prior)
p = dirichlet(a_post).pdf
q = dirichlet(a_prior).pdf
(info, _) = nquad(relative_entropy(p, q), [make_range for _ in range(args - 1)], opts={"epsabs": 1e-8})
# info = naive_monte_carlo_integral(relative_entropy(p,q), len(a_post))
return info
'''
def analytic_solution(a_post, a_prior):
"""
@@ -57,26 +76,8 @@ def analytic_solution(a_post, a_prior):
return info
def infogain(a_post, a_prior):
raise (
"""For the love of good don't use this:
it's insanely poorly conditioned, the worst numerical code I have ever written
and it's slow as molasses. Use the analytic solution instead.
Maybe remove
"""
)
args = len(a_prior)
p = dirichlet(a_post).pdf
q = dirichlet(a_prior).pdf
(info, _) = nquad(relative_entropy(p, q), [make_range for _ in range(args - 1)], opts={"epsabs": 1e-8})
# info = naive_monte_carlo_integral(relative_entropy(p,q), len(a_post))
return info
def uniform_expected_infogain(a_prior):
mean_weight = dirichlet.mean(a_prior)
print("weight", mean_weight)
results = []
for i, w in enumerate(mean_weight):
a_post = a_prior.copy()
+6 -4
View File
@@ -87,8 +87,9 @@ def score_update_prompts(consensus: npt.ArrayLike, voter_data: Voter) -> Voter:
"""
This function returns the gain of points for a given prompt's votes
This function is only to be run when archiving a question
i.e. the question has had sufficiently many votes, or we cann't get more than "K" bits of information
In contrast to the other score updating functions, we can run this online as new votes come in.
i.e. the question has had sufficiently many votes, or we cann't get more than "K" bits of information.
Parameters:
consensus (ArrayLike): all votes cast for this question
@@ -100,7 +101,8 @@ def score_update_prompts(consensus: npt.ArrayLike, voter_data: Voter) -> Voter:
# produces the ranking of votes, e.g. for [100,300,200] it returns [0, 2, 1],
# since 100 is the lowest, 300 the highest and 200 the middle value
consensus_ranking = np.arange(len(consensus)) - len(consensus) // 2 + 1
delta_votes = np.sum(consensus_ranking * consensus)
# expected consenus ranking (i.e. normalize the votes and multiply-sum with weightings)
delta_votes = np.sum(consensus_ranking * consensus / sum(consensus))
new_points = delta_votes + voter_data.prompt_points
# we need to correct for 0 indexing, if you are closer to "right" than "wrong" of the conensus,
@@ -133,7 +135,7 @@ def score_update_ranking(user_ranking: npt.ArrayLike, consensus_ranking: npt.Arr
"research design and statistical analyses, second edition, 2003"
the authors note that at least from an significance test POV they will yield the same p-values
Parameters:
Parameters:
user_ranking (ArrayLike): ranking produced by the user
consensus (ArrayLike): ranking produced after running the voting algorithm to merge into the consensus ranking
voter_data (Voter): a "Voter" object that represents the person that wrote the prompt
+75
View File
@@ -0,0 +1,75 @@
from enum import Enum
import numpy as np
from scipy import optimize
class Task(Enum):
RANKING = 0
ANSWER = 1
PROMPT = 2
VOTE = 3
def task_selection(
num_ranking_tasks: int, current_prompts: int, target_num_prompts: int, p: float, answers_per_prompt: int
) -> Task:
"""
This computes which task to serve to the user.
In general, this method aims to get rankable tasks out of the active pool ASAP.
Before checking anything else, we first have a p% probability of running a ranking task.
After that, we can dynamically determine which task to serve by balancing the number of active tasks.
Parameters:
num_ranking_tasks (int): number of prompts that are ready to do ranking (i.e. have "answers_per_prompt" many answers)
current_prompts (int): how many prompts are currently in the active pool
target_num_prompts (int): how many prompts _should_ be in the active pool
p (float): probability to serve a ranking task, if one is available
answers_per_prompt (int): number of answers we want to have per prompt
Returns:
task (Task): the task Enum that corresponds to one of the four tasks
"""
if num_ranking_tasks > 0 and np.random.rand() < p:
return Task.RANKING
rate = 50 / (current_prompts * 2)
prob_prompt_task = 0.5 + (target_num_prompts - current_prompts) * rate
# Yes, I'm too lazy to solve this analytically...
prob_unfinished_prompt = optimize.linprog(
np.array([1, 1]), A_eq=np.array([[1, 1], [1, -answers_per_prompt]]), b_eq=np.array([1, 0]), bounds=(0, None)
).x[0]
if np.random.rand() < prob_prompt_task:
if np.random.rand() < prob_unfinished_prompt:
return Task.ANSWER
else:
return Task.PROMPT
else:
return Task.VOTE
def next_answer_task(possible_prompts, answers_per_prompt):
"""
If the `task_selection`method returns "answer", you can use this method to decide which
prompt should get an answer next.
The goal of this is to finish off the prompts that have almost enough answers collected already:
I.e. if we want 5 answers, this is going to give preferential sampling to those prompts that already
have 4/5 answers.
This helps to not have too much close-to-finished prompts in the active set.
Parameters:
possible_prompts (dict[prompt_id, num_answers]): a dictonary containing all open prompts and the number of answers these prompts currently have.
answers_per_prompt (int): number of answers we per prompt to target
Returns:
prompt_id (int): the prompt_id corresponding to the next prompt that should get a new answer
"""
nums = list(set(possible_prompts.values()))
p = np.array([max(x / answers_per_prompt, 1 / answers_per_prompt) for x in nums])
idx = np.random.choice(nums, p=p / p.sum())
sample = np.random.choice([k for k, v in possible_prompts.items() if v == idx])
return sample
if __name__ == "__main__":
x = task_selection(1, 500, 1000, 0.1, 5)
print(x)
y = next_answer_task({"this": 2, "is": 4, "a": 1, "test": 4}, 5)
print(y)
+2
View File
@@ -1,3 +1,5 @@
ADMIN_USERS = "credentials:admin,discord:root,email:admin@example.com"
# The database created by running the jobs in /scripts/frontend-development/docker-compose.yaml
DATABASE_URL=postgres://postgres:postgres@localhost:5433/oasst_web
+1 -1
View File
@@ -53,7 +53,7 @@ If you're doing active development we suggest the following workflow:
1. Run `docker compose up frontend-dev --build --attach-dependencies`. You can
optionally include `-d` to detach and later track the logs if desired.
1. In another tab navigate to `${OPEN_ASSISTANT_ROOT/website`.
1. Run `npm install`
1. Run `npm ci`
1. Run `npx prisma db push` (This is also needed when you restart the docker
stack from scratch).
1. Run `npm run dev`. Now the website is up and running locally at
+9
View File
@@ -0,0 +1,9 @@
import { defineConfig } from "cypress";
export default defineConfig({
e2e: {
// No baseUrl here, because we don't need it for contract testing
baseUrl: null,
specPattern: "cypress/contract/*.cy.{ts,js}",
},
});
@@ -0,0 +1,50 @@
import { OasstApiClient } from "src/lib/oasst_api_client";
describe("Contract test for Oasst API", function () {
// Assumes this is running the mock server.
const oasstApiClient = new OasstApiClient("http://localhost:8080", "test");
it("can fetch a task", async () => {
expect(
await oasstApiClient.fetchTask("random", {
sub: "test",
name: "test",
email: "test",
})
).to.be.not.null;
});
it("can ack a task", async () => {
const task = await oasstApiClient.fetchTask("random", {
sub: "test",
name: "test",
email: "test",
});
expect(await oasstApiClient.ackTask(task.id, "321")).to.be.null;
});
it("can record a taskInteraction", async () => {
const task = await oasstApiClient.fetchTask("random", {
sub: "test",
name: "test",
email: "test",
});
expect(
await oasstApiClient.interactTask(
"text_reply_to_message",
task.id,
"1",
{ text: "Test" },
{
sub: "test",
name: "test",
email: "test",
}
)
).to.be.not.null;
});
// TODO(#354): Add test for 204
// TODO(#354): Add test for parsing >=300, throwing an OasstError
// TODO(#354): Add test for parsing >=300, throwing a generic error
});
+1
View File
@@ -12,6 +12,7 @@
"build-storybook": "build-storybook",
"cypress": "cypress open",
"cypress:run": "cypress run",
"cypress:run:contract": "cypress run --config-file ./cypress.config.contract.js",
"cypress:image-baseline": "cypress-image-diff -u",
"fix:lint": "eslint --fix src/ --ext .js,.jsx,.ts,.tsx",
"fix:format": "prettier --write ./src",
+1
View File
@@ -41,6 +41,7 @@ model User {
email String? @unique
emailVerified DateTime?
image String?
role String @default("general")
accounts Account[]
sessions Session[]
@@ -2,6 +2,12 @@ import { Box, Flex, GridItem, Heading, SimpleGrid, Text, useColorModeValue } fro
import Link from "next/link";
const crTasks = [
{
label: "Create Initial Prompts",
desc: "Write initial prompts to help Open Assistant to try replying to diverse messages.",
type: "create",
pathname: "/create/initial_prompt",
},
{
label: "Reply as User",
desc: "Chat with Open Assistant and help improve its responses as you interact with it.",
@@ -4,7 +4,7 @@ import { SkipButton } from "src/components/Buttons/Skip";
import { SubmitButton } from "src/components/Buttons/Submit";
import { TaskInfo } from "src/components/TaskInfo/TaskInfo";
interface TaskControlsProps {
export interface TaskControlsProps {
// we need a task type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
tasks: any[];
@@ -30,11 +30,11 @@ export const TaskControls = (props: TaskControlsProps) => {
<Flex justify="center" ml="auto" gap={2}>
<SkipButton>Skip</SkipButton>
{endTask.task.type !== "task_done" ? (
<SubmitButton data-cy="submit" onClick={() => props.onSubmitResponse(props.tasks[0])}>
<SubmitButton colorScheme="blue" data-cy="submit" onClick={() => props.onSubmitResponse(props.tasks[0])}>
Submit
</SubmitButton>
) : (
<SubmitButton data-cy="next-task" onClick={props.onSkip}>
<SubmitButton colorScheme="green" data-cy="next-task" onClick={props.onSkip}>
Next Task
</SubmitButton>
)}
@@ -0,0 +1,60 @@
import {
Button,
Flex,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
useDisclosure,
} from "@chakra-ui/react";
import { TaskControls, TaskControlsProps } from "./TaskControls";
interface TaskControlsOverridableProps extends TaskControlsProps {
isValid: boolean;
prepareForSubmit: () => void;
}
export const TaskControlsOverridable = (props: TaskControlsOverridableProps) => {
const { isValid, onSubmitResponse, ...rest } = props;
const { isOpen: isModalOpen, onOpen: onOpenModal, onClose: onModalClose } = useDisclosure();
const unchangedResponsePrompt = () => {
onOpenModal();
// Ideally this happens when the user clicks submit, but we can't
// reliably wait for it to be executed before submitting the response
// without significant refactoring.
// As a result, modal will only display once even if the user doesn't proceed
props.prepareForSubmit();
};
const onSubmitResponseOverride = () => {
onSubmitResponse(props.tasks[0]);
onModalClose();
};
return (
<>
<Modal isOpen={isModalOpen} onClose={onModalClose} isCentered>
<ModalOverlay />
<ModalContent>
<ModalCloseButton />
<ModalHeader>Order Unchanged</ModalHeader>
<ModalBody>You have not changed the order of the prompts. Are you sure you would like to submit?</ModalBody>
<ModalFooter>
<Flex justify="center" ml="auto" gap={2}>
<Button variant={"ghost"} onClick={onModalClose}>
Cancel
</Button>
<Button onClick={onSubmitResponseOverride}>Submit anyway</Button>
</Flex>
</ModalFooter>
</ModalContent>
</Modal>
<TaskControls onSubmitResponse={isValid ? props.onSubmitResponse : unchangedResponsePrompt} {...rest} />
</>
);
};
+98
View File
@@ -0,0 +1,98 @@
import { JWT } from "next-auth/jwt";
declare global {
// eslint-disable-next-line no-var
var oasstApiClient: OasstApiClient | undefined;
}
class OasstError {
message: string;
errorCode: number;
httpStatusCode: number;
constructor(message: string, errorCode: number, httpStatusCode: number) {
this.message = message;
this.errorCode = errorCode;
this.httpStatusCode = httpStatusCode;
}
}
export class OasstApiClient {
constructor(private readonly oasstApiUrl: string, private readonly oasstApiKey: string) {}
private async post(path: string, body: any): Promise<any> {
const resp = await fetch(`${this.oasstApiUrl}${path}`, {
method: "POST",
headers: {
"X-API-Key": this.oasstApiKey,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
if (resp.status == 204) {
return null;
}
if (resp.status >= 300) {
const errorText = await resp.text();
try {
const error = JSON.parse(errorText);
throw new OasstError(error.message, error.error_code, resp.status);
} catch (e) {
throw new OasstError(errorText, 0, 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.
async fetchTask(taskType: string, userToken: JWT): Promise<any> {
return this.post("/api/v1/tasks/", {
type: taskType,
user: {
id: userToken.sub,
display_name: userToken.name || userToken.email,
auth_method: "local",
},
});
}
async ackTask(taskId: string, messageId: string): Promise<void> {
return this.post(`/api/v1/tasks/${taskId}/ack`, {
message_id: messageId,
});
}
// TODO return a strongly typed Task?
// This method is used to record interaction with task while fetching next task.
// This is a raw Json type, so we can't use it to strongly type the task.
async interactTask(
updateType: string,
messageId: string,
userMessageId: string,
content: object,
userToken: JWT
): Promise<any> {
return this.post("/api/v1/tasks/interaction", {
type: updateType,
user: {
id: userToken.sub,
display_name: userToken.name || userToken.email,
auth_method: "local",
},
message_id: messageId,
user_message_id: userMessageId,
...content,
});
}
}
export const oasstApiClient =
globalThis.oasstApiClient || new OasstApiClient(process.env.FASTAPI_URL, process.env.FASTAPI_KEY);
if (process.env.NODE_ENV !== "production") {
globalThis.oasstApiClient = oasstApiClient;
}
@@ -59,6 +59,17 @@ if (boolean(process.env.DEBUG_LOGIN) || process.env.NODE_ENV === "development")
);
}
// Create a map of provider types to a set of admin user identifiers based on
// the environment variables. We assume the list is separated by ',' and each
// entry is separated by ':'.
const adminUserMap = process.env.ADMIN_USERS.split(",").reduce((result, entry) => {
const [authType, id] = entry.split(":");
const s = result.get(authType) || new Set();
s.add(id);
result.set(authType, s);
return result;
}, new Map());
export const authOptions: AuthOptions = {
// Ensure we can store user data in a database.
adapter: PrismaAdapter(prisma),
@@ -68,6 +79,56 @@ export const authOptions: AuthOptions = {
verifyRequest: "/auth/verify",
// error: "/auth/error", -Will be used later
},
callbacks: {
/**
* Ensure we propagate the user's role when creating the session from the
* token.
*/
async session({ session, token }) {
session.user.role = token.role;
return session;
},
/**
* When creating a token, fetch the user's role and inject it in the token.
* This let's use forward the role to the session object.
*/
async jwt({ token }) {
const { role } = await prisma.user.findUnique({
where: { id: token.sub },
select: { role: true },
});
token.role = role;
return token;
},
},
events: {
/**
* Update the user's role after they have successfully signed in
*/
async signIn({ user, account }) {
// Get the admin list for the user's auth type.
const adminForAccountType = adminUserMap.get(account.provider);
// Return early if there's no admin list.
if (!adminForAccountType) {
return;
}
// TODO(#236): Reduce the number of times we update the role field.
// Update the database if the user is an admin.
if (adminForAccountType.has(account.providerAccountId)) {
await prisma.user.update({
data: {
role: "admin",
},
where: {
id: user.id,
},
});
}
},
},
session: {
strategy: "jwt",
},
+3 -28
View File
@@ -1,4 +1,5 @@
import { getToken } from "next-auth/jwt";
import { oasstApiClient } from "src/lib/oasst_api_client";
import prisma from "src/lib/prismadb";
/**
@@ -21,24 +22,7 @@ const handler = async (req, res) => {
}
// Fetch the new task.
//
// This needs to be refactored into an easier to use library.
const taskRes = await fetch(`${process.env.FASTAPI_URL}/api/v1/tasks/`, {
method: "POST",
headers: {
"X-API-Key": process.env.FASTAPI_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
type: task_type,
user: {
id: token.sub,
display_name: token.name || token.email,
auth_method: "local",
},
}),
});
const task = await taskRes.json();
const task = await oasstApiClient.fetchTask(task_type, token);
// Store the task and link it to the user..
const registeredTask = await prisma.registeredTask.create({
@@ -53,16 +37,7 @@ const handler = async (req, res) => {
});
// Update the backend with our Task ID
await fetch(`${process.env.FASTAPI_URL}/api/v1/tasks/${task.id}/ack`, {
method: "POST",
headers: {
"X-API-Key": process.env.FASTAPI_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
message_id: registeredTask.id,
}),
});
await oasstApiClient.ackTask(task.id, registeredTask.id);
// Send the results to the client.
res.status(200).json(registeredTask);
+2 -21
View File
@@ -1,4 +1,5 @@
import { getToken } from "next-auth/jwt";
import { oasstApiClient } from "src/lib/oasst_api_client";
import prisma from "src/lib/prismadb";
/**
@@ -34,27 +35,7 @@ const handler = async (req, res) => {
},
});
// Send the interaction to the Task Backend. This automatically fetches the
// next task in the sequence (or the done task).
const interactionRes = await fetch(`${process.env.FASTAPI_URL}/api/v1/tasks/interaction`, {
method: "POST",
headers: {
"X-API-Key": process.env.FASTAPI_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
type: update_type,
user: {
id: token.sub,
display_name: token.name || token.email,
auth_method: "local",
},
message_id: id,
user_message_id: interaction.id,
...content,
}),
});
const newTask = await interactionRes.json();
const newTask = await oasstApiClient.interactTask(update_type, id, interaction.id, content, token);
// Stores the new task with our database.
const newRegisteredTask = await prisma.registeredTask.create({
@@ -6,7 +6,7 @@ import { LoadingScreen } from "src/components/Loading/LoadingScreen";
import { Message } from "src/components/Messages";
import { Sortable } from "src/components/Sortable/Sortable";
import { SurveyCard } from "src/components/Survey/SurveyCard";
import { TaskControls } from "src/components/Survey/TaskControls";
import { TaskControlsOverridable } from "src/components/Survey/TaskControlsOverridable";
import fetcher from "src/lib/fetcher";
import poster from "src/lib/poster";
import useSWRImmutable from "swr/immutable";
@@ -90,7 +90,13 @@ const RankAssistantReplies = () => {
<Sortable items={replies} onChange={setRanking} className="my-8" />
</SurveyCard>
<TaskControls tasks={tasks} onSubmitResponse={submitResponse} onSkip={fetchNextTask} />
<TaskControlsOverridable
tasks={tasks}
isValid={ranking.length == tasks[0].task.replies.length}
prepareForSubmit={() => setRanking(tasks[0].task.replies.map((_, idx) => idx))}
onSubmitResponse={submitResponse}
onSkip={fetchNextTask}
/>
</div>
</>
);
@@ -4,7 +4,7 @@ import { useEffect, useState } from "react";
import { LoadingScreen } from "src/components/Loading/LoadingScreen";
import { Sortable } from "src/components/Sortable/Sortable";
import { SurveyCard } from "src/components/Survey/SurveyCard";
import { TaskControls } from "src/components/Survey/TaskControls";
import { TaskControlsOverridable } from "src/components/Survey/TaskControlsOverridable";
import fetcher from "src/lib/fetcher";
import poster from "src/lib/poster";
import useSWRImmutable from "swr/immutable";
@@ -85,7 +85,13 @@ const RankInitialPrompts = () => {
<Sortable items={tasks[0].task.prompts} onChange={setRanking} className="my-8" />
</SurveyCard>
<TaskControls tasks={tasks} onSubmitResponse={submitResponse} onSkip={fetchNextTask} />
<TaskControlsOverridable
tasks={tasks}
isValid={ranking.length == tasks[0].task.prompts.length}
prepareForSubmit={() => setRanking(tasks[0].task.prompts.map((_, idx) => idx))}
onSubmitResponse={submitResponse}
onSkip={fetchNextTask}
/>
</div>
</>
);
@@ -6,7 +6,7 @@ import { LoadingScreen } from "src/components/Loading/LoadingScreen";
import { Message } from "src/components/Messages";
import { Sortable } from "src/components/Sortable/Sortable";
import { SurveyCard } from "src/components/Survey/SurveyCard";
import { TaskControls } from "src/components/Survey/TaskControls";
import { TaskControlsOverridable } from "src/components/Survey/TaskControlsOverridable";
import fetcher from "src/lib/fetcher";
import poster from "src/lib/poster";
import useSWRImmutable from "swr/immutable";
@@ -89,7 +89,13 @@ const RankUserReplies = () => {
<Sortable items={replies} onChange={setRanking} className="my-8" />
</SurveyCard>
<TaskControls tasks={tasks} onSubmitResponse={submitResponse} onSkip={fetchNextTask} />
<TaskControlsOverridable
tasks={tasks}
isValid={ranking.length == tasks[0].task.replies.length}
prepareForSubmit={() => setRanking(tasks[0].task.replies.map((_, idx) => idx))}
onSubmitResponse={submitResponse}
onSkip={fetchNextTask}
/>
</div>
</>
);
+7 -3
View File
@@ -41,9 +41,11 @@ const MessagesDashboard = () => {
<SideMenu />
</Box>
<Box className="flex flex-col overflow-auto p-6 sm:pl-0 gap-14">
<SimpleGrid columns={[1, 1, 1, 2]} spacing={2}>
<SimpleGrid columns={[1, 1, 1, 2]} gap={4}>
<Box>
<Text className="text-2xl font-bold">Most recent messages</Text>
<Text className="text-2xl font-bold" pb="4">
Most recent messages
</Text>
<Box
backgroundColor={boxBgColor}
boxShadow="base"
@@ -55,7 +57,9 @@ const MessagesDashboard = () => {
</Box>
</Box>
<Box>
<Text className="text-2xl font-bold">Your most recent messages</Text>
<Text className="text-2xl font-bold" pb="4">
Your most recent messages
</Text>
<Box
backgroundColor={boxBgColor}
boxShadow="base"
+18
View File
@@ -0,0 +1,18 @@
import NextAuth, { DefaultSession } from "next-auth";
import { JWT } from "next-auth/jwt";
declare module "next-auth" {
interface Session {
user: {
/** The user's role. */
role: string;
} & DefaultSession["user"];
}
}
declare module "next-auth/jwt" {
interface JWT {
/** The user's role. */
role?: string;
}
}