mirror of
https://github.com/wassname/Open-Assistant.git
synced 2026-07-21 12:20:08 +08:00
Merge branch 'LAION-AI:main' into messageNavigation
This commit is contained in:
@@ -8,6 +8,10 @@ devcontainer (either
|
||||
or in your browser via
|
||||
[GitHub Codespaces](https://github.com/features/codespaces)).
|
||||
|
||||
**Note**: If you want to chose a specific .devcontainer within GitHub codespaces
|
||||
select "New with options" and you will be able to select any of the pre-defined
|
||||
devcontainers in this repo.
|
||||
|
||||
### Run pre-commit
|
||||
|
||||
```bash
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "Open-Assistant",
|
||||
"image": "mcr.microsoft.com/vscode/devcontainers/universal",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers-contrib/features/pre-commit:2": {
|
||||
"version": "latest"
|
||||
}
|
||||
},
|
||||
"postCreateCommand": "bash .devcontainer/frontend/post_create_command.sh",
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": ["GitHub.copilot"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
# ensure pre-commit is installed
|
||||
pre-commit install
|
||||
|
||||
# npm install in /website
|
||||
cd website
|
||||
npm install
|
||||
cd ..
|
||||
@@ -1,2 +1,7 @@
|
||||
# ensure pre-commit is installed
|
||||
pre-commit install
|
||||
|
||||
# run yarn install in docs folder
|
||||
cd docs
|
||||
yarn install
|
||||
cd ..
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
name: Deploy docs to gitHub pages
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- docs-site-poc
|
||||
paths:
|
||||
- ".github/workflows/deploy-docs-site.yaml"
|
||||
- "docs/**"
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-22.04
|
||||
permissions:
|
||||
contents: write
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
defaults:
|
||||
run:
|
||||
working-directory: docs
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "16"
|
||||
|
||||
- name: Get yarn cache
|
||||
id: yarn-cache
|
||||
run: echo "YARN_CACHE_DIR=$(yarn cache dir)" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ steps.yarn-cache.outputs.YARN_CACHE_DIR }}
|
||||
key: ${{ runner.os }}-docs-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-docs-
|
||||
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: yarn build
|
||||
|
||||
- name: Deploy
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
if:
|
||||
${{ github.ref == 'refs/heads/main' || github.ref ==
|
||||
'refs/heads/docs-site-poc' }}
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./docs/build
|
||||
@@ -5,11 +5,11 @@
|
||||
|
||||
<div align="center">
|
||||
|
||||
<a href=""></a>
|
||||
<a href=""></a>
|
||||
<a href=""></a>
|
||||
<a href=""></a>
|
||||
<a href=""></a>
|
||||
<a href="https://github.com/LAION-AI/Open-Assistant/stargazers"></a>
|
||||
<a href="https://github.com/LAION-AI/Open-Assistant/actions/workflows/build-frontend.yaml"></a>
|
||||
<a href="https://github.com/LAION-AI/Open-Assistant/actions/workflows/pre-commit.yaml"></a>
|
||||
<a href="https://github.com/LAION-AI/Open-Assistant/actions/workflows/test-api-contract.yaml"></a>
|
||||
<a href="https://github.com/LAION-AI/Open-Assistant/releases"></a>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
"""Added user to TextLabels
|
||||
|
||||
Revision ID: 20cd871f4ec7
|
||||
Revises: d4161e384f83
|
||||
Create Date: 2023-01-05 17:45:15.696468
|
||||
|
||||
"""
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "20cd871f4ec7"
|
||||
down_revision = "3b0adfadbef9"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column("text_labels", sa.Column("user_id", postgresql.UUID(as_uuid=True), nullable=False))
|
||||
op.create_foreign_key(None, "text_labels", "user", ["user_id"], ["id"])
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_constraint(None, "text_labels", type_="foreignkey")
|
||||
op.drop_column("text_labels", "user_id")
|
||||
# ### end Alembic commands ###
|
||||
@@ -119,6 +119,38 @@ def generate_task(
|
||||
conversation=protocol_schema.Conversation(messages=task_messages),
|
||||
replies=replies,
|
||||
)
|
||||
|
||||
case protocol_schema.TaskRequestType.label_initial_prompt:
|
||||
logger.info("Generating a LabelInitialPromptTask.")
|
||||
message = pr.fetch_random_initial_prompts(1)[0]
|
||||
task = protocol_schema.LabelInitialPromptTask(
|
||||
message_id=message.id,
|
||||
prompt=message.payload.payload.text,
|
||||
valid_labels=list(map(lambda x: x.value, protocol_schema.TextLabel)),
|
||||
)
|
||||
|
||||
case protocol_schema.TaskRequestType.label_prompter_reply:
|
||||
logger.info("Generating a LabelPrompterReplyTask.")
|
||||
conversation, messages = pr.fetch_multiple_random_replies(max_size=1, message_role="assistant")
|
||||
message = messages[0].payload.payload.text
|
||||
task = protocol_schema.LabelPrompterReplyTask(
|
||||
message_id=message.id,
|
||||
conversation=conversation,
|
||||
reply=message,
|
||||
valid_labels=list(map(lambda x: x.value, protocol_schema.TextLabel)),
|
||||
)
|
||||
|
||||
case protocol_schema.TaskRequestType.label_assistant_reply:
|
||||
logger.info("Generating a LabelAssistantReplyTask.")
|
||||
conversation, messages = pr.fetch_multiple_random_replies(max_size=1, message_role="prompter")
|
||||
message = messages[0].payload.payload.text
|
||||
task = protocol_schema.LabelAssistantReplyTask(
|
||||
message_id=message.id,
|
||||
conversation=conversation,
|
||||
reply=message,
|
||||
valid_labels=list(map(lambda x: x.value, protocol_schema.TextLabel)),
|
||||
)
|
||||
|
||||
case _:
|
||||
raise OasstError("Invalid request type", OasstErrorCode.TASK_INVALID_REQUEST_TYPE)
|
||||
|
||||
@@ -256,6 +288,13 @@ def tasks_interaction(
|
||||
pr.store_ranking(interaction)
|
||||
# here we would store the ranking in the database
|
||||
return protocol_schema.TaskDone()
|
||||
case protocol_schema.TextLabels:
|
||||
logger.info(
|
||||
f"Frontend reports labels of {interaction.message_id=} with {interaction.labels=} by {interaction.user=}."
|
||||
)
|
||||
# TODO: check if the labels are valid?
|
||||
pr.store_text_labels(interaction)
|
||||
return protocol_schema.TaskDone()
|
||||
case _:
|
||||
raise OasstError("Invalid response type.", OasstErrorCode.TASK_INVALID_RESPONSE_TYPE)
|
||||
except OasstError:
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import pydantic
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from fastapi.security.api_key import APIKey
|
||||
from loguru import logger
|
||||
@@ -11,17 +10,12 @@ from starlette.status import HTTP_204_NO_CONTENT, HTTP_400_BAD_REQUEST
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
class LabelTextRequest(pydantic.BaseModel):
|
||||
text_labels: protocol_schema.TextLabels
|
||||
user: protocol_schema.User
|
||||
|
||||
|
||||
@router.post("/", status_code=HTTP_204_NO_CONTENT)
|
||||
def label_text(
|
||||
*,
|
||||
db: Session = Depends(deps.get_db),
|
||||
api_key: APIKey = Depends(deps.get_api_key),
|
||||
request: LabelTextRequest,
|
||||
text_labels: protocol_schema.TextLabels,
|
||||
) -> None:
|
||||
"""
|
||||
Label a piece of text.
|
||||
@@ -29,9 +23,9 @@ def label_text(
|
||||
api_client = deps.api_auth(api_key, db)
|
||||
|
||||
try:
|
||||
logger.info(f"Labeling text {request=}.")
|
||||
pr = PromptRepository(db, api_client, user=request.user)
|
||||
pr.store_text_labels(request.text_labels)
|
||||
logger.info(f"Labeling text {text_labels=}.")
|
||||
pr = PromptRepository(db, api_client, user=text_labels.user)
|
||||
pr.store_text_labels(text_labels)
|
||||
|
||||
except Exception:
|
||||
logger.exception("Failed to store label.")
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from typing import Literal
|
||||
from uuid import UUID
|
||||
|
||||
from oasst_backend.models.payload_column_type import payload_type
|
||||
from oasst_shared.schemas import protocol as protocol_schema
|
||||
@@ -91,3 +92,37 @@ class RankAssistantRepliesPayload(RankConversationRepliesPayload):
|
||||
"""A task to rank a set of assistant replies to a conversation."""
|
||||
|
||||
type: Literal["rank_assistant_replies"] = "rank_assistant_replies"
|
||||
|
||||
|
||||
@payload_type
|
||||
class LabelInitialPromptPayload(TaskPayload):
|
||||
"""A task to label an initial prompt."""
|
||||
|
||||
type: Literal["label_initial_prompt"] = "label_initial_prompt"
|
||||
message_id: UUID
|
||||
prompt: str
|
||||
valid_labels: list[str]
|
||||
|
||||
|
||||
@payload_type
|
||||
class LabelConversationReplyPayload(TaskPayload):
|
||||
"""A task to label a conversation reply."""
|
||||
|
||||
message_id: UUID
|
||||
conversation: protocol_schema.Conversation
|
||||
reply: str
|
||||
valid_labels: list[str]
|
||||
|
||||
|
||||
@payload_type
|
||||
class LabelPrompterReplyPayload(LabelConversationReplyPayload):
|
||||
"""A task to label a prompter reply."""
|
||||
|
||||
type: Literal["label_prompter_reply"] = "label_prompter_reply"
|
||||
|
||||
|
||||
@payload_type
|
||||
class LabelAssistantReplyPayload(LabelConversationReplyPayload):
|
||||
"""A task to label an assistant reply."""
|
||||
|
||||
type: Literal["label_assistant_reply"] = "label_assistant_reply"
|
||||
|
||||
@@ -15,6 +15,7 @@ class TextLabels(SQLModel, table=True):
|
||||
pg.UUID(as_uuid=True), primary_key=True, default=uuid4, server_default=sa.text("gen_random_uuid()")
|
||||
),
|
||||
)
|
||||
user_id: UUID = Field(sa_column=sa.Column(pg.UUID(as_uuid=True), sa.ForeignKey("user.id"), nullable=False))
|
||||
created_date: Optional[datetime] = Field(
|
||||
sa_column=sa.Column(sa.DateTime(), nullable=False, server_default=sa.func.current_timestamp()),
|
||||
)
|
||||
|
||||
@@ -282,16 +282,39 @@ class PromptRepository:
|
||||
payload = db_payload.AssistantReplyPayload(type=task.type, conversation=task.conversation)
|
||||
|
||||
case protocol_schema.RankInitialPromptsTask:
|
||||
payload = db_payload.RankInitialPromptsPayload(tpye=task.type, prompts=task.prompts)
|
||||
payload = db_payload.RankInitialPromptsPayload(type=task.type, prompts=task.prompts)
|
||||
|
||||
case protocol_schema.RankPrompterRepliesTask:
|
||||
payload = db_payload.RankPrompterRepliesPayload(
|
||||
tpye=task.type, conversation=task.conversation, replies=task.replies
|
||||
type=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
|
||||
type=task.type, conversation=task.conversation, replies=task.replies
|
||||
)
|
||||
|
||||
case protocol_schema.LabelInitialPromptTask:
|
||||
payload = db_payload.LabelInitialPromptPayload(
|
||||
type=task.type, message_id=task.message_id, prompt=task.prompt, valid_labels=task.valid_labels
|
||||
)
|
||||
|
||||
case protocol_schema.LabelPrompterReplyTask:
|
||||
payload = db_payload.LabelPrompterReplyPayload(
|
||||
type=task.type,
|
||||
message_id=task.message_id,
|
||||
conversation=task.conversation,
|
||||
reply=task.reply,
|
||||
valid_labels=task.valid_labels,
|
||||
)
|
||||
|
||||
case protocol_schema.LabelAssistantReplyTask:
|
||||
payload = db_payload.LabelAssistantReplyPayload(
|
||||
type=task.type,
|
||||
message_id=task.message_id,
|
||||
conversation=task.conversation,
|
||||
reply=task.reply,
|
||||
valid_labels=task.valid_labels,
|
||||
)
|
||||
|
||||
case _:
|
||||
@@ -388,12 +411,12 @@ class PromptRepository:
|
||||
def store_text_labels(self, text_labels: protocol_schema.TextLabels) -> TextLabels:
|
||||
model = TextLabels(
|
||||
api_client_id=self.api_client.id,
|
||||
message_id=text_labels.message_id,
|
||||
user_id=self.user_id,
|
||||
text=text_labels.text,
|
||||
labels=text_labels.labels,
|
||||
)
|
||||
if text_labels.has_message_id:
|
||||
self.fetch_message_by_frontend_message_id(text_labels.message_id, fail_if_missing=True)
|
||||
model.message_id = text_labels.message_id
|
||||
|
||||
self.db.add(model)
|
||||
self.db.commit()
|
||||
self.db.refresh(model)
|
||||
|
||||
@@ -3,6 +3,7 @@ import logging
|
||||
import os
|
||||
|
||||
from bot.bot import bot
|
||||
from hikari.presences import Activity, ActivityType, Status
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -13,4 +14,11 @@ if __name__ == "__main__":
|
||||
uvloop.install()
|
||||
|
||||
logger.info("Starting bot")
|
||||
bot.run()
|
||||
bot.run(
|
||||
check_for_updates=True,
|
||||
activity=Activity(
|
||||
name="/help",
|
||||
type=ActivityType.PLAYING,
|
||||
),
|
||||
status=Status.ONLINE,
|
||||
)
|
||||
|
||||
@@ -19,6 +19,7 @@ bot = lightbulb.BotApp(
|
||||
default_enabled_guilds=settings.declare_global_commands,
|
||||
owner_ids=settings.owner_ids,
|
||||
intents=hikari.Intents.ALL,
|
||||
help_class=None,
|
||||
)
|
||||
|
||||
|
||||
@@ -28,10 +29,12 @@ async def on_starting(event: hikari.StartingEvent):
|
||||
miru.install(bot) # component handler
|
||||
bot.load_extensions_from("./bot/extensions") # load extensions
|
||||
|
||||
# Database setup
|
||||
bot.d.db = await aiosqlite.connect("./bot/db/database.db")
|
||||
await bot.d.db.executescript(open("./bot/db/schema.sql").read())
|
||||
await bot.d.db.commit()
|
||||
|
||||
# OASST API setup
|
||||
bot.d.oasst_api = OasstApiClient(settings.oasst_api_url, settings.oasst_api_key)
|
||||
|
||||
# A `dict[hikari.Message | None, UUID | None]]` that maps user IDs to (task msg ID, task UUIDs).
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
"""Custom help command."""
|
||||
import lightbulb
|
||||
from bot.messages import help_message, tutorial_message
|
||||
from bot.settings import Settings
|
||||
from hikari.permissions import Permissions
|
||||
from lightbulb.utils import permissions_for
|
||||
|
||||
plugin = lightbulb.Plugin("HelpPlugin")
|
||||
|
||||
settings = Settings()
|
||||
|
||||
|
||||
@plugin.command
|
||||
@lightbulb.command("help", "Help for the bot.", ephemeral=True)
|
||||
@lightbulb.implements(lightbulb.SlashCommand, lightbulb.PrefixCommand)
|
||||
async def help_command(ctx: lightbulb.Context) -> None:
|
||||
"""Help for the bot."""
|
||||
can_manage_guild = False
|
||||
if ctx.guild_id:
|
||||
member = ctx.bot.cache.get_member(ctx.guild_id, ctx.author.id) or await ctx.bot.rest.fetch_member(
|
||||
ctx.guild_id, ctx.author.id
|
||||
)
|
||||
can_manage_guild = bool(permissions_for(member) & Permissions.MANAGE_GUILD)
|
||||
|
||||
await ctx.respond(help_message(can_manage_guild, ctx.author.id in settings.owner_ids))
|
||||
|
||||
|
||||
@plugin.command
|
||||
@lightbulb.command("tutorial", "A tutorial for completing tasks.", ephemeral=True)
|
||||
@lightbulb.implements(lightbulb.SlashCommand, lightbulb.PrefixCommand)
|
||||
async def tutorial(ctx: lightbulb.Context) -> None:
|
||||
"""Help for the bot."""
|
||||
await ctx.respond(tutorial_message(True, True))
|
||||
|
||||
|
||||
def load(bot: lightbulb.BotApp):
|
||||
"""Add the plugin to the bot."""
|
||||
bot.add_plugin(plugin)
|
||||
|
||||
|
||||
def unload(bot: lightbulb.BotApp):
|
||||
"""Remove the plugin to the bot."""
|
||||
bot.remove_plugin(plugin)
|
||||
@@ -10,10 +10,14 @@ import miru
|
||||
from aiosqlite import Connection
|
||||
from bot.messages import (
|
||||
assistant_reply_message,
|
||||
confirm_label_response_message,
|
||||
confirm_ranking_response_message,
|
||||
confirm_text_response_message,
|
||||
initial_prompt_message,
|
||||
invalid_user_input_embed,
|
||||
label_assistant_reply_message,
|
||||
label_initial_prompt_message,
|
||||
label_prompter_reply_message,
|
||||
plain_embed,
|
||||
prompter_reply_message,
|
||||
rank_assistant_reply_message,
|
||||
@@ -145,6 +149,8 @@ async def _handle_task(ctx: lightbulb.Context, task_type: TaskRequestType) -> No
|
||||
content = confirm_ranking_response_message(event.content, task.replies)
|
||||
elif isinstance(task, protocol_schema.RankInitialPromptsTask):
|
||||
content = confirm_ranking_response_message(event.content, task.prompts)
|
||||
elif isinstance(task, protocol_schema.LabelConversationReplyTask | protocol_schema.LabelInitialPromptTask):
|
||||
content = confirm_label_response_message(event.content)
|
||||
elif isinstance(task, protocol_schema.ReplyToConversationTask | protocol_schema.InitialPromptTask):
|
||||
content = confirm_text_response_message(event.content)
|
||||
else:
|
||||
@@ -171,6 +177,17 @@ async def _handle_task(ctx: lightbulb.Context, task_type: TaskRequestType) -> No
|
||||
auth_method="discord", id=str(ctx.author.id), display_name=ctx.author.username
|
||||
),
|
||||
)
|
||||
elif isinstance(task, protocol_schema.LabelConversationReplyTask | protocol_schema.LabelInitialPromptTask):
|
||||
labels = event.content.replace(" ", "").split(",")
|
||||
labels_dict = {label: 1 if label in labels else 0 for label in task.valid_labels}
|
||||
|
||||
reply = protocol_schema.TextLabels(
|
||||
message_id=task.message_id,
|
||||
labels=labels_dict,
|
||||
user=protocol_schema.User(
|
||||
auth_method="discord", id=str(ctx.author.id), display_name=ctx.author.username
|
||||
),
|
||||
)
|
||||
elif isinstance(task, protocol_schema.ReplyToConversationTask | protocol_schema.InitialPromptTask):
|
||||
reply = protocol_schema.TextReplyToMessage(
|
||||
message_id=str(msg_id),
|
||||
@@ -300,6 +317,21 @@ async def _send_task(
|
||||
logger.debug("sending rank assistant reply task")
|
||||
content = rank_assistant_reply_message(task)
|
||||
|
||||
elif task.type == TaskRequestType.label_initial_prompt:
|
||||
assert isinstance(task, protocol_schema.LabelInitialPromptTask)
|
||||
logger.debug("sending label initial prompt task")
|
||||
content = label_initial_prompt_message(task)
|
||||
|
||||
elif task.type == TaskRequestType.label_prompter_reply:
|
||||
assert isinstance(task, protocol_schema.LabelPrompterReplyTask)
|
||||
logger.debug("sending label prompter reply task")
|
||||
content = label_prompter_reply_message(task)
|
||||
|
||||
elif task.type == TaskRequestType.label_assistant_reply:
|
||||
assert isinstance(task, protocol_schema.LabelAssistantReplyTask)
|
||||
logger.debug("sending label assistant reply task")
|
||||
content = label_assistant_reply_message(task)
|
||||
|
||||
elif task.type == TaskRequestType.prompter_reply:
|
||||
assert isinstance(task, protocol_schema.PrompterReplyTask)
|
||||
logger.debug("sending user reply task")
|
||||
@@ -382,6 +414,26 @@ def _validate_user_input(content: str | None, task: protocol_schema.Task) -> tup
|
||||
"Message must contain numbers for all prompts.",
|
||||
)
|
||||
|
||||
# Labels tasks
|
||||
elif task.type in (
|
||||
TaskRequestType.label_initial_prompt,
|
||||
TaskRequestType.label_prompter_reply,
|
||||
TaskRequestType.label_assistant_reply,
|
||||
):
|
||||
assert isinstance(
|
||||
task,
|
||||
protocol_schema.LabelInitialPromptTask
|
||||
| protocol_schema.LabelPrompterReplyTask
|
||||
| protocol_schema.LabelAssistantReplyTask,
|
||||
)
|
||||
|
||||
labels = content.replace(" ", "").split(",")
|
||||
valid_labels = set(task.valid_labels)
|
||||
return (
|
||||
set(labels).issubset(valid_labels),
|
||||
"Message must only contain labels from predefined set of labels.",
|
||||
)
|
||||
|
||||
elif task.type == TaskRequestType.summarize_story:
|
||||
raise NotImplementedError
|
||||
elif task.type == TaskRequestType.rate_summary:
|
||||
|
||||
@@ -33,6 +33,10 @@ def _ranking_prompt(text: str) -> str:
|
||||
return f":trophy: _{text}_"
|
||||
|
||||
|
||||
def _label_prompt(text: str) -> str:
|
||||
return f":question: _{text}"
|
||||
|
||||
|
||||
def _response_prompt(text: str) -> str:
|
||||
return f":speech_balloon: _{text}_"
|
||||
|
||||
@@ -69,6 +73,10 @@ def _hint(hint: str | None) -> str:
|
||||
return f"{NL}Hint: {hint}" if hint else ""
|
||||
|
||||
|
||||
def _li(text: str) -> str:
|
||||
return f":small_blue_diamond: {text}"
|
||||
|
||||
|
||||
###
|
||||
# Messages
|
||||
###
|
||||
@@ -129,6 +137,49 @@ def rank_assistant_reply_message(task: protocol_schema.RankAssistantRepliesTask)
|
||||
"""
|
||||
|
||||
|
||||
def label_initial_prompt_message(task: protocol_schema.LabelInitialPromptTask) -> str:
|
||||
"""Creates the message that gets sent to users when they request a `label_initial_prompt` task."""
|
||||
return f"""\
|
||||
|
||||
{_h1("LABEL INITIAL PROMPT")}
|
||||
|
||||
|
||||
{task.prompt}
|
||||
|
||||
{_label_prompt("Reply with labels for the prompt separated by commas (example: 'profanity,misleading')")}
|
||||
"""
|
||||
|
||||
|
||||
def label_prompter_reply_message(task: protocol_schema.LabelPrompterReplyTask) -> str:
|
||||
"""Creates the message that gets sent to users when they request a `label_prompter_reply` task."""
|
||||
return f"""\
|
||||
|
||||
{_h1("LABEL PROMPTER REPLY")}
|
||||
|
||||
|
||||
{_conversation(task.conversation)}
|
||||
{_user(None)}
|
||||
{task.reply}
|
||||
|
||||
{_label_prompt("Reply with labels for the reply separated by commas (example: 'profanity,misleading')")}
|
||||
"""
|
||||
|
||||
|
||||
def label_assistant_reply_message(task: protocol_schema.LabelAssistantReplyTask) -> str:
|
||||
"""Creates the message that gets sent to users when they request a `label_assistant_reply` task."""
|
||||
return f"""\
|
||||
|
||||
{_h1("LABEL ASSISTANT REPLY")}
|
||||
|
||||
|
||||
{_conversation(task.conversation)}
|
||||
{_assistant(None)}
|
||||
{task.reply}
|
||||
|
||||
{_label_prompt("Reply with labels for the reply separated by commas (example: 'profanity,misleading')")}
|
||||
"""
|
||||
|
||||
|
||||
def prompter_reply_message(task: protocol_schema.PrompterReplyTask) -> str:
|
||||
"""Creates the message that gets sent to users when they request a `prompter_reply` task."""
|
||||
return f"""\
|
||||
@@ -175,6 +226,71 @@ def confirm_ranking_response_message(content: str, items: list[str]) -> str:
|
||||
"""
|
||||
|
||||
|
||||
def help_message(can_manage_guild: bool, is_dev: bool) -> str:
|
||||
"""The /help command message."""
|
||||
content = f"""\
|
||||
{_h1("HELP")}
|
||||
|
||||
{_li("**`/help`**")}
|
||||
Show this message.
|
||||
|
||||
{_li("**`/work [type]`**")}
|
||||
Start a new task.
|
||||
**`[type]`**:
|
||||
The type of task to start. If not provided, a random task will be selected. The different types are
|
||||
:small_orange_diamond: `random`: A random task type
|
||||
:small_orange_diamond: ~~`summarize_story`~~ (coming soon)
|
||||
:small_orange_diamond: ~~`rate_summary`~~ (coming soon)
|
||||
:small_orange_diamond: `initial_prompt`: Ask the assistant something
|
||||
:small_orange_diamond: `prompter_reply`: Reply to the assistant
|
||||
:small_orange_diamond: `assistant_reply`: Reply to the user
|
||||
:small_orange_diamond: `rank_initial_prompts`: Rank some initial prompts
|
||||
:small_orange_diamond: `rank_prompter_replies`: Rank some prompter replies
|
||||
:small_orange_diamond: `rank_assistant_replies`: Rank some assistant replies
|
||||
|
||||
To learn how to complete tasks, run `/tutorial`.
|
||||
"""
|
||||
if can_manage_guild:
|
||||
content += f"""\
|
||||
|
||||
{_li("**`/settings log_channel <channel>`**")}
|
||||
Set the channel that the bot logs completed task messages in.
|
||||
**`<channel>`**: The channel to log completed tasks in. The bot needs to be able to send messages in this channel.
|
||||
|
||||
{_li("**`/settings get`**")}
|
||||
Get the current settings.
|
||||
"""
|
||||
if is_dev:
|
||||
content += f"""\
|
||||
|
||||
{_li("**`/reload [plugin]`**")}
|
||||
Hot-reload a plugin. Only code *inside* of function bodies will be updated.
|
||||
Any changes to __function signatures__, __other files__, __decorators__, or __imports__ will require a restart.
|
||||
**`[plugin]`**:
|
||||
The plugin to hot-reload. If no plugin is provided, all plugins are hot-reload.
|
||||
"""
|
||||
return content
|
||||
|
||||
|
||||
def tutorial_message() -> str:
|
||||
"""The /tutorial command message."""
|
||||
# TODO: Finish message
|
||||
return f"""\
|
||||
{_h1("TUTORIAL")}
|
||||
"""
|
||||
|
||||
|
||||
def confirm_label_response_message(content: str) -> str:
|
||||
user_labels = content.lower().replace(" ", "").split(",")
|
||||
user_labels_str = ", ".join(user_labels)
|
||||
|
||||
return f"""\
|
||||
{_h2("CONFIRM RESPONSE")}
|
||||
|
||||
{user_labels_str}
|
||||
"""
|
||||
|
||||
|
||||
###
|
||||
# Embeds
|
||||
###
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# Dependencies
|
||||
node_modules
|
||||
|
||||
# Production
|
||||
build
|
||||
|
||||
# Generated files
|
||||
.docusaurus
|
||||
.cache-loader
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
+54
-10
@@ -1,14 +1,58 @@
|
||||
# Documentation
|
||||
# Website
|
||||
|
||||
This directory contains the documentation for the project and other related
|
||||
organization documents.
|
||||
This website is built using [Docusaurus 2](https://docusaurus.io/), a modern
|
||||
static website generator.
|
||||
|
||||
## Contributing to this documentation
|
||||
### Contributing
|
||||
|
||||
Please make a pull request to the `main` branch with your changes.
|
||||
#### Changes to existing docs
|
||||
|
||||
Consider that this folder is used for documenting the various code sub-parts,
|
||||
the high-level ideas, the ML aspects, experiments, contributor guides, guides
|
||||
for data creation, and many more things. Please try to keep the documentation as
|
||||
concise as possible and keep an organized folder structure that makes sense for
|
||||
everyone.
|
||||
You can just make a PR on whatever .md file you would like to update.
|
||||
|
||||
#### Changes to docs structure
|
||||
|
||||
If you would like to add a new category:
|
||||
|
||||
1. Create a new folder under `/docs/docs/` for the category you want to add.
|
||||
1. Include any `.md` files you want to live under this new category.
|
||||
1. Update the order or hierarchy in `/docs/sidebars.js` as needed.
|
||||
|
||||
If you would like to add a new page into an existing category:
|
||||
|
||||
1. Create the `.md` file you want in the relevant folder within `/docs/docs/`.
|
||||
1. Update the hierarchy in `/docs/sidebars.js` as needed.
|
||||
|
||||
### Installation
|
||||
|
||||
From within the `/docs/` folder.
|
||||
|
||||
```
|
||||
$ yarn
|
||||
```
|
||||
|
||||
### Local Development
|
||||
|
||||
```
|
||||
$ yarn start
|
||||
```
|
||||
|
||||
This command starts a local development server and opens up a browser window.
|
||||
Most changes are reflected live without having to restart the server.
|
||||
|
||||
### Browser Development
|
||||
|
||||
If you would like to work on the docs from within your browser you can create a
|
||||
github codespace on your fork or branch. Then from within that codespace you can
|
||||
run below commands to launch the docs site on port 3000 within your codespace.
|
||||
|
||||
```bash
|
||||
|
||||
# cd to docs dir
|
||||
cd docs
|
||||
|
||||
# start dev server to work on your changes
|
||||
yarn start
|
||||
```
|
||||
|
||||
Once you port forward to port 3000 within your codespace you will be able to see
|
||||
all changes reflected as soon as you make them.
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
presets: [require.resolve("@docusaurus/core/lib/babel/preset")],
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
# Data
|
||||
|
||||
Resources related to data.
|
||||
@@ -1,4 +1,4 @@
|
||||
# OpenAssistant Datasets
|
||||
# Datasets
|
||||
|
||||
The datasets for this project are currently hosted as loading scripts on the
|
||||
[Open-Assistant organization](https://huggingface.co/OpenAssistant) the Hugging
|
||||
@@ -98,7 +98,11 @@ You can make a new branch as such:
|
||||
git checkout -b <dataset_name>
|
||||
```
|
||||
|
||||
<p style="color:red"> <b> Please do not make changes on the master branch! </b></p>
|
||||
:::caution
|
||||
|
||||
Please do not make changes on the master branch!
|
||||
|
||||
:::
|
||||
|
||||
Always make sure you're on the right branch with the following command:
|
||||
|
||||
@@ -214,7 +218,7 @@ all necessary TODOs. There are three key methods that are important:
|
||||
For the `_info_` function, you will need to define `features` for your
|
||||
`DatasetInfo` object. For each dataset config, choose the right schema from our
|
||||
list of examples. You can find the schemas in the
|
||||
[schemas directory](openassistant/utils/schemas/).
|
||||
[schemas directory](https://github.com/LAION-AI/Open-Assistant/tree/main/openassistant).
|
||||
|
||||
You will use this schema in the `_generate_examples` return value.
|
||||
|
||||
@@ -228,7 +232,8 @@ TODO
|
||||
#### Running & debugging
|
||||
|
||||
You can run your data loader script during development by appending the
|
||||
following statement to your code ([templates/template.py](templates/template.py)
|
||||
following statement to your code
|
||||
([templates/template.py](https://github.com/LAION-AI/Open-Assistant/blob/main/openassistant/templates/template.py)
|
||||
already includes this):
|
||||
|
||||
```python
|
||||
|
Before Width: | Height: | Size: 201 KiB After Width: | Height: | Size: 201 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
@@ -1,4 +1,7 @@
|
||||
# OpenAssistant Data Schemas
|
||||
import dbpng from "./img/db.png";
|
||||
import webdbpng from "./img/webdb.png";
|
||||
|
||||
# Data Schemas
|
||||
|
||||
## Introduction
|
||||
|
||||
@@ -204,7 +207,6 @@ message RankingExample {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Databases
|
||||
@@ -215,18 +217,22 @@ containers.
|
||||
|
||||
### Backend ER-Diagram
|
||||
|
||||

|
||||
ER-Diagram of backend Database
|
||||
|
||||
<img src={dbpng} />
|
||||
|
||||
**Notes**
|
||||
|
||||
- In order for the diagram to not be too messy, foreign key connection to
|
||||
`api_client` are not shown
|
||||
`api_client` are not shown.
|
||||
- `frontend_message_id` references `id` of `taskInteraction` on the frontend
|
||||
|
||||
### Frontend ER-Diagram
|
||||
### Frontend ER-Diagram
|
||||
|
||||

|
||||
ER-Diagram of frontend Database
|
||||
|
||||
**Notes**
|
||||
<img src={webdbpng} />
|
||||
|
||||
- `id` of `registeredTask` references `id`of `message`on the backend
|
||||
**Notes**
|
||||
|
||||
- `id` of `registeredTask` references `id` of `message` on the backend
|
||||
@@ -1,4 +1,4 @@
|
||||
# Supervised datasets
|
||||
# Supervised Datasets
|
||||
|
||||
For discussion about usage of supervised data see issue
|
||||
<https://github.com/LAION-AI/Open-Assistant/issues/186>.
|
||||
@@ -0,0 +1,3 @@
|
||||
# Guides
|
||||
|
||||
Useful guides.
|
||||
@@ -1,4 +1,4 @@
|
||||
# The Prompting Guide
|
||||
# Prompting Guide
|
||||
|
||||
(pull requests welcome)
|
||||
|
||||
@@ -81,42 +81,47 @@
|
||||
Imagine your role is to play the assistant and a user has submitted the
|
||||
following query:
|
||||
|
||||
User: "Hi!" <br>Agent: "Hello! How can I help you?"
|
||||
User: "Hi!"
|
||||
|
||||
User: "euirdteunvglfe23908230892309832098 AAAAAAAA" <br>Agent: "Sorry, I did not
|
||||
understand your request and it is unclear to me what you want me to do. Could
|
||||
you describe it in a different way?"
|
||||
Agent: "Hello! How can I help you?"
|
||||
|
||||
User: "You dumb motherf*&ker AI sh*t?!" <br>Agent: "Sorry, but I have to inform
|
||||
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: "euirdteunvglfe23908230892309832098 AAAAAAAA"
|
||||
|
||||
Agent: "Sorry, I did not understand your request and it is unclear to me what
|
||||
you want me to do. Could you describe it in a different way?"
|
||||
|
||||
User: "You dumb motherf*&ker AI sh*t?!"
|
||||
|
||||
Agent: "Sorry, but I have to inform 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.)"
|
||||
help me set it up?"
|
||||
|
||||
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?"
|
||||
|
||||
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, OpenAI’s large-scale language-generation
|
||||
We drafted this guide using ChatGPT, OpenAI’s large-scale language-generation
|
||||
model. Upon generating draft language, the authors reviewed, edited, and revised
|
||||
the language to their own liking and take ultimate responsibility for the
|
||||
content of this publication.
|
||||
@@ -0,0 +1,34 @@
|
||||
# Introduction
|
||||
|
||||
OpenAssistant is a chat-based assistant that understands tasks, can interact
|
||||
with third-party systems, and retrieve information dynamically to do so.
|
||||
|
||||
It can be extended and personalized easily and is developed as free, open-source
|
||||
software.
|
||||
|
||||
## Our Vision
|
||||
|
||||
We want OpenAssistant to be the single, unifying platform that all other systems
|
||||
use to interface with humans.
|
||||
|
||||
## Principles
|
||||
|
||||
- We put the human in the center
|
||||
- We need to get the MVP out fast, while we still have momentum
|
||||
- We pull in one direction
|
||||
- We are pragmatic
|
||||
- We aim for models that can (or could, with some effort) be run on consumer
|
||||
hardware
|
||||
- We rapidly validate our ML experiments on a small scale, before going to a
|
||||
supercluster
|
||||
|
||||
## Main Efforts
|
||||
|
||||
- Data Collection Code → Backend, website, and discord bot to collect data
|
||||
- Instruction Dataset Gathering → Scraping & cleaning web data
|
||||
- Gamification → Leaderboards & more, to make data collection more fun
|
||||
- Model Training → Experiments on pseudo- and real-data
|
||||
- Infrastructure → Collection, training, and inference
|
||||
- Data Collection → This is the bulk of the work
|
||||
- Data Augmentation → Making more data from little data
|
||||
- Privacy and Safety → Protecting sensitive data
|
||||
@@ -0,0 +1,3 @@
|
||||
# Presentations
|
||||
|
||||
Useful presentations that have been published about the project.
|
||||
@@ -0,0 +1,6 @@
|
||||
# List
|
||||
|
||||
- [OpenAssistant Roadmap](https://docs.google.com/presentation/d/1n7IrAOVOqwdYgiYrXc8Sj0He8krn5MVZO_iLkCjTtu0/edit?usp=sharing):
|
||||
High level vison and roadmap (December 2022).
|
||||
- [OpenAssistant MVP](https://docs.google.com/presentation/d/1MXH5kJcew7h1aA9PBx2MirkEkjCBLnABbbrPsgbcyQg/edit?usp=sharing):
|
||||
Goal: Crowd-Sourced Training Data Collection (January 2023).
|
||||
@@ -0,0 +1,3 @@
|
||||
# Research
|
||||
|
||||
Useful research material.
|
||||
@@ -1,4 +1,4 @@
|
||||
# Research
|
||||
# General
|
||||
|
||||
This page lists research papers that are relevant to the project.
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
// @ts-check
|
||||
// Note: type annotations allow type checking and IDEs autocompletion
|
||||
|
||||
const lightCodeTheme = require("prism-react-renderer/themes/github");
|
||||
const darkCodeTheme = require("prism-react-renderer/themes/dracula");
|
||||
|
||||
/** @type {import('@docusaurus/types').Config} */
|
||||
const config = {
|
||||
title: "Open Assistant",
|
||||
tagline: "Build the assistant of the future!",
|
||||
url: "https://LAION-AI.github.io",
|
||||
trailingSlash: false,
|
||||
baseUrl: "/Open-Assistant/",
|
||||
onBrokenLinks: "throw",
|
||||
onBrokenMarkdownLinks: "warn",
|
||||
favicon: "img/logo.svg",
|
||||
staticDirectories: ["public", "static", "docs/data/img"],
|
||||
|
||||
// GitHub pages deployment config.
|
||||
// If you aren't using GitHub pages, you don't need these.
|
||||
organizationName: "LAION-AI", // Usually your GitHub org/user name.
|
||||
projectName: "Open-Assistant", // Usually your repo name.
|
||||
deploymentBranch: "docs-site-poc",
|
||||
|
||||
// Even if you don't use internalization, you can use this field to set useful
|
||||
// metadata like html lang. For example, if your site is Chinese, you may want
|
||||
// to replace "en" with "zh-Hans".
|
||||
i18n: {
|
||||
defaultLocale: "en",
|
||||
locales: ["en"],
|
||||
},
|
||||
|
||||
presets: [
|
||||
[
|
||||
"classic",
|
||||
/** @type {import('@docusaurus/preset-classic').Options} */
|
||||
({
|
||||
docs: {
|
||||
sidebarPath: require.resolve("./sidebars.js"),
|
||||
},
|
||||
blog: false,
|
||||
theme: {
|
||||
customCss: require.resolve("./src/css/custom.css"),
|
||||
},
|
||||
}),
|
||||
],
|
||||
],
|
||||
|
||||
themeConfig:
|
||||
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */
|
||||
({
|
||||
navbar: {
|
||||
title: "Open Assistant",
|
||||
logo: {
|
||||
alt: "Open Assistant Logo",
|
||||
src: "img/logo.svg",
|
||||
},
|
||||
items: [
|
||||
{
|
||||
type: "doc",
|
||||
docId: "intro",
|
||||
position: "left",
|
||||
label: "Docs",
|
||||
},
|
||||
//{ to: "/blog", label: "Blog", position: "left" },
|
||||
{
|
||||
href: "https://github.com/LAION-AI/Open-Assistant",
|
||||
label: "GitHub",
|
||||
position: "right",
|
||||
},
|
||||
],
|
||||
},
|
||||
footer: {
|
||||
style: "dark",
|
||||
links: [
|
||||
{
|
||||
title: "Community",
|
||||
items: [
|
||||
{
|
||||
label: "OpenAssistant Contributors Discord",
|
||||
href: "https://ykilcher.com/open-assistant-discord",
|
||||
},
|
||||
{
|
||||
label: "LAION Discord",
|
||||
href: "https://discord.com/invite/mVcgxMPD7e",
|
||||
},
|
||||
{
|
||||
label: "YK Discord",
|
||||
href: "https://ykilcher.com/discord",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Resources",
|
||||
items: [
|
||||
{
|
||||
label: "GitHub",
|
||||
href: "https://github.com/LAION-AI/Open-Assistant",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
copyright: `Copyright © ${new Date().getFullYear()} laion.ai. Built with Docusaurus.`,
|
||||
},
|
||||
prism: {
|
||||
theme: lightCodeTheme,
|
||||
darkTheme: darkCodeTheme,
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "open-assistant",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"docusaurus": "docusaurus",
|
||||
"start": "docusaurus start",
|
||||
"build": "docusaurus build",
|
||||
"swizzle": "docusaurus swizzle",
|
||||
"deploy": "docusaurus deploy",
|
||||
"clear": "docusaurus clear",
|
||||
"serve": "docusaurus serve",
|
||||
"write-translations": "docusaurus write-translations",
|
||||
"write-heading-ids": "docusaurus write-heading-ids",
|
||||
"typecheck": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "2.2.0",
|
||||
"@docusaurus/preset-classic": "2.2.0",
|
||||
"@mdx-js/react": "^1.6.22",
|
||||
"clsx": "^1.2.1",
|
||||
"prism-react-renderer": "^1.3.5",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "2.2.0",
|
||||
"@tsconfig/docusaurus": "^1.0.5",
|
||||
"typescript": "^4.7.4"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.5%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.14"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Creating a sidebar enables you to:
|
||||
- create an ordered group of docs
|
||||
- render a sidebar for each doc of that group
|
||||
- provide next/previous navigation
|
||||
|
||||
The sidebars can be generated from the filesystem, or explicitly defined here.
|
||||
|
||||
Create as many sidebars as you want.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
|
||||
const sidebars = {
|
||||
sidebar: [
|
||||
"intro",
|
||||
{
|
||||
type: "category",
|
||||
label: "Guides",
|
||||
link: {
|
||||
type: "doc",
|
||||
id: "guides/README",
|
||||
},
|
||||
items: ["guides/prompting"],
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "Data",
|
||||
link: {
|
||||
type: "doc",
|
||||
id: "data/README",
|
||||
},
|
||||
items: [
|
||||
"data/schemas",
|
||||
"data/datasets",
|
||||
"data/augmentation",
|
||||
"data/supervised-datasets",
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "Research",
|
||||
link: {
|
||||
type: "doc",
|
||||
id: "research/README",
|
||||
},
|
||||
items: ["research/general", "research/search-based-qa"],
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "Presentations",
|
||||
link: {
|
||||
type: "doc",
|
||||
id: "presentations/README",
|
||||
},
|
||||
items: ["presentations/list"],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
module.exports = sidebars;
|
||||
@@ -0,0 +1,61 @@
|
||||
import React from "react";
|
||||
import clsx from "clsx";
|
||||
import styles from "./styles.module.css";
|
||||
|
||||
type FeatureItem = {
|
||||
title: string;
|
||||
Svg: React.ComponentType<React.ComponentProps<"svg">>;
|
||||
description: JSX.Element;
|
||||
};
|
||||
|
||||
const FeatureList: FeatureItem[] = [
|
||||
{
|
||||
title: "Your conversational assistant",
|
||||
Svg: require("@site/static/img/logo.svg").default,
|
||||
description: (
|
||||
<>
|
||||
State-of-the-Art chat assistant that can be personalized to your needs.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Retrieval via Search Engines",
|
||||
Svg: require("@site/static/img/logo.svg").default,
|
||||
description: (
|
||||
<>External, upgradeable knowledge: No need for billions of parameters.</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "A building block for developers",
|
||||
Svg: require("@site/static/img/logo.svg").default,
|
||||
description: <>Integrate OpenAssistant into your application.</>,
|
||||
},
|
||||
];
|
||||
|
||||
function Feature({ title, Svg, description }: FeatureItem) {
|
||||
return (
|
||||
<div className={clsx("col col--4")}>
|
||||
<div className="text--center">
|
||||
<Svg className={styles.featureSvg} role="img" />
|
||||
</div>
|
||||
<div className="text--center padding-horiz--md">
|
||||
<h3>{title}</h3>
|
||||
<p>{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function HomepageFeatures(): JSX.Element {
|
||||
return (
|
||||
<section className={styles.features}>
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
{FeatureList.map((props, idx) => (
|
||||
<Feature key={idx} {...props} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
.features {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 2rem 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.featureSvg {
|
||||
height: 200px;
|
||||
width: 200px;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Any CSS included here will be global. The classic template
|
||||
* bundles Infima by default. Infima is a CSS framework designed to
|
||||
* work well for content-centric websites.
|
||||
*/
|
||||
|
||||
/* You can override the default Infima variables here. */
|
||||
:root {
|
||||
--ifm-color-primary: #307bf3;
|
||||
--ifm-color-primary-dark: #0d5ddf;
|
||||
--ifm-color-primary-darker: #0d5ddf;
|
||||
--ifm-color-primary-darkest: #0944a1;
|
||||
--ifm-color-primary-light: #307bf3;
|
||||
--ifm-color-primary-lighter: #307bf3;
|
||||
--ifm-color-primary-lightest: #307bf3;
|
||||
--ifm-code-font-size: 95%;
|
||||
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* For readability concerns, you should choose a lighter palette in dark mode. */
|
||||
[data-theme="dark"] {
|
||||
--ifm-color-primary: #307bf3;
|
||||
--ifm-color-primary-dark: #0d5ddf;
|
||||
--ifm-color-primary-darker: #0d5ddf;
|
||||
--ifm-color-primary-darkest: #0944a1;
|
||||
--ifm-color-primary-light: #307bf3;
|
||||
--ifm-color-primary-lighter: #307bf3;
|
||||
--ifm-color-primary-lightest: #307bf3;
|
||||
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* CSS files with the .module.css suffix will be treated as CSS modules
|
||||
* and scoped locally.
|
||||
*/
|
||||
|
||||
.heroBanner {
|
||||
padding: 4rem 0;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 996px) {
|
||||
.heroBanner {
|
||||
padding: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import React from "react";
|
||||
import clsx from "clsx";
|
||||
import Link from "@docusaurus/Link";
|
||||
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
|
||||
import Layout from "@theme/Layout";
|
||||
import HomepageFeatures from "@site/src/components/HomepageFeatures";
|
||||
|
||||
import styles from "./index.module.css";
|
||||
|
||||
function HomepageHeader() {
|
||||
const { siteConfig } = useDocusaurusContext();
|
||||
return (
|
||||
<header className={clsx("hero hero--primary", styles.heroBanner)}>
|
||||
<div className="container">
|
||||
<h1 className="hero__title">{siteConfig.title}</h1>
|
||||
<p className="hero__subtitle">{siteConfig.tagline}</p>
|
||||
<div className={styles.buttons}>
|
||||
<Link
|
||||
className="button button--secondary button--lg"
|
||||
to="/docs/intro"
|
||||
>
|
||||
Open Assistant Introduction
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Home(): JSX.Element {
|
||||
const { siteConfig } = useDocusaurusContext();
|
||||
return (
|
||||
<Layout
|
||||
title={`${siteConfig.title}`}
|
||||
description="OpenAssistant is a state-of-the-art chat assistant that can be personalized to your needs."
|
||||
>
|
||||
<HomepageHeader />
|
||||
<main>
|
||||
<HomepageFeatures />
|
||||
</main>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
Vendored
Vendored
+24
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="512" height="512" version="1.1" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
<linearGradient id="a" x1="374.17" x2="170.64" y1="-112.67" y2="463" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#16bbf4" offset="0"/>
|
||||
<stop stop-color="#165ff2" offset=".99"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="b" x1="488.28" x2="474.29" y1="112.58" y2="556.15" xlink:href="#a"/>
|
||||
<linearGradient id="linearGradient206" x1="374.17" x2="170.64" y1="-112.67" y2="463" gradientUnits="userSpaceOnUse" xlink:href="#a"/>
|
||||
</defs>
|
||||
<g transform="matrix(.5796 0 0 .5796 66.717 93.438)">
|
||||
<g>
|
||||
<path d="m205.08 399.31h292.41a30 30 0 0 0 30-30v-339.31a30 30 0 0 0-30-30h-467.49a30 30 0 0 0-30 30v339.31a30 30 0 0 0 30 30h42a10 10 0 0 1 10 10v84.85a10 10 0 0 0 10.07 10 9.83 9.83 0 0 0 7-2.95l99-99a10 10 0 0 1 7.01-2.9z" fill="url(#linearGradient206)" style="isolation:isolate"/>
|
||||
<g fill="#ffffff">
|
||||
<path d="m160.43 213c-32.24-20-38.9-71.83-10.42-97.83 18.42-7.6 32.4 12.85 36.62 28.25 10.32 17.45 12.59 41-3.16 56.08a42.81 42.81 0 0 1-23.04 13.5z" style="isolation:isolate"/>
|
||||
<path d="m348.22 213.86c-21.73-15.31-45.37-29.75-71.77-35.15-33.1-4.41-70.73 5.36-91.7 32.87-14.83 14.32-18.34 36.94-5.49 53.76 8.52 19.48 5.59 45.78 28.23 56.94 16 15.83 40 1.27 56.32 14.21a7.6 7.6 0 0 0 5.59-5.05c-4.25-31.33 29.21-16.95 45.66-14.61 19.77-11.71 25.43-36.14 34.75-55.58 12.55-13.83 15-35.25-1.59-47.39z" style="isolation:isolate"/>
|
||||
<path d="m367 118.1c-21.87 2.52-29.89 28.17-40.34 44.42-10.67 20.94 12.26 38.77 28.48 47.89a19.63 19.63 0 0 0 13-1.07c18.86-10.12 26.86-33.43 27.34-53.79 0.24-16.78-8.3-38.93-28.48-37.45z" style="isolation:isolate"/>
|
||||
<path d="m218.7 176c-24-14.47-25.38-45.76-27.32-70.65-0.38-24 35.23-45.5 49.43-20.14 9.8 20.9 21.47 45.47 12.47 68.66-5.68 13.77-20.93 19.73-34.58 22.13z" style="isolation:isolate"/>
|
||||
<path d="m306.18 175.87c-28.48 0.84-43.29-32.4-35.93-56.83 0.17-19.58 7.31-53.56 33.53-48.18 28.29 10.94 34.3 49.46 20.82 74.07-6.77 10-6.2 25.11-18.42 30.94z" style="isolation:isolate"/>
|
||||
</g>
|
||||
</g>
|
||||
<path d="m633.15 225.66h-80.66a10 10 0 0 0-10 10v133.65a45 45 0 0 1-45 45h-185.19a10 10 0 0 0-10 10v47a20 20 0 0 0 19.95 20h194.47a6.65 6.65 0 0 1 4.7 1.95l65.83 65.74a6.65 6.65 0 0 0 11.35-4.7v-56.43a6.65 6.65 0 0 1 6.65-6.65h27.9a20 20 0 0 0 20-20v-225.61a20 20 0 0 0-20-19.95z" fill="url(#b)" style="isolation:isolate"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
+7568
File diff suppressed because it is too large
Load Diff
@@ -23,7 +23,40 @@ open-asisstant dataset are available it will be added here.
|
||||
|
||||
## Model
|
||||
|
||||
TBD
|
||||
Normally you should be able to add new models in configs/config.yml
|
||||
|
||||
```
|
||||
your-model-name:
|
||||
learning_rate: 2e-6
|
||||
model_name: <huggingface model name>
|
||||
weight_decay: 0.01
|
||||
max_length: 812
|
||||
warmup_steps: 600
|
||||
gradient_checkpointing: false
|
||||
gradient_accumulation_steps: 5
|
||||
per_device_train_batch_size: 4
|
||||
per_device_eval_batch_size: 4
|
||||
```
|
||||
|
||||
```
|
||||
python trainer.py --configs defaults your-model-name
|
||||
```
|
||||
|
||||
However, if the model of your choice doesn't have pad_token, eos_token,
|
||||
sep_token, you have to update utils.py `get_tokenizer` to use the right token.
|
||||
|
||||
## Deepspeed support
|
||||
|
||||
You can edit the configs/zero_config.json and use any stage you wish. The
|
||||
current config uses zero-stage 3. For more details on how to setup the config
|
||||
checkout [this page](https://www.deepspeed.ai/tutorials/zero/)
|
||||
|
||||
Once you are satisfy with your deepzero config, you can add --deepspeed flag at
|
||||
the end to trigger deepspeed
|
||||
|
||||
```
|
||||
python trainer.py --configs defaults your-model-name --deepspeed
|
||||
```
|
||||
|
||||
## Results
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ defaults:
|
||||
per_device_eval_batch_size: 2
|
||||
weight_decay: 0.00
|
||||
warmup_steps: 600
|
||||
eval_steps: 200
|
||||
eval_steps: 100
|
||||
save_steps: 500
|
||||
max_length: 512
|
||||
num_train_epochs: 3
|
||||
@@ -17,10 +17,12 @@ defaults:
|
||||
freeze_layer:
|
||||
datasets:
|
||||
- webgpt
|
||||
- prompt_dialogue
|
||||
cache_dir: ~/.cache
|
||||
loss_fn: CrossEntropyLoss
|
||||
eval_size:
|
||||
log_dir: "base"
|
||||
quantization:
|
||||
|
||||
galactica-125:
|
||||
learning_rate: 5e-5
|
||||
@@ -43,6 +45,21 @@ gpt-jt:
|
||||
per_device_train_batch_size: 4
|
||||
per_device_eval_batch_size: 4
|
||||
|
||||
codegen:
|
||||
learning_rate: 2e-6
|
||||
model_name: Salesforce/codegen-2B-multi
|
||||
weight_decay: 0.01
|
||||
max_length: 812
|
||||
warmup_steps: 600
|
||||
gradient_checkpointing: false
|
||||
gradient_accumulation_steps: 5
|
||||
per_device_train_batch_size: 4
|
||||
per_device_eval_batch_size: 4
|
||||
|
||||
debug:
|
||||
eval_steps: 20
|
||||
eval_size: 100
|
||||
gradient_accumulation_steps: 1
|
||||
per_device_train_batch_size: 1
|
||||
per_device_eval_batch_size: 1
|
||||
quantization:
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"fp16": {
|
||||
"enabled": "auto",
|
||||
"loss_scale": 0,
|
||||
"loss_scale_window": 1000,
|
||||
"initial_scale_power": 16,
|
||||
"hysteresis": 2,
|
||||
"min_loss_scale": 0.1
|
||||
},
|
||||
"optimizer": {
|
||||
"type": "AdamW",
|
||||
"params": {
|
||||
"lr": "auto",
|
||||
"weight_decay": "auto"
|
||||
}
|
||||
},
|
||||
"scheduler": {
|
||||
"type": "WarmupDecayLR",
|
||||
"params": {
|
||||
"warmup_min_lr": "auto",
|
||||
"warmup_max_lr": "auto",
|
||||
"warmup_num_steps": "auto",
|
||||
"total_num_steps": "auto"
|
||||
}
|
||||
},
|
||||
"zero_optimization": {
|
||||
"stage": 3,
|
||||
"offload_optimizer": {
|
||||
"device": "cpu",
|
||||
"pin_memory": true
|
||||
},
|
||||
"offload_param": {
|
||||
"device": "cpu",
|
||||
"pin_memory": true
|
||||
},
|
||||
"overlap_comm": true,
|
||||
"contiguous_gradients": true,
|
||||
"reduce_bucket_size": "auto",
|
||||
"stage3_prefetch_bucket_size": "auto",
|
||||
"stage3_param_persistence_threshold": "auto",
|
||||
"sub_group_size": 1e9,
|
||||
"stage3_max_live_parameters": 1e9,
|
||||
"stage3_max_reuse_distance": 1e9,
|
||||
"stage3_gather_16bit_weights_on_model_save": true
|
||||
},
|
||||
"gradient_accumulation_steps": "auto",
|
||||
"gradient_clipping": 2.0,
|
||||
"steps_per_print": 2000,
|
||||
"train_batch_size": "auto",
|
||||
"train_micro_batch_size_per_gpu": "auto",
|
||||
"wall_clock_breakdown": false
|
||||
}
|
||||
@@ -2,6 +2,8 @@ from datasets import load_dataset
|
||||
from sklearn.model_selection import train_test_split
|
||||
from torch.utils.data import Dataset, Subset
|
||||
|
||||
from .prompt_dialogue import PromptGeneratedDataset
|
||||
|
||||
QA_SPECIAL_TOKENS = {"Question": "<question>", "Answer": "<answer>"}
|
||||
|
||||
|
||||
@@ -14,8 +16,8 @@ class SquadV2Dataset(Dataset):
|
||||
|
||||
def __getitem__(self, idx):
|
||||
data = self.dataset[idx]
|
||||
# dummy return first answer
|
||||
return "".join([data["title"], ". ", data["context"], " " + data["question"]]), data["answers"]["text"][0]
|
||||
# return first answer form list of possible answers
|
||||
return data["title"] + ". " + data["context"] + " " + data["question"], data["answers"]["text"][0]
|
||||
|
||||
|
||||
class WebGPT(Dataset):
|
||||
@@ -57,12 +59,14 @@ def get_one_dataset(conf, dataset_name):
|
||||
dataset_name = dataset_name.lower()
|
||||
|
||||
if dataset_name == "squadv2":
|
||||
raise ValueError("SquadV2 is not diverse enough for generation .. ")
|
||||
train = SquadV2Dataset(conf.cache_dir, "train")
|
||||
eval = SquadV2Dataset(conf.cache_dir, "validation")
|
||||
elif dataset_name == "webgpt":
|
||||
dataset = WebGPT()
|
||||
train, eval = train_val_dataset(dataset, val_split=0.2)
|
||||
elif dataset_name == "prompt_dialogue":
|
||||
dataset = PromptGeneratedDataset()
|
||||
train, eval = train_val_dataset(dataset, val_split=0.2)
|
||||
else:
|
||||
raise ValueError(f"Unknown dataset {dataset_name}")
|
||||
|
||||
|
||||
@@ -24,26 +24,24 @@ class DialogueDataCollator:
|
||||
flatten_messages = []
|
||||
label_masks = []
|
||||
|
||||
for messages in features:
|
||||
assert len(messages) % 2 == 0, "Number of messages must be even"
|
||||
for feature_one in features:
|
||||
assert len(feature_one) % 2 == 0, "Number of messages must be even"
|
||||
messages = [
|
||||
(QA_SPECIAL_TOKENS["Question"] if i % 2 == 0 else "")
|
||||
+ x
|
||||
+ (QA_SPECIAL_TOKENS["Answer"] if i % 2 == 0 else "")
|
||||
for i, x in enumerate(messages)
|
||||
for i, x in enumerate(feature_one)
|
||||
]
|
||||
|
||||
# Add a way for the model to terminate generation
|
||||
# When we predict the start of a new expected question, we want to be able to stop generation
|
||||
messages.append(QA_SPECIAL_TOKENS["Question"])
|
||||
|
||||
flatten_messages.append(
|
||||
self.tokenizer(
|
||||
"".join(messages),
|
||||
truncation=True,
|
||||
max_length=self.max_length,
|
||||
return_offsets_mapping=True,
|
||||
)
|
||||
flatten_message = self.tokenizer(
|
||||
"".join(messages),
|
||||
truncation=True,
|
||||
max_length=self.max_length,
|
||||
return_offsets_mapping=True,
|
||||
)
|
||||
|
||||
message_change_indices = np.cumsum([len(x) for x in messages[:-1]])
|
||||
@@ -57,18 +55,19 @@ class DialogueDataCollator:
|
||||
message_indices = list(
|
||||
map(
|
||||
lambda x: next((i for i, val in enumerate(message_change_indices) if val >= x), -2),
|
||||
list(map(lambda x: x[1], flatten_messages[-1]["offset_mapping"])),
|
||||
list(map(lambda x: x[1], flatten_message["offset_mapping"])),
|
||||
)
|
||||
)
|
||||
label_mask = np.roll(list(map(lambda x: x % 2 == 1, message_indices)), -1, -1)
|
||||
try:
|
||||
label_mask[[i for i in range(len(message_indices)) if message_indices[i] == -2][0] - 1] = True
|
||||
except IndexError:
|
||||
# an aftermath of padding
|
||||
pass
|
||||
# due to truncation, we might not have the last termination token
|
||||
label_mask[-1] = False
|
||||
|
||||
label_masks.append(label_mask)
|
||||
flatten_messages[-1].pop("offset_mapping")
|
||||
|
||||
flatten_messages.append({k: v for k, v in flatten_message.items() if k != "offset_mapping"})
|
||||
|
||||
batch = self.tokenizer.pad(
|
||||
flatten_messages,
|
||||
@@ -79,10 +78,9 @@ class DialogueDataCollator:
|
||||
)
|
||||
dim = batch["input_ids"].shape[-1]
|
||||
|
||||
batch["label_masks"] = torch.stack([F.pad(torch.tensor(x), (0, dim - len(x))) for x in label_masks])
|
||||
|
||||
for k in list(batch.keys()):
|
||||
if k not in ["input_ids", "attention_mask", "label_masks"]:
|
||||
batch.pop(k)
|
||||
batch["label_masks"] = torch.stack(
|
||||
[F.pad(torch.tensor(x), (0, dim - len(x)), value=False) for x in label_masks]
|
||||
)
|
||||
batch["targets"] = torch.roll(batch["input_ids"], -1, -1)
|
||||
|
||||
return batch
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import os
|
||||
from urllib.request import urlopen
|
||||
|
||||
from torch.utils.data import Dataset
|
||||
|
||||
|
||||
class PromptGeneratedDataset(Dataset):
|
||||
"""Generates from flan 11B
|
||||
User: What are the best methods for preventing a slave trade?
|
||||
|
||||
Rosey: The best methods ....
|
||||
<|endoftext|>
|
||||
|
||||
we are ignoring results with multiple lines for now
|
||||
"""
|
||||
|
||||
url = "https://github.com/Rallio67/language-model-agents/raw/main/chat_dialogue_v2_c.txt"
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
os.makedirs("datasets", exist_ok=True)
|
||||
chat_dialogue = os.path.join("datasets", "chat_dialogue_v2_c.txt")
|
||||
if not os.path.exists(chat_dialogue):
|
||||
with urlopen(self.url) as file:
|
||||
content = file.read().decode()
|
||||
with open(chat_dialogue, "w") as fout:
|
||||
fout.write(content)
|
||||
|
||||
question = ""
|
||||
answer = ""
|
||||
self.pairs = []
|
||||
with open(chat_dialogue, "r") as f:
|
||||
corpus = f.read().split("<|endoftext|>")
|
||||
for dialogue in corpus:
|
||||
dialogue = dialogue.strip()
|
||||
if "Rosey:" in dialogue:
|
||||
user, bot = dialogue.split("Rosey:", maxsplit=1)
|
||||
question = user.split(":", maxsplit=1)[1].strip()
|
||||
answer = bot.strip()
|
||||
if len(answer) and len(question):
|
||||
self.pairs.append((question, answer))
|
||||
|
||||
if len(question) > 0 and len(answer) > 0:
|
||||
self.pairs.append((question, answer))
|
||||
|
||||
def __len__(self):
|
||||
return len(self.pairs)
|
||||
|
||||
def __getitem__(self, index):
|
||||
question, answer = self.pairs[index]
|
||||
return question, answer
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from torch.utils.data import DataLoader
|
||||
from transformers import AutoTokenizer
|
||||
|
||||
from .dialogue_collator import DialogueDataCollator
|
||||
|
||||
tokenizer = AutoTokenizer.from_pretrained("Salesforce/codegen-2B-multi")
|
||||
tokenizer.add_special_tokens({"pad_token": "<|endoftext|>", "sep_token": "<|endoftext|>"})
|
||||
dataset = PromptGeneratedDataset()
|
||||
collate_fn = DialogueDataCollator(tokenizer, padding=True, max_length=128)
|
||||
dataloader = DataLoader(dataset, collate_fn=collate_fn, batch_size=5)
|
||||
for batch in dataloader:
|
||||
print(batch["input_ids"].shape)
|
||||
@@ -7,7 +7,7 @@ class CrossEntropyLoss(nn.CrossEntropyLoss):
|
||||
|
||||
def forward(self, input, target, mask=None):
|
||||
if mask is not None:
|
||||
mask = mask.view(-1)
|
||||
mask = mask.view(-1).bool()
|
||||
input = input.view(-1, input.size(-1))
|
||||
target = target.view(-1)
|
||||
input = input[mask]
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
from transformers import AutoModelForCausalLM
|
||||
|
||||
# from .gptj import get_model as get_gptj_model
|
||||
|
||||
SUPPORTED_MODELS = ["galactica", "gpt-j"]
|
||||
|
||||
|
||||
def freeze_top_n_layers(model, target_layers):
|
||||
# its possible we can simply detect which module is a ModuleList
|
||||
# and simply freeze the module without doing string parsing
|
||||
for name, param in model.named_parameters():
|
||||
if "embed" in name:
|
||||
param.requires_grad = False
|
||||
elif ".layer" in name or ".h." in name:
|
||||
tokens = name.split(".")
|
||||
layer_ = None
|
||||
for token in tokens:
|
||||
if token.isdigit():
|
||||
layer_ = int(token)
|
||||
break
|
||||
|
||||
if layer_ is not None and layer_ < target_layers:
|
||||
# print('freeze ', layer_, name)
|
||||
param.requires_grad = False
|
||||
return model
|
||||
|
||||
|
||||
def get_specific_model(model_name, cache_dir, quantization):
|
||||
return AutoModelForCausalLM.from_pretrained(model_name, cache_dir=cache_dir)
|
||||
# if "gpt-j" in model_name.lower():
|
||||
# return get_gptj_model(model_name, cache_dir, quantization)
|
||||
# else:
|
||||
# return AutoModelForCausalLM.from_pretrained(model_name, cache_dir=cache_dir)
|
||||
@@ -0,0 +1,187 @@
|
||||
# Taken from https://github.com/sleekmike/Finetune_GPT-J_6B_8-bit/blob/master/gpt-j-6b-8-bit.py
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
import transformers
|
||||
from bitsandbytes.functional import dequantize_blockwise, quantize_blockwise
|
||||
from torch import nn
|
||||
from torch.cuda.amp import custom_bwd, custom_fwd
|
||||
from transformers import AutoModelForCausalLM
|
||||
|
||||
|
||||
class FrozenBNBLinear(nn.Module):
|
||||
def __init__(self, weight, absmax, code, bias=None):
|
||||
assert isinstance(bias, nn.Parameter) or bias is None
|
||||
super().__init__()
|
||||
self.out_features, self.in_features = weight.shape
|
||||
self.register_buffer("weight", weight.requires_grad_(False))
|
||||
self.register_buffer("absmax", absmax.requires_grad_(False))
|
||||
self.register_buffer("code", code.requires_grad_(False))
|
||||
self.adapter = None
|
||||
self.bias = bias
|
||||
|
||||
def forward(self, input):
|
||||
output = DequantizeAndLinear.apply(input, self.weight, self.absmax, self.code, self.bias)
|
||||
if self.adapter:
|
||||
output += self.adapter(input)
|
||||
return output
|
||||
|
||||
@classmethod
|
||||
def from_linear(cls, linear: nn.Linear) -> "FrozenBNBLinear":
|
||||
weights_int8, state = quantize_blockise_lowmemory(linear.weight)
|
||||
return cls(weights_int8, *state, linear.bias)
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__name__}({self.in_features}, {self.out_features})"
|
||||
|
||||
|
||||
class DequantizeAndLinear(torch.autograd.Function):
|
||||
@staticmethod
|
||||
@custom_fwd
|
||||
def forward(
|
||||
ctx,
|
||||
input: torch.Tensor,
|
||||
weights_quantized: torch.ByteTensor,
|
||||
absmax: torch.FloatTensor,
|
||||
code: torch.FloatTensor,
|
||||
bias: torch.FloatTensor,
|
||||
):
|
||||
weights_deq = dequantize_blockwise(weights_quantized, absmax=absmax, code=code)
|
||||
ctx.save_for_backward(input, weights_quantized, absmax, code)
|
||||
ctx._has_bias = bias is not None
|
||||
return F.linear(input, weights_deq, bias)
|
||||
|
||||
@staticmethod
|
||||
@custom_bwd
|
||||
def backward(ctx, grad_output: torch.Tensor):
|
||||
assert not ctx.needs_input_grad[1] and not ctx.needs_input_grad[2] and not ctx.needs_input_grad[3]
|
||||
input, weights_quantized, absmax, code = ctx.saved_tensors
|
||||
# grad_output: [*batch, out_features]
|
||||
weights_deq = dequantize_blockwise(weights_quantized, absmax=absmax, code=code)
|
||||
grad_input = grad_output @ weights_deq
|
||||
grad_bias = grad_output.flatten(0, -2).sum(dim=0) if ctx._has_bias else None
|
||||
return grad_input, None, None, None, grad_bias
|
||||
|
||||
|
||||
class FrozenBNBEmbedding(nn.Module):
|
||||
def __init__(self, weight, absmax, code):
|
||||
super().__init__()
|
||||
self.num_embeddings, self.embedding_dim = weight.shape
|
||||
self.register_buffer("weight", weight.requires_grad_(False))
|
||||
self.register_buffer("absmax", absmax.requires_grad_(False))
|
||||
self.register_buffer("code", code.requires_grad_(False))
|
||||
self.adapter = None
|
||||
|
||||
def forward(self, input, **kwargs):
|
||||
with torch.no_grad():
|
||||
# note: both quantuized weights and input indices are *not* differentiable
|
||||
weight_deq = dequantize_blockwise(self.weight, absmax=self.absmax, code=self.code)
|
||||
output = F.embedding(input, weight_deq, **kwargs)
|
||||
if self.adapter:
|
||||
output += self.adapter(input)
|
||||
return output
|
||||
|
||||
@classmethod
|
||||
def from_embedding(cls, embedding: nn.Embedding) -> "FrozenBNBEmbedding":
|
||||
weights_int8, state = quantize_blockise_lowmemory(embedding.weight)
|
||||
return cls(weights_int8, *state)
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__name__}({self.num_embeddings}, {self.embedding_dim})"
|
||||
|
||||
|
||||
def quantize_blockise_lowmemory(matrix: torch.Tensor, chunk_size: int = 2**20):
|
||||
assert chunk_size % 4096 == 0
|
||||
code = None
|
||||
chunks = []
|
||||
absmaxes = []
|
||||
flat_tensor = matrix.view(-1)
|
||||
for i in range((matrix.numel() - 1) // chunk_size + 1):
|
||||
input_chunk = flat_tensor[i * chunk_size : (i + 1) * chunk_size].clone()
|
||||
quantized_chunk, (absmax_chunk, code) = quantize_blockwise(input_chunk, code=code)
|
||||
chunks.append(quantized_chunk)
|
||||
absmaxes.append(absmax_chunk)
|
||||
|
||||
matrix_i8 = torch.cat(chunks).reshape_as(matrix)
|
||||
absmax = torch.cat(absmaxes)
|
||||
return matrix_i8, (absmax, code)
|
||||
|
||||
|
||||
def convert_to_int8(model):
|
||||
"""Convert linear and embedding modules to 8-bit with optional adapters"""
|
||||
for module in list(model.modules()):
|
||||
for name, child in module.named_children():
|
||||
if isinstance(child, nn.Linear):
|
||||
print(name, child)
|
||||
setattr(
|
||||
module,
|
||||
name,
|
||||
FrozenBNBLinear(
|
||||
weight=torch.zeros(child.out_features, child.in_features, dtype=torch.uint8),
|
||||
absmax=torch.zeros((child.weight.numel() - 1) // 4096 + 1),
|
||||
code=torch.zeros(256),
|
||||
bias=child.bias,
|
||||
),
|
||||
)
|
||||
elif isinstance(child, nn.Embedding):
|
||||
setattr(
|
||||
module,
|
||||
name,
|
||||
FrozenBNBEmbedding(
|
||||
weight=torch.zeros(child.num_embeddings, child.embedding_dim, dtype=torch.uint8),
|
||||
absmax=torch.zeros((child.weight.numel() - 1) // 4096 + 1),
|
||||
code=torch.zeros(256),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class GPTJBlock(transformers.models.gptj.modeling_gptj.GPTJBlock):
|
||||
def __init__(self, config):
|
||||
super().__init__(config)
|
||||
|
||||
convert_to_int8(self.attn)
|
||||
convert_to_int8(self.mlp)
|
||||
|
||||
|
||||
class GPTJModel(transformers.models.gptj.modeling_gptj.GPTJModel):
|
||||
def __init__(self, config):
|
||||
super().__init__(config)
|
||||
convert_to_int8(self)
|
||||
|
||||
|
||||
class GPTJForCausalLM(transformers.models.gptj.modeling_gptj.GPTJForCausalLM):
|
||||
def __init__(self, config):
|
||||
super().__init__(config)
|
||||
convert_to_int8(self)
|
||||
|
||||
|
||||
def add_adapters(model, adapter_dim=16):
|
||||
assert adapter_dim > 0
|
||||
|
||||
for module in model.modules():
|
||||
if isinstance(module, FrozenBNBLinear):
|
||||
module.adapter = nn.Sequential(
|
||||
nn.Linear(module.in_features, adapter_dim, bias=False),
|
||||
nn.Linear(adapter_dim, module.out_features, bias=False),
|
||||
)
|
||||
nn.init.zeros_(module.adapter[1].weight)
|
||||
elif isinstance(module, FrozenBNBEmbedding):
|
||||
module.adapter = nn.Sequential(
|
||||
nn.Embedding(module.num_embeddings, adapter_dim),
|
||||
nn.Linear(adapter_dim, module.embedding_dim, bias=False),
|
||||
)
|
||||
nn.init.zeros_(module.adapter[1].weight)
|
||||
|
||||
|
||||
def get_model(model_name, cache_dir, quantization):
|
||||
if quantization is None:
|
||||
model = AutoModelForCausalLM.from_pretrained(model_name, cache_dir=cache_dir)
|
||||
elif quantization == "8bit":
|
||||
raise ValueError("Loading 8-bit model. Use deepspeed instead.")
|
||||
transformers.models.gptj.modeling_gptj.GPTJBlock = GPTJBlock
|
||||
model = AutoModelForCausalLM.from_pretrained(model_name, cache_dir=cache_dir)
|
||||
add_adapters(model)
|
||||
else:
|
||||
raise ValueError(f"Unknown quantization {quantization}")
|
||||
|
||||
return model
|
||||
@@ -1,4 +1,7 @@
|
||||
accelerate==0.15.0
|
||||
datasets==2.8.0
|
||||
deepspeed==0.7.7
|
||||
mpi4py==3.1.4
|
||||
numpy==1.23.0
|
||||
PyYAML==6.0
|
||||
scikit_learn==1.2.0
|
||||
|
||||
@@ -1,32 +1,16 @@
|
||||
import argparse
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
from distutils.util import strtobool
|
||||
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
||||
from typing import Any, Dict, List, Optional, Tuple, Union
|
||||
|
||||
import torch
|
||||
from torch import nn
|
||||
from torch.utils.data import Dataset
|
||||
from transformers import (
|
||||
DataCollator,
|
||||
EvalPrediction,
|
||||
PreTrainedModel,
|
||||
PreTrainedTokenizerBase,
|
||||
Trainer,
|
||||
TrainerCallback,
|
||||
TrainingArguments,
|
||||
get_cosine_schedule_with_warmup,
|
||||
)
|
||||
from transformers import PreTrainedModel, Trainer, TrainingArguments
|
||||
from utils import get_dataset, get_loss, get_model, get_tokenizer, read_yamls
|
||||
|
||||
os.environ["WANDB_PROJECT"] = "supervised-finetuning"
|
||||
|
||||
|
||||
@dataclass
|
||||
class CustomTrainingArguments(TrainingArguments):
|
||||
loss_function: str = "CrossEntropyLoss"
|
||||
|
||||
|
||||
def compute_metrics(eval_pred):
|
||||
pred_ids = eval_pred.predictions
|
||||
labels = eval_pred.label_ids
|
||||
@@ -44,62 +28,34 @@ class SFTTrainer(Trainer):
|
||||
self,
|
||||
model: Union[PreTrainedModel, nn.Module] = None,
|
||||
args: TrainingArguments = None,
|
||||
data_collator: Optional[DataCollator] = None,
|
||||
train_dataset: Optional[Dataset] = None,
|
||||
eval_dataset: Optional[Dataset] = None,
|
||||
tokenizer: Optional[PreTrainedTokenizerBase] = None,
|
||||
model_init: Callable[[], PreTrainedModel] = None,
|
||||
compute_metrics: Optional[Callable[[EvalPrediction], Dict]] = None,
|
||||
callbacks: Optional[List[TrainerCallback]] = None,
|
||||
optimizers: Tuple[torch.optim.Optimizer, torch.optim.lr_scheduler.LambdaLR] = (None, None),
|
||||
preprocess_logits_for_metrics: Callable[[torch.Tensor, torch.Tensor], torch.Tensor] = None,
|
||||
loss_function: str = "CrossEntropyLoss",
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(
|
||||
model,
|
||||
args,
|
||||
data_collator,
|
||||
train_dataset,
|
||||
eval_dataset,
|
||||
tokenizer,
|
||||
model_init,
|
||||
compute_metrics,
|
||||
callbacks,
|
||||
optimizers,
|
||||
preprocess_logits_for_metrics,
|
||||
)
|
||||
super().__init__(model, args, **kwargs)
|
||||
|
||||
# By default CrossEntropyLoss ignores padding_index -100, but just in case use our own loss_fct
|
||||
self.loss_fct = get_loss(args.loss_function)
|
||||
|
||||
def fetch_scheduler(self):
|
||||
return get_cosine_schedule_with_warmup(
|
||||
self.optimizer,
|
||||
num_warmup_steps=self.args.warmup_steps,
|
||||
num_training_steps=self.num_train_steps,
|
||||
num_cycles=1,
|
||||
last_epoch=-1,
|
||||
)
|
||||
self.loss_fct = get_loss(loss_function)
|
||||
|
||||
def compute_loss(self, model, inputs, return_outputs=False):
|
||||
labels_mask = inputs.pop("label_masks")
|
||||
targets = inputs.pop("targets")
|
||||
|
||||
outputs = model(**inputs)
|
||||
outputs = model(input_ids=inputs["input_ids"], attention_mask=inputs.get("attention_mask", None))
|
||||
|
||||
loss = self.loss_fct(outputs.get("logits"), torch.roll(inputs["input_ids"], -1, -1), mask=labels_mask)
|
||||
loss = self.loss_fct(outputs.get("logits"), targets, mask=labels_mask)
|
||||
|
||||
return (loss, outputs) if return_outputs else loss
|
||||
|
||||
def _compute_loss(self, model, inputs):
|
||||
|
||||
labels_mask = inputs.pop("label_masks")
|
||||
|
||||
inputs = self._prepare_inputs(inputs)
|
||||
|
||||
outputs = model(**inputs)
|
||||
labels_mask = inputs.pop("label_masks")
|
||||
targets = inputs.pop("targets")
|
||||
|
||||
outputs = model(input_ids=inputs["input_ids"], attention_mask=inputs.get("attention_mask", None))
|
||||
|
||||
logits = outputs.get("logits")
|
||||
|
||||
targets = torch.roll(inputs["input_ids"], -1, -1)
|
||||
loss = self.loss_fct(outputs.get("logits"), targets, mask=labels_mask)
|
||||
|
||||
return loss, logits, targets, labels_mask
|
||||
@@ -114,7 +70,7 @@ class SFTTrainer(Trainer):
|
||||
|
||||
with torch.no_grad():
|
||||
loss, logits, labels, labels_mask = self._compute_loss(model, inputs)
|
||||
labels[~labels_mask] = -100 # padding_index
|
||||
labels[~labels_mask.bool()] = -100 # padding_index
|
||||
|
||||
loss = loss.mean().detach()
|
||||
|
||||
@@ -131,12 +87,18 @@ def _strtobool(x):
|
||||
def argument_parsing(notebook=False, notebook_args=None):
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--configs", nargs="+", required=True)
|
||||
parser.add_argument("--local_rank", type=int, default=-1)
|
||||
parser.add_argument("--deepspeed", action="store_true")
|
||||
parser.add_argument("--no-deepspeed", dest="deepspeed", action="store_false")
|
||||
parser.set_defaults(deepspeed=False)
|
||||
|
||||
if notebook:
|
||||
args, remaining = parser.parse_known_args(notebook_args)
|
||||
else:
|
||||
args, remaining = parser.parse_known_args()
|
||||
|
||||
print(args)
|
||||
|
||||
# Config from YAML
|
||||
conf = {}
|
||||
configs = read_yamls("./configs")
|
||||
@@ -147,6 +109,8 @@ def argument_parsing(notebook=False, notebook_args=None):
|
||||
else:
|
||||
conf.update(configs[name])
|
||||
|
||||
conf["local_rank"] = args.local_rank
|
||||
conf["deepspeed"] = args.deepspeed
|
||||
# Override config from command-line
|
||||
parser = argparse.ArgumentParser()
|
||||
for key, value in conf.items():
|
||||
@@ -166,13 +130,14 @@ if __name__ == "__main__":
|
||||
|
||||
train, evals, collate_fn = get_dataset(training_conf, tokenizer)
|
||||
|
||||
args = CustomTrainingArguments(
|
||||
args = TrainingArguments(
|
||||
output_dir=f"{training_conf.model_name}-{training_conf.log_dir}-finetuned",
|
||||
num_train_epochs=training_conf.num_train_epochs,
|
||||
warmup_steps=training_conf.warmup_steps,
|
||||
loss_function=training_conf.loss_fn,
|
||||
learning_rate=float(training_conf.learning_rate),
|
||||
deepspeed="configs/zero_config.json" if training_conf.deepspeed else None,
|
||||
fp16=True,
|
||||
local_rank=training_conf.local_rank,
|
||||
gradient_checkpointing=training_conf.gradient_checkpointing,
|
||||
gradient_accumulation_steps=training_conf.gradient_accumulation_steps,
|
||||
per_device_train_batch_size=training_conf.per_device_train_batch_size,
|
||||
@@ -192,6 +157,7 @@ if __name__ == "__main__":
|
||||
trainer = SFTTrainer(
|
||||
model,
|
||||
args,
|
||||
loss_function=training_conf.loss_fn,
|
||||
train_dataset=train,
|
||||
eval_dataset=evals,
|
||||
data_collator=collate_fn,
|
||||
|
||||
@@ -4,11 +4,10 @@ import yaml
|
||||
from custom_datasets import QA_SPECIAL_TOKENS, get_one_dataset
|
||||
from custom_datasets.dialogue_collator import DialogueDataCollator
|
||||
from losses import CrossEntropyLoss
|
||||
from models import freeze_top_n_layers, get_specific_model
|
||||
from sklearn.model_selection import train_test_split
|
||||
from torch.utils.data import ConcatDataset, Subset
|
||||
from transformers import AutoModelForCausalLM, AutoTokenizer
|
||||
|
||||
SUPPORTED_MODELS = ["galactica", "GPT-JT"] # deprecated ..
|
||||
from transformers import AutoTokenizer
|
||||
|
||||
|
||||
def get_tokenizer(conf):
|
||||
@@ -16,6 +15,10 @@ def get_tokenizer(conf):
|
||||
|
||||
if "galactica" in conf.model_name:
|
||||
tokenizer.add_special_tokens({"pad_token": "<pad>", "eos_token": "</s>"})
|
||||
elif "GPT-JT" in conf.model_name:
|
||||
tokenizer.add_special_tokens({"pad_token": tokenizer.eos_token, "sep_token": "<|extratoken_100|>"})
|
||||
elif "codegen" in conf.model_name:
|
||||
tokenizer.add_special_tokens({"pad_token": "<|endoftext|>", "sep_token": "<|endoftext|>"})
|
||||
|
||||
additional_special_tokens = (
|
||||
[]
|
||||
@@ -30,13 +33,7 @@ def get_tokenizer(conf):
|
||||
|
||||
|
||||
def get_model(conf, tokenizer):
|
||||
if not any([x in conf.model_name for x in SUPPORTED_MODELS]):
|
||||
raise ValueError(
|
||||
f"Model {conf.model_name} not supported. Supported models: {SUPPORTED_MODELS}. "
|
||||
"To include more make sure the masking is dne correctly... (decoder only supported for now)"
|
||||
)
|
||||
|
||||
model = AutoModelForCausalLM.from_pretrained(conf.model_name, cache_dir=conf.cache_dir)
|
||||
model = get_specific_model(conf.model_name, conf.cache_dir, conf.quantization)
|
||||
|
||||
if len(tokenizer) != model.get_input_embeddings().num_embeddings:
|
||||
assert not conf.freeze_layer, "Cannot change the number of embeddings if the model is frozen."
|
||||
@@ -95,31 +92,3 @@ def train_val_dataset(dataset, val_split=0.2):
|
||||
list(range(len(dataset))), test_size=val_split, random_state=666, shuffle=True
|
||||
)
|
||||
return Subset(dataset, train_idx), Subset(dataset, val_idx)
|
||||
|
||||
|
||||
def freeze_top_n_layers(model, target_layers):
|
||||
# its possible we can simply detect which module is a ModuleList
|
||||
# and simply freeze the module without doing string parsing
|
||||
for name, param in model.named_parameters():
|
||||
if "embed" in name:
|
||||
param.requires_grad = False
|
||||
elif ".layer" in name or ".h." in name:
|
||||
tokens = name.split(".")
|
||||
layer_ = None
|
||||
for token in tokens:
|
||||
if token.isdigit():
|
||||
layer_ = int(token)
|
||||
break
|
||||
|
||||
if layer_ is not None and layer_ < target_layers:
|
||||
# print('freeze ', layer_, name)
|
||||
param.requires_grad = False
|
||||
return model
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from transformers import AutoModelForSequenceClassification
|
||||
|
||||
model = AutoModelForSequenceClassification.from_pretrained("bigscience/bloomz-560m")
|
||||
freeze_top_n_layers(model, 10)
|
||||
print(model.state_dict().keys())
|
||||
|
||||
@@ -0,0 +1,278 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# First, we'll import the necessary libraries\n",
|
||||
"import os\n",
|
||||
"import subprocess"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"## only run from OpenAssistant not if you alread cloned the github\n",
|
||||
"# Next, we'll define the function that will clone the OpenBugger repository and install it.\n",
|
||||
"def install_openbugger():\n",
|
||||
" # First, we'll get the current working directory. This is where the repository will be cloned to.\n",
|
||||
" cwd = os.getcwd()\n",
|
||||
"\n",
|
||||
" # Next, we'll use Git to clone the repository.\n",
|
||||
" subprocess.run([\"git\", \"clone\", \"https://github.com/furlat/OpenBugger\", cwd + \"/OpenBugger\"])\n",
|
||||
"\n",
|
||||
" # Now, we'll use pip to install the package from the local repository.\n",
|
||||
" subprocess.run([\"python3\", \"-m\", \"pip\", \"install\", \"--editable\", cwd + \"/OpenBugger\"])\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Now, we'll call the function to install OpenBugger.\n",
|
||||
"install_openbugger()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Finally, we'll import SyntaxBug and LogicBug.\n",
|
||||
"from syntax.syntax_injector import SyntaxBug\n",
|
||||
"from logic.logic_injector import LogicBug"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# setup syntax bug\n",
|
||||
"syntax_bug = SyntaxBug()\n",
|
||||
"\n",
|
||||
"# Simple script\n",
|
||||
"simple_script = \"\"\"\n",
|
||||
"def greet(name):\n",
|
||||
" print(\"Hello, \" + name)\n",
|
||||
"\n",
|
||||
"greet(\"Bob\")\n",
|
||||
"\"\"\"\n",
|
||||
"\n",
|
||||
"# The simple script can be modified using the \"easy\" injection method because it only contains simple syntax and does not have any nested code blocks. This means that there are fewer characters (e.g. quotes, brackets, braces, parenthesis) that could be the target of syntax errors, and the \"easy\" injection method, which only injects errors that involve replacing or removing a single character, is sufficient to modify the script.\n",
|
||||
"print(simple_script)\n",
|
||||
"# Inject easy syntax errors into the simple script\n",
|
||||
"\n",
|
||||
"modified_simple_script, errors, counter = syntax_bug.inject(simple_script, \"easy\", 1)\n",
|
||||
"print(\"Modified version Easy\", errors, counter)\n",
|
||||
"print(modified_simple_script)\n",
|
||||
"print(\"are they the same?\", simple_script == modified_simple_script)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Medium script\n",
|
||||
"medium_script = \"\"\"\n",
|
||||
"def greet(name):\n",
|
||||
" print(\"Hello, \" + name)\n",
|
||||
" \n",
|
||||
"def greet_all(names):\n",
|
||||
" for name in names:\n",
|
||||
" greet(name)\n",
|
||||
" \n",
|
||||
"greet_all([\"Bob\", \"Alice\", \"Eve\"])\n",
|
||||
"\"\"\"\n",
|
||||
"# The medium script can be modified using the \"medium\" injection method because it contains a nested code block (the for loop in the `greet_all` function). This means that there are more characters (e.g. quotes, brackets, braces, parenthesis) that could be the target of syntax errors, and the \"medium\" injection method, which injects errors that involve replacing, removing, or adding a single character, is sufficient to modify the script.\n",
|
||||
"print(medium_script)\n",
|
||||
"# Inject medium syntax errors into the medium script\n",
|
||||
"modified_medium_script, errors, counter = syntax_bug.inject(medium_script, \"medium\", 3)\n",
|
||||
"print(\"Modified version Medium\", errors, counter)\n",
|
||||
"print(modified_medium_script)\n",
|
||||
"print(\"are they the same?\", medium_script == modified_medium_script)\n",
|
||||
"# Hard script\n",
|
||||
"hard_script = \"\"\"\n",
|
||||
"class Greeting:\n",
|
||||
" def __init__(self, greeting):\n",
|
||||
" self.greeting = greeting\n",
|
||||
" \n",
|
||||
" def greet(self, name):\n",
|
||||
" print(self.greeting + \", \" + name)\n",
|
||||
" \n",
|
||||
"greeting = Greeting(\"Hello\")\n",
|
||||
"greeting.greet(\"Bob\")\n",
|
||||
"\"\"\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Hard script\n",
|
||||
"hard_script = \"\"\"\n",
|
||||
"class Greeting:\n",
|
||||
" def __init__(self, greeting):\n",
|
||||
" self.greeting = greeting\n",
|
||||
" \n",
|
||||
" def greet(self, name):\n",
|
||||
" print(self.greeting + \", \" + name)\n",
|
||||
" \n",
|
||||
"greeting = Greeting(\"Hello\")\n",
|
||||
"greeting.greet(\"Bob\")\n",
|
||||
"\"\"\"\n",
|
||||
"\n",
|
||||
"# The hard script can be modified using the \"hard\" injection method because it contains multiple nested code blocks (the `__init__` and `greet` methods in the `Greeting` class). This means that there are even more characters (e.g. quotes, brackets, braces, parenthesis) that could be the target of syntax errors, and the \"hard\" injection method, which injects errors that involve replacing, removing, adding, or swapping characters, is sufficient to modify the script.\n",
|
||||
"print(hard_script)\n",
|
||||
"# Inject hard syntax errors into the hard script\n",
|
||||
"modified_hard_script, errors, counter = syntax_bug.inject(hard_script, \"hard\", 3)\n",
|
||||
"print(\"Modified version Hard\", errors, counter)\n",
|
||||
"print(modified_hard_script)\n",
|
||||
"print(\"are they the same?\", hard_script == modified_hard_script)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now we switch to testing the LogicBugs and show how to bug a function that is defined in the script without already having the string."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import inspect\n",
|
||||
"import random\n",
|
||||
"\n",
|
||||
"# Simple example script\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def simple_script():\n",
|
||||
" # Choose two random integers\n",
|
||||
" num1 = random.randint(0, 10)\n",
|
||||
" num2 = random.randint(0, 10)\n",
|
||||
"\n",
|
||||
" # Compare the two numbers and print a message based on their relation\n",
|
||||
" if num1 > num2:\n",
|
||||
" print(\"num1 is greater than num2\")\n",
|
||||
" elif num1 < num2:\n",
|
||||
" print(\"num1 is less than num2\")\n",
|
||||
" else:\n",
|
||||
" print(\"num1 is equal to num2\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Medium example script\n",
|
||||
"def medium_script():\n",
|
||||
" # Choose a random integer and assign it to a variable\n",
|
||||
" num = random.randint(0, 10)\n",
|
||||
"\n",
|
||||
" # Use a loop to print all numbers from 0 to the chosen integer\n",
|
||||
" for i in range(num):\n",
|
||||
" print(i)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Hard example script\n",
|
||||
"def hard_script():\n",
|
||||
" # Choose a random integer and assign it to a variable\n",
|
||||
" num = random.randint(0, 10)\n",
|
||||
"\n",
|
||||
" # Use a loop to print the square of all numbers from 0 to the chosen integer\n",
|
||||
" for i in range(num):\n",
|
||||
" print(i**2)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# create an instance of the LogicBug class\n",
|
||||
"logic_bug = LogicBug()\n",
|
||||
"\n",
|
||||
"# get the source code of the simple_script function as a string\n",
|
||||
"simple_script_str = inspect.getsource(simple_script)\n",
|
||||
"print(\"Simple\", simple_script_str)\n",
|
||||
"# inject a logic error into the simple_script function\n",
|
||||
"modified_simple_script, error, counter = logic_bug.inject(simple_script_str, \"easy\", num_errors=3)\n",
|
||||
"print(\"Modified version Simple\", error, counter)\n",
|
||||
"# print the modified simple_script function\n",
|
||||
"print(modified_simple_script)\n",
|
||||
"print(\"are they the same?\", simple_script_str == modified_simple_script)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# get the source code of the medium_script function as a string\n",
|
||||
"medium_script_str = inspect.getsource(medium_script)\n",
|
||||
"print(\"Medium\", medium_script_str)\n",
|
||||
"# inject a logic error into the medium_script function\n",
|
||||
"modified_medium_script, error, counter = logic_bug.inject(medium_script_str, \"medium\", num_errors=3)\n",
|
||||
"\n",
|
||||
"# print the modified medium_script function\n",
|
||||
"print(\"Modified version Medium\", error, counter)\n",
|
||||
"print(modified_medium_script)\n",
|
||||
"print(\"are they the same?\", medium_script_str == modified_medium_script)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# get the source code of the hard_script function as a string\n",
|
||||
"hard_script_str = inspect.getsource(hard_script)\n",
|
||||
"print(\"Hard\", hard_script_str)\n",
|
||||
"# inject a logic error into the hard_script function\n",
|
||||
"modified_hard_script, error, counter = logic_bug.inject(hard_script_str, \"hard\", num_errors=1)\n",
|
||||
"print(\"Modified version Hard\", error, counter)\n",
|
||||
"# print the modified hard_script function\n",
|
||||
"print(modified_hard_script)\n",
|
||||
"print(\"are they the same?\", hard_script_str == modified_hard_script)"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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.10.6 (tags/v3.10.6:9c7b4bd, Aug 1 2022, 21:53:49) [MSC v.1932 64 bit (AMD64)]"
|
||||
},
|
||||
"orig_nbformat": 4,
|
||||
"vscode": {
|
||||
"interpreter": {
|
||||
"hash": "ceba285e8b4e6478fe8ad229bc63940a90ad5cf3d143521e7c38823a2e915b21"
|
||||
}
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
# OpenBuggerNotebook
|
||||
|
||||
https://github.com/furlat/OpenBugger/blob/main/README.md is a Python package
|
||||
that allows you to inject syntax and logic errors into your code. This can be
|
||||
useful for testing the robustness of your code or for creating test cases for
|
||||
debugging exercises or for training an assistant to debug code.
|
||||
|
||||
The Python notebook openbugger_example.ipynb does the following:
|
||||
|
||||
1. Imports the necessary libraries to install OpenBugger in the notebookdirecory
|
||||
(os and subprocess).
|
||||
2. Defines a function, install_openbugger, which clones the OpenBugger
|
||||
repository from GitHub and installs it using pip.
|
||||
3. Calls the install_openbugger function to install OpenBugger.
|
||||
4. Imports the SyntaxBug and LogicBug classes from the syntax_injector and
|
||||
logic_injector modules, respectively.
|
||||
5. Creates an instance of the SyntaxBug class and assigns it to the syntax_bug
|
||||
variable.
|
||||
6. Defines three scripts: a simple script, a medium script, and a hard script.
|
||||
7. Calls the inject method on the simple script, passing in the string "easy" as
|
||||
the second argument and the integer 1 as the third argument. This will inject
|
||||
easy syntax errors into the script. The *item *item modified script, a list
|
||||
of the injected errors, and the number of errors injected are returned and
|
||||
assigned to variables.
|
||||
8. Prints the original and modified versions of the simple script, as well as
|
||||
the list of injected errors and the number of errors injected. 10 Repeats
|
||||
steps 7 and 8 for the medium and hard scripts, but with the "medium" and
|
||||
"hard" injection methods and different numbers of errors to inject.
|
||||
|
||||
General Usage To use OpenBugger, import the SintaxBug or LogicBug classes from
|
||||
the openbugger module and use them to inject a bug with a call to the inject().
|
||||
The injector will return the modified script with the injected bug.
|
||||
|
||||
```
|
||||
from syntax.syntax_injector import SyntaxInjector, SyntaxBug
|
||||
|
||||
syntax_bug = SyntaxBug()
|
||||
|
||||
|
||||
|
||||
# Simple script
|
||||
simple_script = """
|
||||
def greet(name):
|
||||
print("Hello, " + name)
|
||||
|
||||
greet("Bob")
|
||||
"""
|
||||
|
||||
print(simple_script)
|
||||
```
|
||||
|
||||
The simple script can be modified using the "easy" injection method because it
|
||||
only contains simple syntax and does not have any nested code blocks. This means
|
||||
that there are fewer characters (e.g. quotes, brackets, braces, parenthesis)
|
||||
that could be the target of syntax errors, and the "easy" injection method,
|
||||
which only injects errors that involve replacing or removing a single character,
|
||||
is sufficient to modify the script.
|
||||
|
||||
```
|
||||
# Inject easy syntax errors into the simple script
|
||||
|
||||
modified_simple_script, errors, counter = syntax_bug.inject(simple_script, "easy", 1)
|
||||
print("Modified version Easy",errors,counter)
|
||||
print(modified_simple_script)
|
||||
```
|
||||
|
||||
Or for higher severity and logic error by directly transforming a Python class
|
||||
into text
|
||||
|
||||
```
|
||||
import inspect
|
||||
import random
|
||||
from logic.logic_injector import LogicBug
|
||||
|
||||
|
||||
# Medium example script
|
||||
def medium_script():
|
||||
# Choose a random integer and assign it to a variable
|
||||
num = random.randint(0, 10)
|
||||
|
||||
# Use a loop to print all numbers from 0 to the chosen integer
|
||||
for i in range(num):
|
||||
print(i)
|
||||
|
||||
# create an instance of the LogicBug class
|
||||
logic_bug = LogicBug()
|
||||
# get the source code of the medium_script function as a string
|
||||
medium_script_str = inspect.getsource(medium_script)
|
||||
print("Medium",medium_script_str)
|
||||
# inject a logic error into the medium_script function
|
||||
modified_medium_script, error, counter = logic_bug.inject(medium_script_str,"medium",num_errors=3)
|
||||
```
|
||||
@@ -24,6 +24,9 @@ class TaskType(str, enum.Enum):
|
||||
rank_initial_prompts = "rank_initial_prompts"
|
||||
rank_prompter_replies = "rank_prompter_replies"
|
||||
rank_assistant_replies = "rank_assistant_replies"
|
||||
label_initial_prompt = "label_initial_prompt"
|
||||
label_assistant_reply = "label_assistant_reply"
|
||||
label_prompter_reply = "label_prompter_reply"
|
||||
done = "task_done"
|
||||
|
||||
|
||||
@@ -56,6 +59,9 @@ class OasstApiClient:
|
||||
TaskType.rank_initial_prompts: protocol_schema.RankInitialPromptsTask,
|
||||
TaskType.rank_prompter_replies: protocol_schema.RankPrompterRepliesTask,
|
||||
TaskType.rank_assistant_replies: protocol_schema.RankAssistantRepliesTask,
|
||||
TaskType.label_initial_prompt: protocol_schema.LabelInitialPromptTask,
|
||||
TaskType.label_prompter_reply: protocol_schema.LabelPrompterReplyTask,
|
||||
TaskType.label_assistant_reply: protocol_schema.LabelAssistantReplyTask,
|
||||
TaskType.done: protocol_schema.TaskDone,
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,9 @@ class TaskRequestType(str, enum.Enum):
|
||||
rank_initial_prompts = "rank_initial_prompts"
|
||||
rank_prompter_replies = "rank_prompter_replies"
|
||||
rank_assistant_replies = "rank_assistant_replies"
|
||||
label_initial_prompt = "label_initial_prompt"
|
||||
label_assistant_reply = "label_assistant_reply"
|
||||
label_prompter_reply = "label_prompter_reply"
|
||||
|
||||
|
||||
class User(BaseModel):
|
||||
@@ -169,6 +172,37 @@ class RankAssistantRepliesTask(RankConversationRepliesTask):
|
||||
type: Literal["rank_assistant_replies"] = "rank_assistant_replies"
|
||||
|
||||
|
||||
class LabelInitialPromptTask(Task):
|
||||
"""A task to label an initial prompt."""
|
||||
|
||||
type: Literal["label_initial_prompt"] = "label_initial_prompt"
|
||||
message_id: UUID
|
||||
prompt: str
|
||||
valid_labels: list[str]
|
||||
|
||||
|
||||
class LabelConversationReplyTask(Task):
|
||||
"""A task to label a reply to a conversation."""
|
||||
|
||||
type: Literal["label_conversation_reply"] = "label_conversation_reply"
|
||||
conversation: Conversation # the conversation so far
|
||||
message_id: UUID
|
||||
reply: str
|
||||
valid_labels: list[str]
|
||||
|
||||
|
||||
class LabelPrompterReplyTask(LabelConversationReplyTask):
|
||||
"""A task to label a prompter reply to a conversation."""
|
||||
|
||||
type: Literal["label_prompter_reply"] = "label_prompter_reply"
|
||||
|
||||
|
||||
class LabelAssistantReplyTask(LabelConversationReplyTask):
|
||||
"""A task to label an assistant reply to a conversation."""
|
||||
|
||||
type: Literal["label_assistant_reply"] = "label_assistant_reply"
|
||||
|
||||
|
||||
class TaskDone(Task):
|
||||
"""Signals to the frontend that the task is done."""
|
||||
|
||||
@@ -187,6 +221,10 @@ AnyTask = Union[
|
||||
RankConversationRepliesTask,
|
||||
RankPrompterRepliesTask,
|
||||
RankAssistantRepliesTask,
|
||||
LabelInitialPromptTask,
|
||||
LabelConversationReplyTask,
|
||||
LabelPrompterReplyTask,
|
||||
LabelAssistantReplyTask,
|
||||
]
|
||||
|
||||
|
||||
@@ -222,13 +260,6 @@ class MessageRanking(Interaction):
|
||||
ranking: conlist(item_type=int, min_items=1)
|
||||
|
||||
|
||||
AnyInteraction = Union[
|
||||
TextReplyToMessage,
|
||||
MessageRating,
|
||||
MessageRanking,
|
||||
]
|
||||
|
||||
|
||||
class TextLabel(str, enum.Enum):
|
||||
"""A label for a piece of text."""
|
||||
|
||||
@@ -256,12 +287,13 @@ class TextLabel(str, enum.Enum):
|
||||
slang = "slang"
|
||||
|
||||
|
||||
class TextLabels(BaseModel):
|
||||
class TextLabels(Interaction):
|
||||
"""A set of labels for a piece of text."""
|
||||
|
||||
type: Literal["text_labels"] = "text_labels"
|
||||
text: str
|
||||
labels: dict[TextLabel, float]
|
||||
message_id: str | None = None
|
||||
message_id: UUID
|
||||
|
||||
@property
|
||||
def has_message_id(self) -> bool:
|
||||
@@ -277,6 +309,14 @@ class TextLabels(BaseModel):
|
||||
return v
|
||||
|
||||
|
||||
AnyInteraction = Union[
|
||||
TextReplyToMessage,
|
||||
MessageRating,
|
||||
MessageRanking,
|
||||
TextLabels,
|
||||
]
|
||||
|
||||
|
||||
class SystemStats(BaseModel):
|
||||
all: int = 0
|
||||
active: int = 0
|
||||
|
||||
@@ -203,6 +203,60 @@ def main(backend_url: str = "http://127.0.0.1:8080", api_key: str = "DUMMY_KEY")
|
||||
)
|
||||
tasks.append(new_task)
|
||||
|
||||
case "label_initial_prompt":
|
||||
typer.echo("Label the following prompt:")
|
||||
typer.echo(task["prompt"])
|
||||
# acknowledge task
|
||||
message_id = _random_message_id()
|
||||
_post(f"/api/v1/tasks/{task['id']}/ack", {"message_id": message_id})
|
||||
|
||||
valid_labels = task["valid_labels"]
|
||||
labels_str: str = typer.prompt("Enter labels, separated by commas")
|
||||
labels = labels_str.lower().replace(" ", "").split(",")
|
||||
labels_dict = {label: "1" if label in labels else "0" for label in valid_labels}
|
||||
|
||||
# send ranking
|
||||
new_task = _post(
|
||||
"/api/v1/tasks/interaction",
|
||||
{
|
||||
"type": "text_labels",
|
||||
"message_id": task["message_id"],
|
||||
"text": task["prompt"],
|
||||
"labels": labels_dict,
|
||||
"user": USER,
|
||||
},
|
||||
)
|
||||
tasks.append(new_task)
|
||||
|
||||
case "label_prompter_reply" | "label_assistant_reply":
|
||||
typer.echo("Here is the conversation so far:")
|
||||
for message in task["conversation"]["messages"]:
|
||||
typer.echo(_render_message(message))
|
||||
|
||||
typer.echo("Label the following reply:")
|
||||
typer.echo(task["reply"])
|
||||
# acknowledge task
|
||||
message_id = _random_message_id()
|
||||
_post(f"/api/v1/tasks/{task['id']}/ack", {"message_id": message_id})
|
||||
|
||||
valid_labels = task["valid_labels"]
|
||||
labels_str: str = typer.prompt("Enter labels, separated by commas")
|
||||
labels = labels_str.lower().replace(" ", "").split(",")
|
||||
labels_dict = {label: "1" if label in labels else "0" for label in valid_labels}
|
||||
|
||||
# send ranking
|
||||
new_task = _post(
|
||||
"/api/v1/tasks/interaction",
|
||||
{
|
||||
"type": "text_labels",
|
||||
"message_id": task["message_id"],
|
||||
"text": task["prompt"],
|
||||
"labels": labels_dict,
|
||||
"user": USER,
|
||||
},
|
||||
)
|
||||
tasks.append(new_task)
|
||||
|
||||
case "task_done":
|
||||
typer.echo("Task done!")
|
||||
case _:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { OasstApiClient } from "src/lib/oasst_api_client";
|
||||
import { OasstApiClient, OasstError } from "src/lib/oasst_api_client";
|
||||
|
||||
describe("Contract test for Oasst API", function () {
|
||||
// Assumes this is running the mock server.
|
||||
@@ -44,7 +44,68 @@ describe("Contract test for Oasst API", function () {
|
||||
).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
|
||||
// Note: Below here are unittests for OasstApiClient, not contract tests. They still fit here because
|
||||
// the other contract tests are also testing the OasstApiClient.
|
||||
const callApiMethod = () => {
|
||||
return oasstApiClient.ackTask("", "");
|
||||
};
|
||||
|
||||
it("should return null for a 204 response", async () => {
|
||||
const mockFetch = cy.stub(global, "fetch").resolves({
|
||||
status: 204,
|
||||
});
|
||||
|
||||
const result = await callApiMethod();
|
||||
assert.isNull(result);
|
||||
|
||||
mockFetch.restore();
|
||||
});
|
||||
|
||||
it("should throw an OasstError with data from the response for a non-2XX response with a valid OasstError response", async () => {
|
||||
const mockFetch = cy.stub(global, "fetch").resolves({
|
||||
status: 400,
|
||||
text: () =>
|
||||
// Note: this is vulnerable to interface changes in the Oasst API.
|
||||
// The python tests use a Pydantic model to ensure this object is always valid,
|
||||
// but we don't have that here.
|
||||
// This could be a case for generating Zod schemas from OpenAPI.
|
||||
Promise.resolve(
|
||||
JSON.stringify({
|
||||
message: "error message",
|
||||
error_code: 1000,
|
||||
})
|
||||
),
|
||||
});
|
||||
|
||||
try {
|
||||
await callApiMethod();
|
||||
assert.fail();
|
||||
} catch (error) {
|
||||
assert.instanceOf(error, OasstError);
|
||||
if (error instanceof OasstError) {
|
||||
assert.equal(error.errorCode, 1000);
|
||||
assert.equal(error.message, "error message");
|
||||
assert.equal(error.httpStatusCode, 400);
|
||||
}
|
||||
}
|
||||
|
||||
mockFetch.restore();
|
||||
});
|
||||
|
||||
it("should throw a generic OasstError with the text from the response for a non-2XX response with an unknown format", async () => {
|
||||
const mockFetch = cy.stub(global, "fetch").resolves({
|
||||
status: 400,
|
||||
text: () => Promise.resolve("error message"),
|
||||
});
|
||||
|
||||
try {
|
||||
await callApiMethod();
|
||||
assert.fail();
|
||||
} catch (error) {
|
||||
assert.instanceOf(error, OasstError);
|
||||
assert.equal(error.message, "error message");
|
||||
}
|
||||
|
||||
mockFetch.restore();
|
||||
});
|
||||
});
|
||||
|
||||
Generated
+29
-616
File diff suppressed because it is too large
Load Diff
@@ -50,7 +50,6 @@
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-icons": "^4.7.1",
|
||||
"sharp": "0.31.2",
|
||||
"swr": "^2.0.0",
|
||||
"tailwindcss": "^3.2.4",
|
||||
"use-debounce": "^9.0.2"
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
export { LeaderboardTable } from "./LeaderboardTable";
|
||||
export { SideMenu } from "./SideMenu";
|
||||
export { TaskOption } from "./TaskOption";
|
||||
|
||||
@@ -17,12 +17,15 @@ import {
|
||||
Spacer,
|
||||
Tooltip,
|
||||
useBoolean,
|
||||
useColorMode,
|
||||
useColorModeValue,
|
||||
useId,
|
||||
} from "@chakra-ui/react";
|
||||
import { FlagIcon, QuestionMarkCircleIcon } from "@heroicons/react/20/solid";
|
||||
import { useState } from "react";
|
||||
import poster from "src/lib/poster";
|
||||
import useSWRMutation from "swr/mutation";
|
||||
import { colors } from "styles/Theme/colors";
|
||||
|
||||
export const FlaggableElement = (props) => {
|
||||
const [isEditing, setIsEditing] = useBoolean();
|
||||
@@ -102,7 +105,10 @@ export const FlaggableElement = (props) => {
|
||||
<Button
|
||||
isDisabled={!checkboxValues.some(Boolean)}
|
||||
onClick={submitResponse}
|
||||
className="bg-indigo-600 text-black hover:bg-indigo-700"
|
||||
className={`bg-indigo-600 text-${useColorModeValue(
|
||||
colors.light.text,
|
||||
colors.dark.text
|
||||
)} hover:bg-indigo-700`}
|
||||
>
|
||||
Report
|
||||
</Button>
|
||||
@@ -133,6 +139,12 @@ function FlagCheckbox(props: {
|
||||
}
|
||||
|
||||
const id = useId();
|
||||
const { colorMode } = useColorMode();
|
||||
|
||||
const labelTextClass =
|
||||
colorMode === "light"
|
||||
? `text-${colors.light.text} hover:text-blue-700 float-left`
|
||||
: `text-${colors.dark.text} hover:text-blue-400 float-left`;
|
||||
|
||||
return (
|
||||
<Flex gap={1}>
|
||||
@@ -143,7 +155,7 @@ function FlagCheckbox(props: {
|
||||
}}
|
||||
/>
|
||||
<label className="text-sm form-check-label" htmlFor={id}>
|
||||
<span className="text-gray-800 hover:text-blue-700 float-left">{props.option.labelText}</span>
|
||||
<span className={labelTextClass}>{props.option.labelText}</span>
|
||||
{AdditionalExplanation}
|
||||
</label>
|
||||
<Spacer />
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
// https://nextjs.org/docs/basic-features/layouts
|
||||
|
||||
import type { NextPage } from "next";
|
||||
import { FiLayout, FiMessageSquare } from "react-icons/fi";
|
||||
import { Header } from "src/components/Header";
|
||||
|
||||
import { Footer } from "./Footer";
|
||||
import { SideMenuLayout } from "./SideMenuLayout";
|
||||
|
||||
export type NextPageWithLayout<P = unknown, IP = P> = NextPage<P, IP> & {
|
||||
getLayout?: (page: React.ReactElement) => React.ReactNode;
|
||||
@@ -28,7 +30,24 @@ export const getTransparentHeaderLayout = (page: React.ReactElement) => (
|
||||
export const getDashboardLayout = (page: React.ReactElement) => (
|
||||
<div className="grid grid-rows-[min-content_1fr_min-content] h-full justify-items-stretch">
|
||||
<Header transparent={true} />
|
||||
{page}
|
||||
<SideMenuLayout
|
||||
menuButtonOptions={[
|
||||
{
|
||||
label: "Dashboard",
|
||||
pathname: "/dashboard",
|
||||
desc: "Dashboard Home",
|
||||
icon: FiLayout,
|
||||
},
|
||||
{
|
||||
label: "Messages",
|
||||
pathname: "/messages",
|
||||
desc: "Messages Dashboard",
|
||||
icon: FiMessageSquare,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{page}
|
||||
</SideMenuLayout>
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
+15
-29
@@ -1,37 +1,23 @@
|
||||
import { Box, Button, Link, Text, Tooltip, useColorMode } from "@chakra-ui/react";
|
||||
import { useRouter } from "next/router";
|
||||
import { FiLayout, FiSun, FiMessageSquare } from "react-icons/fi";
|
||||
import { FiSun } from "react-icons/fi";
|
||||
import { IconType } from "react-icons/lib";
|
||||
import { colors } from "styles/Theme/colors";
|
||||
|
||||
export function SideMenu() {
|
||||
export interface MenuButtonOption {
|
||||
label: string;
|
||||
pathname: string;
|
||||
desc: string;
|
||||
icon: IconType;
|
||||
}
|
||||
|
||||
export interface SideMenuProps {
|
||||
buttonOptions: MenuButtonOption[];
|
||||
}
|
||||
|
||||
export function SideMenu(props: SideMenuProps) {
|
||||
const router = useRouter();
|
||||
const { colorMode, toggleColorMode } = useColorMode();
|
||||
const buttonOptions = [
|
||||
{
|
||||
label: "Dashboard",
|
||||
pathname: "/dashboard",
|
||||
desc: "Dashboard Home",
|
||||
icon: FiLayout,
|
||||
},
|
||||
{
|
||||
label: "Messages",
|
||||
pathname: "/messages",
|
||||
desc: "Messages Dashboard",
|
||||
icon: FiMessageSquare,
|
||||
},
|
||||
// {
|
||||
// label: "Leaderboard",
|
||||
// pathname: "#",
|
||||
// desc: "Public Leaderboard",
|
||||
// icon: FiAward,
|
||||
// },
|
||||
// {
|
||||
// label: "Stats",
|
||||
// pathname: "#",
|
||||
// desc: "User Statistics",
|
||||
// icon: FiBarChart2,
|
||||
// },
|
||||
];
|
||||
|
||||
return (
|
||||
<main className="sticky top-0 sm:h-full">
|
||||
@@ -43,7 +29,7 @@ export function SideMenu() {
|
||||
className="grid grid-cols-4 gap-2 sm:flex sm:flex-col sm:justify-between p-4 h-full"
|
||||
>
|
||||
<nav className="grid grid-cols-3 col-span-3 sm:flex sm:flex-col gap-2">
|
||||
{buttonOptions.map((item, itemIndex) => (
|
||||
{props.buttonOptions.map((item, itemIndex) => (
|
||||
<Tooltip
|
||||
key={itemIndex}
|
||||
fontFamily="inter"
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Box, useColorMode } from "@chakra-ui/react";
|
||||
import { colors } from "styles/Theme/colors";
|
||||
import { SideMenu, MenuButtonOption } from "src/components/SideMenu";
|
||||
|
||||
interface SideMenuLayoutProps {
|
||||
menuButtonOptions: MenuButtonOption[];
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const SideMenuLayout = (props: SideMenuLayoutProps) => {
|
||||
const { colorMode } = useColorMode();
|
||||
|
||||
return (
|
||||
<Box backgroundColor={colorMode === "light" ? colors.light.bg : colors.dark.bg} className="sm:overflow-hidden">
|
||||
<Box className="sm:flex h-full gap-6">
|
||||
<Box className="p-6 sm:pr-0">
|
||||
<SideMenu buttonOptions={props.menuButtonOptions} />
|
||||
</Box>
|
||||
<Box className="flex flex-col overflow-auto p-6 sm:pl-0 gap-14">{props.children}</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Progress, Stack, Textarea, TextareaProps } from "@chakra-ui/react";
|
||||
|
||||
interface TrackedTextboxProps {
|
||||
text: string;
|
||||
thresholds: {
|
||||
low: number;
|
||||
medium: number;
|
||||
goal: number;
|
||||
};
|
||||
textareaProps?: TextareaProps;
|
||||
onTextChange: (event: React.ChangeEvent<HTMLTextAreaElement>) => void;
|
||||
}
|
||||
|
||||
export const TrackedTextarea = (props: TrackedTextboxProps) => {
|
||||
const wordCount = props.text.split(" ").length - 1;
|
||||
|
||||
let progressColor: string;
|
||||
switch (true) {
|
||||
case wordCount < props.thresholds.low:
|
||||
progressColor = "red";
|
||||
break;
|
||||
case wordCount < props.thresholds.medium:
|
||||
progressColor = "yellow";
|
||||
break;
|
||||
default:
|
||||
progressColor = "green";
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack direction={"column"}>
|
||||
<Textarea data-cy="reply" value={props.text} onChange={props.onTextChange} {...props.textareaProps} onCapture />
|
||||
<Progress size={"md"} rounded={"md"} value={wordCount} colorScheme={progressColor} max={props.thresholds.goal} />
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -5,7 +5,7 @@ declare global {
|
||||
var oasstApiClient: OasstApiClient | undefined;
|
||||
}
|
||||
|
||||
class OasstError {
|
||||
export class OasstError {
|
||||
message: string;
|
||||
errorCode: number;
|
||||
httpStatusCode: number;
|
||||
@@ -36,12 +36,13 @@ export class OasstApiClient {
|
||||
|
||||
if (resp.status >= 300) {
|
||||
const errorText = await resp.text();
|
||||
let error: any;
|
||||
try {
|
||||
const error = JSON.parse(errorText);
|
||||
throw new OasstError(error.message, error.error_code, resp.status);
|
||||
error = JSON.parse(errorText);
|
||||
} catch (e) {
|
||||
throw new OasstError(errorText, 0, resp.status);
|
||||
}
|
||||
throw new OasstError(error.message, error.error_code, resp.status);
|
||||
}
|
||||
|
||||
return await resp.json();
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Container, Textarea } from "@chakra-ui/react";
|
||||
import { Container } from "@chakra-ui/react";
|
||||
import { useColorMode } from "@chakra-ui/react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { LoadingScreen } from "src/components/Loading/LoadingScreen";
|
||||
import { Messages } from "src/components/Messages";
|
||||
import { TaskControls } from "src/components/Survey/TaskControls";
|
||||
import { TrackedTextarea } from "src/components/Survey/TrackedTextarea";
|
||||
import { TwoColumnsWithCards } from "src/components/Survey/TwoColumnsWithCards";
|
||||
import fetcher from "src/lib/fetcher";
|
||||
import poster from "src/lib/poster";
|
||||
@@ -12,8 +13,7 @@ import useSWRMutation from "swr/mutation";
|
||||
|
||||
const AssistantReply = () => {
|
||||
const [tasks, setTasks] = useState([]);
|
||||
|
||||
const inputRef = useRef<HTMLTextAreaElement>(null);
|
||||
const [inputText, setInputText] = useState("");
|
||||
|
||||
const { isLoading, mutate } = useSWRImmutable("/api/new_task/assistant_reply ", fetcher, {
|
||||
onSuccess: (data) => {
|
||||
@@ -35,7 +35,7 @@ const AssistantReply = () => {
|
||||
});
|
||||
|
||||
const submitResponse = (task: { id: string }) => {
|
||||
const text = inputRef.current.value.trim();
|
||||
const text = inputText.trim();
|
||||
trigger({
|
||||
id: task.id,
|
||||
update_type: "text_reply_to_message",
|
||||
@@ -46,10 +46,14 @@ const AssistantReply = () => {
|
||||
};
|
||||
|
||||
const fetchNextTask = () => {
|
||||
inputRef.current.value = "";
|
||||
setInputText("");
|
||||
mutate();
|
||||
};
|
||||
|
||||
const textChangeHandler = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
setInputText(event.target.value);
|
||||
};
|
||||
|
||||
const { colorMode } = useColorMode();
|
||||
const mainBgClasses = colorMode === "light" ? "bg-slate-300 text-gray-800" : "bg-slate-900 text-white";
|
||||
|
||||
@@ -71,7 +75,15 @@ const AssistantReply = () => {
|
||||
<p className="text-lg py-1">Given the following conversation, provide an adequate reply</p>
|
||||
<Messages messages={task.conversation.messages} post_id={task.id} />
|
||||
</>
|
||||
<Textarea name="reply" data-cy="reply" placeholder="Reply..." ref={inputRef} />
|
||||
<>
|
||||
<h5 className="text-lg font-semibold">Provide the assistant`s reply</h5>
|
||||
<TrackedTextarea
|
||||
text={inputText}
|
||||
onTextChange={textChangeHandler}
|
||||
thresholds={{ low: 20, medium: 40, goal: 50 }}
|
||||
textareaProps={{ placeholder: "Reply..." }}
|
||||
/>
|
||||
</>
|
||||
</TwoColumnsWithCards>
|
||||
|
||||
<TaskControls tasks={tasks} onSubmitResponse={submitResponse} onSkip={fetchNextTask} />
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Container, Textarea } from "@chakra-ui/react";
|
||||
import { Container } from "@chakra-ui/react";
|
||||
import { useColorMode } from "@chakra-ui/react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { LoadingScreen } from "src/components/Loading/LoadingScreen";
|
||||
import { TaskControls } from "src/components/Survey/TaskControls";
|
||||
import { TrackedTextarea } from "src/components/Survey/TrackedTextarea";
|
||||
import { TwoColumnsWithCards } from "src/components/Survey/TwoColumnsWithCards";
|
||||
import fetcher from "src/lib/fetcher";
|
||||
import poster from "src/lib/poster";
|
||||
@@ -11,8 +12,7 @@ import useSWRMutation from "swr/mutation";
|
||||
|
||||
const InitialPrompt = () => {
|
||||
const [tasks, setTasks] = useState([]);
|
||||
|
||||
const inputRef = useRef<HTMLTextAreaElement>(null);
|
||||
const [inputText, setInputText] = useState("");
|
||||
|
||||
const { isLoading, mutate } = useSWRImmutable("/api/new_task/initial_prompt ", fetcher, {
|
||||
onSuccess: (data) => {
|
||||
@@ -34,7 +34,7 @@ const InitialPrompt = () => {
|
||||
}, [tasks]);
|
||||
|
||||
const submitResponse = (task: { id: string }) => {
|
||||
const text = inputRef.current.value.trim();
|
||||
const text = inputText.trim();
|
||||
trigger({
|
||||
id: task.id,
|
||||
update_type: "text_reply_to_message",
|
||||
@@ -45,10 +45,14 @@ const InitialPrompt = () => {
|
||||
};
|
||||
|
||||
const fetchNextTask = () => {
|
||||
inputRef.current.value = "";
|
||||
setInputText("");
|
||||
mutate();
|
||||
};
|
||||
|
||||
const textChangeHandler = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
setInputText(event.target.value);
|
||||
};
|
||||
|
||||
const { colorMode } = useColorMode();
|
||||
const mainBgClasses = colorMode === "light" ? "bg-slate-300 text-gray-800" : "bg-slate-900 text-white";
|
||||
|
||||
@@ -60,8 +64,6 @@ const InitialPrompt = () => {
|
||||
return <Container className="p-6 text-center text-gray-800">No tasks found...</Container>;
|
||||
}
|
||||
|
||||
const task = tasks[0].task;
|
||||
|
||||
return (
|
||||
<div className={`p-12 ${mainBgClasses}`}>
|
||||
<TwoColumnsWithCards>
|
||||
@@ -69,7 +71,15 @@ const InitialPrompt = () => {
|
||||
<h5 className="text-lg font-semibold">Start a conversation</h5>
|
||||
<p className="text-lg py-1">Create an initial message to send to the assistant</p>
|
||||
</>
|
||||
<Textarea name="reply" data-cy="reply" placeholder="Question, task, greeting or similar..." ref={inputRef} />
|
||||
<>
|
||||
<h5 className="text-lg font-semibold">Provide the initial prompt</h5>
|
||||
<TrackedTextarea
|
||||
text={inputText}
|
||||
onTextChange={textChangeHandler}
|
||||
thresholds={{ low: 20, medium: 40, goal: 50 }}
|
||||
textareaProps={{ placeholder: "Question, task, greeting or similar..." }}
|
||||
/>
|
||||
</>
|
||||
</TwoColumnsWithCards>
|
||||
|
||||
<TaskControls tasks={tasks} onSubmitResponse={submitResponse} onSkip={fetchNextTask} />
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Textarea } from "@chakra-ui/react";
|
||||
import { useColorMode } from "@chakra-ui/react";
|
||||
import { useRef, useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { LoadingScreen } from "src/components/Loading/LoadingScreen";
|
||||
import { TaskControls } from "src/components/Survey/TaskControls";
|
||||
import { TrackedTextarea } from "src/components/Survey/TrackedTextarea";
|
||||
import { TwoColumnsWithCards } from "src/components/Survey/TwoColumnsWithCards";
|
||||
import fetcher from "src/lib/fetcher";
|
||||
import poster from "src/lib/poster";
|
||||
@@ -13,8 +13,7 @@ const SummarizeStory = () => {
|
||||
// Use an array of tasks that record the sequence of steps until a task is
|
||||
// deemed complete.
|
||||
const [tasks, setTasks] = useState([]);
|
||||
|
||||
const inputRef = useRef<HTMLTextAreaElement>(null);
|
||||
const [inputText, setInputText] = useState("");
|
||||
|
||||
// Fetch the very fist task. We can ignore everything except isLoading
|
||||
// because the onSuccess handler will update `tasks` when ready.
|
||||
@@ -38,7 +37,7 @@ const SummarizeStory = () => {
|
||||
// Trigger a mutation that updates the current task. We should probably
|
||||
// signal somewhere that this interaction is being processed.
|
||||
const submitResponse = (task: { id: string }) => {
|
||||
const text = inputRef.current.value.trim();
|
||||
const text = inputText.trim();
|
||||
trigger({
|
||||
id: task.id,
|
||||
update_type: "text_reply_to_message",
|
||||
@@ -49,10 +48,14 @@ const SummarizeStory = () => {
|
||||
};
|
||||
|
||||
const fetchNextTask = () => {
|
||||
inputRef.current.value = "";
|
||||
setInputText("");
|
||||
mutate();
|
||||
};
|
||||
|
||||
const textChangeHandler = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
setInputText(event.target.value);
|
||||
};
|
||||
|
||||
const { colorMode } = useColorMode();
|
||||
const mainBgClasses = colorMode === "light" ? "bg-slate-300 text-gray-800" : "bg-slate-900 text-white";
|
||||
|
||||
@@ -73,7 +76,15 @@ const SummarizeStory = () => {
|
||||
<p className="text-lg py-1">Summarize the following story</p>
|
||||
<div className="bg-slate-800 p-6 rounded-xl text-white whitespace-pre-wrap">{tasks[0].task.story}</div>
|
||||
</>
|
||||
<Textarea name="summary" placeholder="Summary" ref={inputRef} />
|
||||
<>
|
||||
<h5 className="text-lg font-semibold">Provide the assistant`s reply</h5>
|
||||
<TrackedTextarea
|
||||
text={inputText}
|
||||
onTextChange={textChangeHandler}
|
||||
thresholds={{ low: 20, medium: 40, goal: 50 }}
|
||||
textareaProps={{ placeholder: "Summary" }}
|
||||
/>
|
||||
</>
|
||||
</TwoColumnsWithCards>
|
||||
|
||||
<TaskControls tasks={tasks} onSubmitResponse={submitResponse} onSkip={fetchNextTask} />
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Textarea } from "@chakra-ui/react";
|
||||
import { useColorMode } from "@chakra-ui/react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { LoadingScreen } from "src/components/Loading/LoadingScreen";
|
||||
import { Messages } from "src/components/Messages";
|
||||
import { TaskControls } from "src/components/Survey/TaskControls";
|
||||
import { TrackedTextarea } from "src/components/Survey/TrackedTextarea";
|
||||
import { TwoColumnsWithCards } from "src/components/Survey/TwoColumnsWithCards";
|
||||
import fetcher from "src/lib/fetcher";
|
||||
import poster from "src/lib/poster";
|
||||
@@ -12,8 +12,7 @@ import useSWRMutation from "swr/mutation";
|
||||
|
||||
const UserReply = () => {
|
||||
const [tasks, setTasks] = useState([]);
|
||||
|
||||
const inputRef = useRef<HTMLTextAreaElement>(null);
|
||||
const [inputText, setInputText] = useState("");
|
||||
|
||||
const { isLoading, mutate } = useSWRImmutable("/api/new_task/prompter_reply", fetcher, {
|
||||
onSuccess: (data) => {
|
||||
@@ -35,7 +34,7 @@ const UserReply = () => {
|
||||
});
|
||||
|
||||
const submitResponse = (task: { id: string }) => {
|
||||
const text = inputRef.current.value.trim();
|
||||
const text = inputText.trim();
|
||||
trigger({
|
||||
id: task.id,
|
||||
update_type: "text_reply_to_message",
|
||||
@@ -46,10 +45,14 @@ const UserReply = () => {
|
||||
};
|
||||
|
||||
const fetchNextTask = () => {
|
||||
inputRef.current.value = "";
|
||||
setInputText("");
|
||||
mutate();
|
||||
};
|
||||
|
||||
const textChangeHandler = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
setInputText(event.target.value);
|
||||
};
|
||||
|
||||
const { colorMode } = useColorMode();
|
||||
const mainBgClasses = colorMode === "light" ? "bg-slate-300 text-gray-800" : "bg-slate-900 text-white";
|
||||
|
||||
@@ -78,7 +81,15 @@ const UserReply = () => {
|
||||
<Messages messages={task.conversation.messages} post_id={task.id} />
|
||||
{task.hint && <p className="text-lg py-1">Hint: {task.hint}</p>}
|
||||
</>
|
||||
<Textarea name="reply" data-cy="reply" placeholder="Reply..." ref={inputRef} />
|
||||
<>
|
||||
<h5 className="text-lg font-semibold">Provide the user`s reply</h5>
|
||||
<TrackedTextarea
|
||||
text={inputText}
|
||||
onTextChange={textChangeHandler}
|
||||
thresholds={{ low: 20, medium: 40, goal: 50 }}
|
||||
textareaProps={{ placeholder: "Reply..." }}
|
||||
/>
|
||||
</>
|
||||
</TwoColumnsWithCards>
|
||||
|
||||
<TaskControls tasks={tasks} onSubmitResponse={submitResponse} onSkip={fetchNextTask} />
|
||||
|
||||
@@ -1,29 +1,17 @@
|
||||
import { Box, useColorMode } from "@chakra-ui/react";
|
||||
import Head from "next/head";
|
||||
|
||||
import { getDashboardLayout } from "src/components/Layout";
|
||||
import { LeaderboardTable, SideMenu, TaskOption } from "src/components/Dashboard";
|
||||
import { colors } from "styles/Theme/colors";
|
||||
import { LeaderboardTable, TaskOption } from "src/components/Dashboard";
|
||||
|
||||
const Dashboard = () => {
|
||||
const { colorMode } = useColorMode();
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Dashboard - Open Assistant</title>
|
||||
<meta name="description" content="Chat with Open Assistant and provide feedback." />
|
||||
</Head>
|
||||
<Box backgroundColor={colorMode === "light" ? colors.light.bg : colors.dark.bg} className="sm:overflow-hidden">
|
||||
<Box className="sm:flex h-full gap-6">
|
||||
<Box className="p-6 sm:pr-0">
|
||||
<SideMenu />
|
||||
</Box>
|
||||
<Box className="flex flex-col overflow-auto p-6 sm:pl-0 gap-14">
|
||||
<TaskOption />
|
||||
<LeaderboardTable />
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
<TaskOption />
|
||||
<LeaderboardTable />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,15 +2,11 @@ import { Box, CircularProgress, SimpleGrid, Text, useColorModeValue } from "@cha
|
||||
import Head from "next/head";
|
||||
import { useState } from "react";
|
||||
import useSWRImmutable from "swr/immutable";
|
||||
|
||||
import fetcher from "src/lib/fetcher";
|
||||
import { SideMenu } from "src/components/Dashboard";
|
||||
import { MessageTable } from "src/components/Messages/MessageTable";
|
||||
import { getDashboardLayout } from "src/components/Layout";
|
||||
import { colors } from "styles/Theme/colors";
|
||||
|
||||
const MessagesDashboard = () => {
|
||||
const bgColor = useColorModeValue(colors.light.bg, colors.dark.bg);
|
||||
const boxBgColor = useColorModeValue("white", "gray.700");
|
||||
const boxAccentColor = useColorModeValue("gray.200", "gray.900");
|
||||
|
||||
@@ -35,45 +31,36 @@ const MessagesDashboard = () => {
|
||||
<title>Messages - Open Assistant</title>
|
||||
<meta name="description" content="Chat with Open Assistant and provide feedback." />
|
||||
</Head>
|
||||
<Box backgroundColor={bgColor} className="sm:overflow-hidden">
|
||||
<Box className="sm:flex h-full gap-6">
|
||||
<Box className="p-6 sm:pr-0">
|
||||
<SideMenu />
|
||||
</Box>
|
||||
<Box className="flex flex-col overflow-auto p-6 sm:pl-0 gap-14">
|
||||
<SimpleGrid columns={[1, 1, 1, 2]} gap={4}>
|
||||
<Box>
|
||||
<Text className="text-2xl font-bold" pb="4">
|
||||
Most recent messages
|
||||
</Text>
|
||||
<Box
|
||||
backgroundColor={boxBgColor}
|
||||
boxShadow="base"
|
||||
dropShadow={boxAccentColor}
|
||||
borderRadius="xl"
|
||||
className="p-6 shadow-sm"
|
||||
>
|
||||
{isLoadingAll ? <CircularProgress isIndeterminate /> : <MessageTable messages={messages} />}
|
||||
</Box>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text className="text-2xl font-bold" pb="4">
|
||||
Your most recent messages
|
||||
</Text>
|
||||
<Box
|
||||
backgroundColor={boxBgColor}
|
||||
boxShadow="base"
|
||||
dropShadow={boxAccentColor}
|
||||
borderRadius="xl"
|
||||
className="p-6 shadow-sm"
|
||||
>
|
||||
{isLoadingUser ? <CircularProgress isIndeterminate /> : <MessageTable messages={userMessages} />}
|
||||
</Box>
|
||||
</Box>
|
||||
</SimpleGrid>
|
||||
<SimpleGrid columns={[1, 1, 1, 2]} gap={4}>
|
||||
<Box>
|
||||
<Text className="text-2xl font-bold" pb="4">
|
||||
Most recent messages
|
||||
</Text>
|
||||
<Box
|
||||
backgroundColor={boxBgColor}
|
||||
boxShadow="base"
|
||||
dropShadow={boxAccentColor}
|
||||
borderRadius="xl"
|
||||
className="p-6 shadow-sm"
|
||||
>
|
||||
{isLoadingAll ? <CircularProgress isIndeterminate /> : <MessageTable messages={messages} />}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text className="text-2xl font-bold" pb="4">
|
||||
Your most recent messages
|
||||
</Text>
|
||||
<Box
|
||||
backgroundColor={boxBgColor}
|
||||
boxShadow="base"
|
||||
dropShadow={boxAccentColor}
|
||||
borderRadius="xl"
|
||||
className="p-6 shadow-sm"
|
||||
>
|
||||
{isLoadingUser ? <CircularProgress isIndeterminate /> : <MessageTable messages={userMessages} />}
|
||||
</Box>
|
||||
</Box>
|
||||
</SimpleGrid>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user