From 34ab948adeefe5e6479ce486c71de4992687d455 Mon Sep 17 00:00:00 2001 From: Bobak Hashemi Date: Sun, 1 Jan 2023 23:30:12 -0500 Subject: [PATCH 01/54] testing rankgen integration into instructor trainer --- .../instructor/configs/rankgen-t5-base.yml | 15 +++ model/reward/instructor/models.py | 22 +++++ model/reward/instructor/rank_datasets.py | 23 +++++ model/reward/instructor/requirements.txt | 3 +- model/reward/instructor/trainer.py | 91 ++++++++++++++----- model/reward/instructor/utils.py | 7 +- 6 files changed, 133 insertions(+), 28 deletions(-) create mode 100644 model/reward/instructor/configs/rankgen-t5-base.yml create mode 100644 model/reward/instructor/models.py diff --git a/model/reward/instructor/configs/rankgen-t5-base.yml b/model/reward/instructor/configs/rankgen-t5-base.yml new file mode 100644 index 00000000..7dd39777 --- /dev/null +++ b/model/reward/instructor/configs/rankgen-t5-base.yml @@ -0,0 +1,15 @@ +model_name: kalpeshk2011/rankgen-t5-base-all +tokenizer_name: google/t5-v1_1-base +learning_rate: 6e-6 +gradient_checkpointing: false +gradient_accumulation_steps: 16 +per_device_train_batch_size: 3 +warmup_steps: 600 +freeze_layer: 20 +eval_steps: 200 +save_steps: 500 +max_length: 400 +num_train_epochs: 2 +datasets: + - webgpt + - hfsummary diff --git a/model/reward/instructor/models.py b/model/reward/instructor/models.py new file mode 100644 index 00000000..699f3566 --- /dev/null +++ b/model/reward/instructor/models.py @@ -0,0 +1,22 @@ +import torch +from transformers import AutoModel + +class RankGenModel(torch.nn.Module): + def __init__(self, model_name): + super().__init__() + self.rankgen_hf_hub = model_name + assert model_name in ["kalpeshk2011/rankgen-t5-xl-all", + "kalpeshk2011/rankgen-t5-xl-pg19", + "kalpeshk2011/rankgen-t5-base-all", + "kalpeshk2011/rankgen-t5-large-all"] + self.model = AutoModel.from_pretrained(self.rankgen_hf_hub, trust_remote_code=True) + + def forward(self, prefixes, suffixes): + embedded_prefixes = self.model(**prefixes) + embedded_suffixes = self.model(**suffixes) + # take dot product of each row independently + dot_products = torch.sum(embedded_prefixes * embedded_suffixes, dim=1) + + print(f"{prefixes=}, {suffixes=}, {embedded_prefixes=}, {embedded_suffixes=}, {dot_products=}") + + return dot_products \ No newline at end of file diff --git a/model/reward/instructor/rank_datasets.py b/model/reward/instructor/rank_datasets.py index 99ba9955..3b995a7d 100644 --- a/model/reward/instructor/rank_datasets.py +++ b/model/reward/instructor/rank_datasets.py @@ -24,9 +24,32 @@ from typing import Optional, Union import numpy as np from datasets import load_dataset +import torch from torch.utils.data import Dataset from transformers.tokenization_utils_base import PaddingStrategy, PreTrainedTokenizerBase +@dataclass +class RankGenCollator(): + tokenizer: PreTrainedTokenizerBase + padding: Union[bool, str, PaddingStrategy] = True + max_length: Optional[int] = None + + def __call__(self, batch : list[dict[str, str]]) -> dict[str, torch.Tensor]: + prefixes = [] + better_answers = [] + worse_answers = [] + for question, pairs in batch: + for (pos, neg) in pairs: + prefixes.append("pre " + question) + better_answers.append("suffi " + pos) + worse_answers.append("suffi " + neg) + + tokenized_prefixes = self.tokenizer(prefixes, return_tensors="pt", padding=self.padding, max_length=self.max_length, truncation=True) + tokenized_pos = self.tokenizer(better_answers, return_tensors="pt", padding=self.padding, max_length=self.max_length, truncation=True) + tokenized_neg = self.tokenizer(worse_answers, return_tensors="pt", padding=self.padding, max_length=self.max_length, truncation=True) + return {"prefix" : tokenized_prefixes, + "positive": tokenized_pos, + "negative": tokenized_neg} @dataclass class DataCollatorForPairRank: diff --git a/model/reward/instructor/requirements.txt b/model/reward/instructor/requirements.txt index e225a2ca..eaaf36e6 100644 --- a/model/reward/instructor/requirements.txt +++ b/model/reward/instructor/requirements.txt @@ -1,6 +1,7 @@ datasets==2.8.0 evaluate==0.4.0 scikit-learn==1.2.0 -torch==1.12.1+cu116 +torch>=1.12.1 transformers==4.25.1 wandb==0.13.7 +sentencepiece==0.1.97 diff --git a/model/reward/instructor/trainer.py b/model/reward/instructor/trainer.py index 0e98e4c5..5bb1017a 100644 --- a/model/reward/instructor/trainer.py +++ b/model/reward/instructor/trainer.py @@ -7,10 +7,11 @@ from typing import Any, Callable, Dict, List, Optional, Tuple, Union import evaluate import numpy as np import torch -from rank_datasets import DataCollatorForPairRank, HFSummary, WebGPT +from rank_datasets import DataCollatorForPairRank, HFSummary, RankGenCollator, WebGPT from torch import nn from torch.utils.data import ConcatDataset, Dataset from transformers import ( + AutoModel, AutoModelForSequenceClassification, DataCollator, EvalPrediction, @@ -20,6 +21,7 @@ from transformers import ( TrainerCallback, TrainingArguments, ) +from models import RankGenModel from utils import argument_parsing, freeze_top_n_layers, get_tokenizer, train_val_dataset os.environ["WANDB_PROJECT"] = "reward-model" @@ -47,14 +49,17 @@ class RankLoss(nn.Module): self.log_sigmoid = nn.LogSigmoid() def forward(self, pos, neg): - return -self.log_sigmoid(pos - neg + self.eps).mean() + loss = -self.log_sigmoid(pos - neg + self.eps).mean() + print(f"in loss {pos=}, {neg=}, {loss=}") + return loss class RankTrainer(Trainer): def __init__( self, model: Union[PreTrainedModel, nn.Module] = None, - args: TrainingArguments = None, + model_name: str = None, + args: Optional[TrainingArguments] = None, data_collator: Optional[DataCollator] = None, train_dataset: Optional[Dataset] = None, eval_dataset: Optional[Dataset] = None, @@ -80,15 +85,26 @@ class RankTrainer(Trainer): ) self.loss_fct = RankLoss() if args.loss_function == "rank" else nn.CrossEntropyLoss() self.loss_function = args.loss_function + self.model_name = model_name def compute_loss(self, model, inputs, return_outputs=False): # forward pass - outputs = model(**inputs) - logits = outputs.get("logits").view(-1, 2) - if self.loss_function == "rank": - loss = self.loss_fct(logits[:, 0], logits[:, 1]) + if "rankgen" in self.model_name: + print(f"{inputs=}") + positive_outputs = model(inputs["prefix"], inputs["positive"]) + negative_outputs = model(inputs["prefix"], inputs["negative"]) + if self.loss_function == "rank": + loss = self.loss_fct(positive_outputs, negative_outputs) + else: + raise NotImplementedError("Only ranking loss has been implemented for rankgen model") + outputs = torch.hstack((positive_outputs, negative_outputs)) #logits else: - loss = self.loss_fct(logits, torch.zeros(logits.shape[0], device=logits.device, dtype=torch.long)) + outputs = model(**inputs) + logits = outputs.get("logits").view(-1, 2) + if self.loss_function == "rank": + loss = self.loss_fct(logits[:, 0], logits[:, 1]) + else: + loss = self.loss_fct(logits, torch.zeros(logits.shape[0], device=logits.device, dtype=torch.long)) return (loss, outputs) if return_outputs else loss @@ -110,32 +126,44 @@ class RankTrainer(Trainer): prediction_loss_only: bool, ignore_keys: Optional[List[str]] = None, ) -> Tuple[Optional[torch.Tensor], Optional[torch.Tensor], Optional[torch.Tensor]]: + with torch.inference_mode(): + if "rankgen" in self.model_name: + inputs = self._prepare_inputs(inputs) + positive_outputs = model(inputs["prefix"], inputs["positive"]) + negative_outputs = model(inputs["prefix"], inputs["negative"]) + if self.loss_function == "rank": + loss = self.loss_fct(positive_outputs, negative_outputs) + else: + raise NotImplementedError("Only ranking loss has been implemented for rankgen model") + outputs = torch.hstack((positive_outputs, negative_outputs)) # logits + return (loss, outputs, None) + else: + # compute loss on predict data + loss, logits = self._compute_loss(model, inputs) - with torch.no_grad(): - # compute loss on predict data - loss, logits = self._compute_loss(model, inputs) + loss = loss.mean().detach() + labels = torch.zeros(logits.shape[0], device=logits.device, dtype=torch.long) + if self.args.prediction_loss_only: + return (loss, None, None) - loss = loss.mean().detach() - labels = torch.zeros(logits.shape[0], device=logits.device, dtype=torch.long) - if self.args.prediction_loss_only: - return (loss, None, None) - - return (loss, logits, labels) + return (loss, logits, labels) if __name__ == "__main__": training_conf = argument_parsing(parser) model_name = training_conf["model_name"] - model = AutoModelForSequenceClassification.from_pretrained(model_name, num_labels=1, problem_type="regression") + if "rankgen-t5" in model_name: + model = RankGenModel(model_name) + else: + model = AutoModelForSequenceClassification.from_pretrained(model_name, num_labels=1, problem_type="regression") if "freeze_layer" in training_conf: num_layer = training_conf["freeze_layer"] model = freeze_top_n_layers(model, num_layer) model_parameters = filter(lambda p: p.requires_grad, model.parameters()) params = sum([np.prod(p.size()) for p in model_parameters]) print("Number of trainable : {}M".format(int(params / 1e6))) - - tokenizer = get_tokenizer(model_name) + args = CustomTrainingArguments( output_dir=f"{model_name}-finetuned", num_train_epochs=training_conf["num_train_epochs"], @@ -170,17 +198,30 @@ if __name__ == "__main__": assert len(sum_eval) > 0 evals["hfsummary"] = sum_eval train = ConcatDataset(train_datasets) - collate_fn = DataCollatorForPairRank( - tokenizer, max_length=training_conf["max_length"], drop_token_type="galactica" in model_name - ) + + if "tokenizer_name" in training_conf: + tokenizer=get_tokenizer(training_conf["tokenizer_name"]) + else: + tokenizer = get_tokenizer(model_name) + + if "rankgen" in model_name: + collate_fn = RankGenCollator( + tokenizer, max_length=training_conf["max_length"] + ) + else: + collate_fn = DataCollatorForPairRank( + tokenizer, max_length=training_conf["max_length"] + ) assert len(evals) > 0 trainer = RankTrainer( - model, - args, + model=model, + model_name=model_name, + args=args, train_dataset=train, eval_dataset=eval, data_collator=collate_fn, tokenizer=tokenizer, compute_metrics=compute_metrics, ) + # trainer.evaluate() trainer.train() diff --git a/model/reward/instructor/utils.py b/model/reward/instructor/utils.py index 9441ddb9..59165598 100644 --- a/model/reward/instructor/utils.py +++ b/model/reward/instructor/utils.py @@ -4,7 +4,7 @@ import re import yaml from sklearn.model_selection import train_test_split from torch.utils.data import Subset -from transformers import AutoTokenizer +from transformers import AutoTokenizer, T5Tokenizer re_reference_remove = re.compile(r"\[([0-9])+\]|\[([0-9])+,([0-9])+\]") @@ -26,7 +26,10 @@ def webgpt_return_format(row): def get_tokenizer(tokenizer_name): - tokenizer = AutoTokenizer.from_pretrained(tokenizer_name) + if "t5" in tokenizer_name: #rankgen + tokenizer = T5Tokenizer.from_pretrained(tokenizer_name, truncation_side="left") + else: + tokenizer = AutoTokenizer.from_pretrained(tokenizer_name) if "galactica" in tokenizer_name: tokenizer.add_special_tokens({"pad_token": "", "eos_token": ""}) From 568a42066a80198f197fb0ac42c24af3cb334795 Mon Sep 17 00:00:00 2001 From: Bobak Hashemi Date: Tue, 3 Jan 2023 00:53:07 -0500 Subject: [PATCH 02/54] FP32 Training Works --- model/reward/instructor/configs/rankgen-t5-base.yml | 3 ++- model/reward/instructor/models.py | 6 ++++-- model/reward/instructor/rank_datasets.py | 1 + model/reward/instructor/trainer.py | 4 +--- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/model/reward/instructor/configs/rankgen-t5-base.yml b/model/reward/instructor/configs/rankgen-t5-base.yml index 7dd39777..6776ad47 100644 --- a/model/reward/instructor/configs/rankgen-t5-base.yml +++ b/model/reward/instructor/configs/rankgen-t5-base.yml @@ -2,8 +2,9 @@ model_name: kalpeshk2011/rankgen-t5-base-all tokenizer_name: google/t5-v1_1-base learning_rate: 6e-6 gradient_checkpointing: false +fp16: false gradient_accumulation_steps: 16 -per_device_train_batch_size: 3 +per_device_train_batch_size: 2 warmup_steps: 600 freeze_layer: 20 eval_steps: 200 diff --git a/model/reward/instructor/models.py b/model/reward/instructor/models.py index 699f3566..dc7692bf 100644 --- a/model/reward/instructor/models.py +++ b/model/reward/instructor/models.py @@ -12,11 +12,13 @@ class RankGenModel(torch.nn.Module): self.model = AutoModel.from_pretrained(self.rankgen_hf_hub, trust_remote_code=True) def forward(self, prefixes, suffixes): + # print(list(self.model.parameters())) + # raise Exception("stop") embedded_prefixes = self.model(**prefixes) embedded_suffixes = self.model(**suffixes) # take dot product of each row independently dot_products = torch.sum(embedded_prefixes * embedded_suffixes, dim=1) - print(f"{prefixes=}, {suffixes=}, {embedded_prefixes=}, {embedded_suffixes=}, {dot_products=}") - + # print(f"{embedded_prefixes.shape=}, {embedded_suffixes.shape=}, {prefixes['input_ids'].shape=}, {suffixes['input_ids'].shape=}, {embedded_prefixes=}, {embedded_suffixes=}, {dot_products=}") + # raise Exception("stop") return dot_products \ No newline at end of file diff --git a/model/reward/instructor/rank_datasets.py b/model/reward/instructor/rank_datasets.py index 3b995a7d..965893ce 100644 --- a/model/reward/instructor/rank_datasets.py +++ b/model/reward/instructor/rank_datasets.py @@ -33,6 +33,7 @@ class RankGenCollator(): tokenizer: PreTrainedTokenizerBase padding: Union[bool, str, PaddingStrategy] = True max_length: Optional[int] = None + max_examples: Optional[int] = None def __call__(self, batch : list[dict[str, str]]) -> dict[str, torch.Tensor]: prefixes = [] diff --git a/model/reward/instructor/trainer.py b/model/reward/instructor/trainer.py index 5bb1017a..c6f58f66 100644 --- a/model/reward/instructor/trainer.py +++ b/model/reward/instructor/trainer.py @@ -50,7 +50,6 @@ class RankLoss(nn.Module): def forward(self, pos, neg): loss = -self.log_sigmoid(pos - neg + self.eps).mean() - print(f"in loss {pos=}, {neg=}, {loss=}") return loss @@ -90,7 +89,6 @@ class RankTrainer(Trainer): def compute_loss(self, model, inputs, return_outputs=False): # forward pass if "rankgen" in self.model_name: - print(f"{inputs=}") positive_outputs = model(inputs["prefix"], inputs["positive"]) negative_outputs = model(inputs["prefix"], inputs["negative"]) if self.loss_function == "rank": @@ -171,7 +169,7 @@ if __name__ == "__main__": loss_function=training_conf["loss"], learning_rate=training_conf["learning_rate"], # half_precision_backend="apex", - fp16=True, + fp16=training_conf["fp16"] if "fp16" in training_conf else True, 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"], From 45c147362e01e755cce1dc229f56c75cead1aedd Mon Sep 17 00:00:00 2001 From: Bobak Hashemi Date: Tue, 3 Jan 2023 01:41:45 -0500 Subject: [PATCH 03/54] added precommit hooks and cleaned up configs for rankgen --- .../instructor/configs/rankgen-t5-base-fp16.yml | 16 ++++++++++++++++ .../instructor/configs/rankgen-t5-base.yml | 3 +++ 2 files changed, 19 insertions(+) create mode 100644 model/reward/instructor/configs/rankgen-t5-base-fp16.yml diff --git a/model/reward/instructor/configs/rankgen-t5-base-fp16.yml b/model/reward/instructor/configs/rankgen-t5-base-fp16.yml new file mode 100644 index 00000000..c6f2a5e0 --- /dev/null +++ b/model/reward/instructor/configs/rankgen-t5-base-fp16.yml @@ -0,0 +1,16 @@ +model_name: kalpeshk2011/rankgen-t5-base-all +tokenizer_name: google/t5-v1_1-base +learning_rate: 6e-6 +gradient_checkpointing: false +fp16: true +gradient_accumulation_steps: 16 +per_device_train_batch_size: 2 +warmup_steps: 600 +freeze_layer: 20 +eval_steps: 200 +save_steps: 500 +max_length: 400 +num_train_epochs: 2 +datasets: + - webgpt + - hfsummary diff --git a/model/reward/instructor/configs/rankgen-t5-base.yml b/model/reward/instructor/configs/rankgen-t5-base.yml index 6776ad47..bcb4d613 100644 --- a/model/reward/instructor/configs/rankgen-t5-base.yml +++ b/model/reward/instructor/configs/rankgen-t5-base.yml @@ -1,4 +1,7 @@ model_name: kalpeshk2011/rankgen-t5-base-all +# model_name: kalpeshk2011/rankgen-t5-xl-all +# model_name: kalpeshk2011/rankgen-t5-xl-pg19 +# model_name: kalpeshk2011/rankgen-t5-large-all tokenizer_name: google/t5-v1_1-base learning_rate: 6e-6 gradient_checkpointing: false From b0f0705f64cf4a6e0fe855c3f6672ff9af8128f4 Mon Sep 17 00:00:00 2001 From: Karthik Raju Date: Tue, 3 Jan 2023 16:45:24 +0530 Subject: [PATCH 04/54] use discord credentials when available --- discord-bot/README.md | 14 ++++++++++++++ package-lock.json | 6 ++++++ website/.env | 3 +++ website/.gitignore | 1 + website/next.config.js | 8 ++++++++ website/src/components/Header/UserMenu.tsx | 2 +- 6 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 package-lock.json diff --git a/discord-bot/README.md b/discord-bot/README.md index 000155ae..371fd001 100644 --- a/discord-bot/README.md +++ b/discord-bot/README.md @@ -51,6 +51,20 @@ Remember to save your changes. https://discord.com/oauth2/authorize?client_id=YOUR_CLIENT_ID_HERE&permissions=8&scope=bot%20applications.commands ``` +## Discord setup for development + +- Create `DISCORD_CLIENT_ID` and `DISCORD_CLIENT_SECRET` keys in the `.env` file + in `website`. +- Go to `https://discord.com/developers/applications` and click on + `New Application` and create a new application. +- Once the new application is created, you will have access to `Client ID` and + `Client Secret` in the `OAuth2` section. Copy those values and paste for the + respective fields in the `.env` file. +- In the `Oauth2` section, there is an field called `Redirects` which has to be + provided with the following URL. This URL is nothing but the discord callback + URL which NextAuth uses - `http://localhost:3000/api/auth/callback/discord` + (The PORT number for the localhost could be different based on your setup) + ### Environment Setup To run the bot: diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..0c313854 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "Open-Assistant", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/website/.env b/website/.env index 9544836b..c2f1c7e0 100644 --- a/website/.env +++ b/website/.env @@ -12,3 +12,6 @@ NEXTAUTH_SECRET=O/M2uIbGj+lDD2oyNa8ax4jEOJqCPJzO53UbWShmq98= EMAIL_SERVER_HOST=localhost EMAIL_SERVER_PORT=1025 EMAIL_FROM=info@example.com + +DISCORD_CLIENT_ID=1058355952459452446 +DISCORD_CLIENT_SECRET=Fz_I1wnexVaCty9zFRscDBQN-gBPcil_ diff --git a/website/.gitignore b/website/.gitignore index 86e167da..5a3cffad 100644 --- a/website/.gitignore +++ b/website/.gitignore @@ -27,6 +27,7 @@ yarn-error.log* # local env files .env*.local +.env # vercel .vercel diff --git a/website/next.config.js b/website/next.config.js index 2c37ebe6..1a713ca6 100644 --- a/website/next.config.js +++ b/website/next.config.js @@ -2,6 +2,14 @@ const nextConfig = { output: "standalone", reactStrictMode: true, + images: { + remotePatterns: [ + { + protocol: 'https', + hostname: '**.discordapp.com', + }, + ], + }, experimental: { /* Disabling this for now only because it causes a warning in the console that cannot be silenced for eslint If this can be resolved, we should re-enable this. diff --git a/website/src/components/Header/UserMenu.tsx b/website/src/components/Header/UserMenu.tsx index 35b71698..280a207b 100644 --- a/website/src/components/Header/UserMenu.tsx +++ b/website/src/components/Header/UserMenu.tsx @@ -30,7 +30,7 @@ export function UserMenu() {
Profile Picture Date: Tue, 3 Jan 2023 16:46:22 +0530 Subject: [PATCH 05/54] use discord credentials when avaialble --- website/next.config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/next.config.js b/website/next.config.js index 1a713ca6..28da824f 100644 --- a/website/next.config.js +++ b/website/next.config.js @@ -5,8 +5,8 @@ const nextConfig = { images: { remotePatterns: [ { - protocol: 'https', - hostname: '**.discordapp.com', + protocol: "https", + hostname: "**.discordapp.com", }, ], }, From 1f26d4f2aa0eff6b24b6c29609a5d3c232189620 Mon Sep 17 00:00:00 2001 From: Karthik Raju Date: Tue, 3 Jan 2023 16:59:38 +0530 Subject: [PATCH 06/54] update .env to remove sensitive details --- website/.env | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/website/.env b/website/.env index c2f1c7e0..bcd39d12 100644 --- a/website/.env +++ b/website/.env @@ -11,7 +11,4 @@ NEXTAUTH_SECRET=O/M2uIbGj+lDD2oyNa8ax4jEOJqCPJzO53UbWShmq98= # The SMTP host and port found by running the jobs in /scripts/frontend-development/docker-compose.yaml EMAIL_SERVER_HOST=localhost EMAIL_SERVER_PORT=1025 -EMAIL_FROM=info@example.com - -DISCORD_CLIENT_ID=1058355952459452446 -DISCORD_CLIENT_SECRET=Fz_I1wnexVaCty9zFRscDBQN-gBPcil_ +EMAIL_FROM=info@example.com \ No newline at end of file From 4cdec519449b5303bb7fda42f30b5701404f80e5 Mon Sep 17 00:00:00 2001 From: Karthik Raju Date: Tue, 3 Jan 2023 17:01:20 +0530 Subject: [PATCH 07/54] update .gitignore file --- website/.gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/website/.gitignore b/website/.gitignore index 5a3cffad..86e167da 100644 --- a/website/.gitignore +++ b/website/.gitignore @@ -27,7 +27,6 @@ yarn-error.log* # local env files .env*.local -.env # vercel .vercel From 73178898352772fd690b7e4861b2e6b8dd2e8b8d Mon Sep 17 00:00:00 2001 From: Karthik Raju Date: Tue, 3 Jan 2023 21:39:39 +0530 Subject: [PATCH 08/54] move discord setup to website readme --- discord-bot/README.md | 15 --------------- website/README.md | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/discord-bot/README.md b/discord-bot/README.md index 371fd001..80f6c490 100644 --- a/discord-bot/README.md +++ b/discord-bot/README.md @@ -50,21 +50,6 @@ Remember to save your changes. ``` https://discord.com/oauth2/authorize?client_id=YOUR_CLIENT_ID_HERE&permissions=8&scope=bot%20applications.commands ``` - -## Discord setup for development - -- Create `DISCORD_CLIENT_ID` and `DISCORD_CLIENT_SECRET` keys in the `.env` file - in `website`. -- Go to `https://discord.com/developers/applications` and click on - `New Application` and create a new application. -- Once the new application is created, you will have access to `Client ID` and - `Client Secret` in the `OAuth2` section. Copy those values and paste for the - respective fields in the `.env` file. -- In the `Oauth2` section, there is an field called `Redirects` which has to be - provided with the following URL. This URL is nothing but the discord callback - URL which NextAuth uses - `http://localhost:3000/api/auth/callback/discord` - (The PORT number for the localhost could be different based on your setup) - ### Environment Setup To run the bot: diff --git a/website/README.md b/website/README.md index 5198a820..fb59c5d7 100644 --- a/website/README.md +++ b/website/README.md @@ -75,6 +75,20 @@ OAuth. 1. You should see a section for debug credentials. Enter any username you wish, you will be logged in as that user. +## Discord setup to enable discord authentication + +- Create `DISCORD_CLIENT_ID` and `DISCORD_CLIENT_SECRET` keys in the `.env` file + in `website`. +- Go to `https://discord.com/developers/applications` and click on + `New Application` and create a new application. +- Once the new application is created, you will have access to `Client ID` and + `Client Secret` in the `OAuth2` section. Copy those values and paste for the + respective fields in the `.env` file. +- In the `Oauth2` section, there is an field called `Redirects` which has to be + provided with the following URL. This URL is nothing but the discord callback + URL which NextAuth uses - `http://localhost:3000/api/auth/callback/discord` + (The PORT number for the localhost could be different based on your setup) + ### Using Storybook To develop components using [Storybook](https://storybook.js.org/) run From 3a10e9412d220e4a17f99151a6cbc67152b4faa5 Mon Sep 17 00:00:00 2001 From: Sotirios Anagnostidis Date: Tue, 3 Jan 2023 22:02:32 +0100 Subject: [PATCH 09/54] Question-Answer special tokens --- .../custom_datasets/__init__.py | 6 ++++++ .../custom_datasets/dialogue_collator.py | 21 +++++++++++-------- model/supervised_finetuning/trainer.py | 6 ++++-- model/supervised_finetuning/utils.py | 17 ++++++++++++++- 4 files changed, 38 insertions(+), 12 deletions(-) diff --git a/model/supervised_finetuning/custom_datasets/__init__.py b/model/supervised_finetuning/custom_datasets/__init__.py index fcab8a56..907e1a9b 100644 --- a/model/supervised_finetuning/custom_datasets/__init__.py +++ b/model/supervised_finetuning/custom_datasets/__init__.py @@ -2,6 +2,12 @@ from datasets import load_dataset from sklearn.model_selection import train_test_split from torch.utils.data import Dataset, Subset +QA_SPECIAL_TOKENS = { + 'Question': '', + 'Answer': '' +} + + class SquadV2Dataset(Dataset): def __init__(self, cache_dir, split): diff --git a/model/supervised_finetuning/custom_datasets/dialogue_collator.py b/model/supervised_finetuning/custom_datasets/dialogue_collator.py index 17fe1082..f9e1bb5e 100644 --- a/model/supervised_finetuning/custom_datasets/dialogue_collator.py +++ b/model/supervised_finetuning/custom_datasets/dialogue_collator.py @@ -6,6 +6,8 @@ import torch from torch.nn import functional as F from transformers.tokenization_utils_base import PaddingStrategy, PreTrainedTokenizerBase +from . import QA_SPECIAL_TOKENS + @dataclass class DialogueDataCollator: @@ -19,22 +21,21 @@ class DialogueDataCollator: pad_to_multiple_of: Optional[int] = None def __call__(self, features): - # TODO add special tokens for question and answer here - # additional_special_tokens = ['', ''] - prompt_tokens = ["Question: ", "Answer: "] - flatten_messages = [] label_masks = [] for messages in features: assert len(messages) % 2 == 0, "Number of messages must be even" messages = [ - (prompt_tokens[0] if i % 2 == 0 else "") + x + ((" " + prompt_tokens[1]) if i % 2 == 0 else "") + (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) ] - # Add a way for the model to terminate generation, reinitialize prompter - messages.append(prompt_tokens[0]) + # 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( @@ -47,8 +48,10 @@ class DialogueDataCollator: message_change_indices = np.cumsum([len(x) for x in messages[:-1]]) # for each token an integer indicating the index of the message it belongs to. Just to create the label mask. - # TEXT: Question: Hello, how are you? Answer: I am fine. Question: What is your name? Answer: My name is John. - # MESSAGE_INDICES: 0 0 0 0 0 0 1 1 1 2 2 2 2 2 2 3 3 3 3 + # Label mask is true when predicting a token that is part of the answer, false otherwise. + # TEXT: Question: Hello, how are you? Answer: I am fine. Question: What is your name? Answer: My name is John. Question: + # MESSAGE_INDICES: 0 0 0 0 0 0 1 1 1 2 2 2 2 2 2 3 3 3 3 -2 + # LABEL_MASK: 0 0 0 0 0 1 1 1 1 0 0 0 0 0 1 1 1 1 1 0 # If no result in next, we are predicting the last termination token(s) message_indices = list( diff --git a/model/supervised_finetuning/trainer.py b/model/supervised_finetuning/trainer.py index b44890df..dc7b5934 100644 --- a/model/supervised_finetuning/trainer.py +++ b/model/supervised_finetuning/trainer.py @@ -67,6 +67,8 @@ class SFTTrainer(Trainer): optimizers, preprocess_logits_for_metrics, ) + + # 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): @@ -112,7 +114,7 @@ class SFTTrainer(Trainer): with torch.no_grad(): loss, logits, labels, labels_mask = self._compute_loss(model, inputs) - labels[~labels_mask] = -1 + labels[~labels_mask] = -100 # padding_index loss = loss.mean().detach() @@ -159,8 +161,8 @@ def argument_parsing(notebook=False, notebook_args=None): if __name__ == "__main__": training_conf = argument_parsing() - model = get_model(training_conf) tokenizer = get_tokenizer(training_conf) + model = get_model(training_conf, tokenizer) train, evals, collate_fn = get_dataset(training_conf, tokenizer) diff --git a/model/supervised_finetuning/utils.py b/model/supervised_finetuning/utils.py index 4a451bed..6aa5d365 100644 --- a/model/supervised_finetuning/utils.py +++ b/model/supervised_finetuning/utils.py @@ -7,6 +7,7 @@ from losses import CrossEntropyLoss from sklearn.model_selection import train_test_split from torch.utils.data import ConcatDataset, Subset from transformers import AutoModelForCausalLM, AutoTokenizer +from custom_datasets import QA_SPECIAL_TOKENS SUPPORTED_MODELS = ["galactica"] @@ -17,10 +18,19 @@ def get_tokenizer(conf): if "galactica" in conf.model_name: tokenizer.add_special_tokens({"pad_token": "", "eos_token": ""}) + additional_special_tokens = ( + [] + if not "additional_special_tokens" in tokenizer.special_tokens_map + else tokenizer.special_tokens_map["additional_special_tokens"] + ) + additional_special_tokens = list(set(additional_special_tokens + list(QA_SPECIAL_TOKENS.values()))) + + tokenizer.add_special_tokens({"additional_special_tokens": additional_special_tokens}) + return tokenizer -def get_model(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}. " @@ -29,6 +39,11 @@ def get_model(conf): model = AutoModelForCausalLM.from_pretrained(conf.model_name, cache_dir=conf.cache_dir) + 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." + + model.resize_token_embeddings(len(tokenizer)) + if conf.freeze_layer: model = freeze_top_n_layers(model, conf.freeze_layer) From 525e6964e843dab51a370824626033155adc2da4 Mon Sep 17 00:00:00 2001 From: Sotirios Anagnostidis Date: Tue, 3 Jan 2023 22:06:59 +0100 Subject: [PATCH 10/54] requirements --- model/supervised_finetuning/requirements.txt | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 model/supervised_finetuning/requirements.txt diff --git a/model/supervised_finetuning/requirements.txt b/model/supervised_finetuning/requirements.txt new file mode 100644 index 00000000..d579468f --- /dev/null +++ b/model/supervised_finetuning/requirements.txt @@ -0,0 +1,6 @@ +datasets==2.8.0 +numpy==1.23.0 +PyYAML==6.0 +scikit_learn==1.2.0 +torch==1.13.1 +transformers==4.25.1 From c20dfaad5b48e4e176557378983c84f443b6dd2a Mon Sep 17 00:00:00 2001 From: Sotirios Anagnostidis Date: Tue, 3 Jan 2023 22:45:34 +0100 Subject: [PATCH 11/54] pre-commits --- model/supervised_finetuning/README.md | 4 ++-- model/supervised_finetuning/configs/config.yaml | 11 +++++++++++ .../supervised_finetuning/custom_datasets/__init__.py | 6 +----- model/supervised_finetuning/utils.py | 7 +++---- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/model/supervised_finetuning/README.md b/model/supervised_finetuning/README.md index e223e1cd..014afa95 100644 --- a/model/supervised_finetuning/README.md +++ b/model/supervised_finetuning/README.md @@ -33,6 +33,6 @@ Experimental results in wandb ## TODOS - decide on a model -- add special token to declare prompt and reply. Do nto freeze the weights for - these - Merge utils etc with reward model +- Casual Modelling for GPT-JT does not leverage the bidirectional mask for the + prompt? (https://huggingface.co/togethercomputer/GPT-JT-6B-v1) diff --git a/model/supervised_finetuning/configs/config.yaml b/model/supervised_finetuning/configs/config.yaml index f7164002..29086395 100644 --- a/model/supervised_finetuning/configs/config.yaml +++ b/model/supervised_finetuning/configs/config.yaml @@ -32,6 +32,17 @@ galactica-125: per_device_train_batch_size: 4 per_device_eval_batch_size: 4 +gpt-jt: + learning_rate: 2e-6 + model_name: togethercomputer/GPT-JT-6B-v1 + weight_decay: 0.01 + max_length: 1024 + warmup_steps: 600 + gradient_checkpointing: false + gradient_accumulation_steps: 2 + per_device_train_batch_size: 4 + per_device_eval_batch_size: 4 + debug: eval_steps: 20 eval_size: 100 diff --git a/model/supervised_finetuning/custom_datasets/__init__.py b/model/supervised_finetuning/custom_datasets/__init__.py index 907e1a9b..7e3bdc79 100644 --- a/model/supervised_finetuning/custom_datasets/__init__.py +++ b/model/supervised_finetuning/custom_datasets/__init__.py @@ -2,11 +2,7 @@ from datasets import load_dataset from sklearn.model_selection import train_test_split from torch.utils.data import Dataset, Subset -QA_SPECIAL_TOKENS = { - 'Question': '', - 'Answer': '' -} - +QA_SPECIAL_TOKENS = {"Question": "", "Answer": ""} class SquadV2Dataset(Dataset): diff --git a/model/supervised_finetuning/utils.py b/model/supervised_finetuning/utils.py index 6aa5d365..a31f74d3 100644 --- a/model/supervised_finetuning/utils.py +++ b/model/supervised_finetuning/utils.py @@ -1,15 +1,14 @@ from pathlib import Path import yaml -from custom_datasets import get_one_dataset +from custom_datasets import QA_SPECIAL_TOKENS, get_one_dataset from custom_datasets.dialogue_collator import DialogueDataCollator from losses import CrossEntropyLoss from sklearn.model_selection import train_test_split from torch.utils.data import ConcatDataset, Subset from transformers import AutoModelForCausalLM, AutoTokenizer -from custom_datasets import QA_SPECIAL_TOKENS -SUPPORTED_MODELS = ["galactica"] +SUPPORTED_MODELS = ["galactica", "GPT-JT"] # deprecated .. def get_tokenizer(conf): @@ -20,7 +19,7 @@ def get_tokenizer(conf): additional_special_tokens = ( [] - if not "additional_special_tokens" in tokenizer.special_tokens_map + if "additional_special_tokens" not in tokenizer.special_tokens_map else tokenizer.special_tokens_map["additional_special_tokens"] ) additional_special_tokens = list(set(additional_special_tokens + list(QA_SPECIAL_TOKENS.values()))) From 91099657feb90e96bc5f7216b21306bd5c60952f Mon Sep 17 00:00:00 2001 From: Jack Michaud Date: Tue, 3 Jan 2023 17:11:28 -0500 Subject: [PATCH 12/54] refactor: move new task's oasst api fetching into OasstApiClient --- website/src/lib/oasst_api_client.ts | 63 +++++++++++++++++++ website/src/pages/api/new_task/[task_type].ts | 33 ++-------- 2 files changed, 68 insertions(+), 28 deletions(-) create mode 100644 website/src/lib/oasst_api_client.ts diff --git a/website/src/lib/oasst_api_client.ts b/website/src/lib/oasst_api_client.ts new file mode 100644 index 00000000..ce61e591 --- /dev/null +++ b/website/src/lib/oasst_api_client.ts @@ -0,0 +1,63 @@ +import { JWT } from "next-auth/jwt"; + +class OasstError { + message: string; + errorCode: number; + httpStatusCode: number; + + constructor(message: string, errorCode: number, httpStatusCode: number) { + this.message = message; + this.errorCode = errorCode; + this.httpStatusCode = httpStatusCode; + } +} + +export default class OasstApiClient { + constructor(private readonly oasstApiUrl: string, private readonly oasstApiKey: string) {} + + private async post(path: string, body: any): Promise { + const resp = await fetch(`${this.oasstApiUrl}${path}`, { + method: "POST", + headers: { + "X-API-Key": this.oasstApiKey, + "Content-Type": "application/json", + }, + body: JSON.stringify(body), + }); + + if (resp.status == 204) { + return null; + } + + if (resp.status >= 300) { + try { + const error = await resp.clone().json(); + throw new OasstError(error.message, error.error_code, resp.status); + } catch (e) { + throw new OasstError(await resp.text(), 0, resp.status); + } + } + + return await resp.json(); + } + + // TODO return a strongly typed Task? + // This method is used to store a task in RegisteredTask.task. + // This is a raw Json type, so we can't use it to strongly type the task. + async fetchTask(taskType: string, userToken: JWT): Promise { + return this.post("/api/v1/tasks/", { + type: taskType, + user: { + id: userToken.sub, + display_name: userToken.name || userToken.email, + auth_method: "local", + }, + }); + } + + async ackTask(taskId: string, messageId: string): Promise { + return this.post(`/api/v1/tasks/${taskId}/ack`, { + message_id: messageId, + }); + } +} diff --git a/website/src/pages/api/new_task/[task_type].ts b/website/src/pages/api/new_task/[task_type].ts index 50f0b4e2..bbe31bef 100644 --- a/website/src/pages/api/new_task/[task_type].ts +++ b/website/src/pages/api/new_task/[task_type].ts @@ -1,4 +1,5 @@ import { getToken } from "next-auth/jwt"; +import OasstApiClient from "src/lib/oasst_api_client"; import prisma from "src/lib/prismadb"; /** @@ -20,25 +21,10 @@ const handler = async (req, res) => { return; } + const oasstApiClient = new OasstApiClient(process.env.FASTAPI_URL, process.env.FASTAPI_KEY); + // Fetch the new task. - // - // This needs to be refactored into an easier to use library. - const taskRes = await fetch(`${process.env.FASTAPI_URL}/api/v1/tasks/`, { - method: "POST", - headers: { - "X-API-Key": process.env.FASTAPI_KEY, - "Content-Type": "application/json", - }, - body: JSON.stringify({ - type: task_type, - user: { - id: token.sub, - display_name: token.name || token.email, - auth_method: "local", - }, - }), - }); - const task = await taskRes.json(); + const task = await oasstApiClient.fetchTask(task_type, token); // Store the task and link it to the user.. const registeredTask = await prisma.registeredTask.create({ @@ -53,16 +39,7 @@ const handler = async (req, res) => { }); // Update the backend with our Task ID - await fetch(`${process.env.FASTAPI_URL}/api/v1/tasks/${task.id}/ack`, { - method: "POST", - headers: { - "X-API-Key": process.env.FASTAPI_KEY, - "Content-Type": "application/json", - }, - body: JSON.stringify({ - message_id: registeredTask.id, - }), - }); + await oasstApiClient.ackTask(task.id, registeredTask.id); // Send the results to the client. res.status(200).json(registeredTask); From b7fb1325b22d23213939f8effa96bdcfe99fc700 Mon Sep 17 00:00:00 2001 From: Jack Michaud Date: Tue, 3 Jan 2023 17:12:02 -0500 Subject: [PATCH 13/54] test: add contract tests for fetchTask and ackTask --- .../e2e/oasst_api_contract_tests.cy.ts | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 website/cypress/e2e/oasst_api_contract_tests.cy.ts diff --git a/website/cypress/e2e/oasst_api_contract_tests.cy.ts b/website/cypress/e2e/oasst_api_contract_tests.cy.ts new file mode 100644 index 00000000..94545358 --- /dev/null +++ b/website/cypress/e2e/oasst_api_contract_tests.cy.ts @@ -0,0 +1,24 @@ +import OasstApiClient from "src/lib/oasst_api_client"; + +describe("Contract test for Oasst API", function () { + const oasstApiClient = new OasstApiClient("http://localhost:8080", "test"); + + it("can fetch a task", async () => { + expect( + await oasstApiClient.fetchTask("random", { + sub: "test", + name: "test", + email: "test", + }) + ).to.be.not.null; + }); + + it("can ack a task", async () => { + const task = await oasstApiClient.fetchTask("random", { + sub: "test", + name: "test", + email: "test", + }); + expect(await oasstApiClient.ackTask(task.id, "321")).to.be.null; + }); +}); From 4569bcf354a23b986463a9f70ee564785e423ef2 Mon Sep 17 00:00:00 2001 From: Bobak Hashemi Date: Tue, 3 Jan 2023 20:47:33 -0500 Subject: [PATCH 14/54] fixed linting --- model/reward/instructor/models.py | 44 +++++++++++++----------- model/reward/instructor/rank_datasets.py | 26 ++++++++------ model/reward/instructor/requirements.txt | 2 +- model/reward/instructor/trainer.py | 23 +++++-------- model/reward/instructor/utils.py | 2 +- 5 files changed, 51 insertions(+), 46 deletions(-) diff --git a/model/reward/instructor/models.py b/model/reward/instructor/models.py index dc7692bf..084cfa51 100644 --- a/model/reward/instructor/models.py +++ b/model/reward/instructor/models.py @@ -1,24 +1,28 @@ +# -*- coding: utf-8 -*- import torch from transformers import AutoModel -class RankGenModel(torch.nn.Module): - def __init__(self, model_name): - super().__init__() - self.rankgen_hf_hub = model_name - assert model_name in ["kalpeshk2011/rankgen-t5-xl-all", - "kalpeshk2011/rankgen-t5-xl-pg19", - "kalpeshk2011/rankgen-t5-base-all", - "kalpeshk2011/rankgen-t5-large-all"] - self.model = AutoModel.from_pretrained(self.rankgen_hf_hub, trust_remote_code=True) - def forward(self, prefixes, suffixes): - # print(list(self.model.parameters())) - # raise Exception("stop") - embedded_prefixes = self.model(**prefixes) - embedded_suffixes = self.model(**suffixes) - # take dot product of each row independently - dot_products = torch.sum(embedded_prefixes * embedded_suffixes, dim=1) - - # print(f"{embedded_prefixes.shape=}, {embedded_suffixes.shape=}, {prefixes['input_ids'].shape=}, {suffixes['input_ids'].shape=}, {embedded_prefixes=}, {embedded_suffixes=}, {dot_products=}") - # raise Exception("stop") - return dot_products \ No newline at end of file +class RankGenModel(torch.nn.Module): + def __init__(self, model_name): + super().__init__() + self.rankgen_hf_hub = model_name + assert model_name in [ + "kalpeshk2011/rankgen-t5-xl-all", + "kalpeshk2011/rankgen-t5-xl-pg19", + "kalpeshk2011/rankgen-t5-base-all", + "kalpeshk2011/rankgen-t5-large-all", + ] + self.model = AutoModel.from_pretrained(self.rankgen_hf_hub, trust_remote_code=True) + + def forward(self, prefixes, suffixes): + # print(list(self.model.parameters())) + # raise Exception("stop") + embedded_prefixes = self.model(**prefixes) + embedded_suffixes = self.model(**suffixes) + # take dot product of each row independently + dot_products = torch.sum(embedded_prefixes * embedded_suffixes, dim=1) + + # print(f"{embedded_prefixes.shape=}, {embedded_suffixes.shape=}, {prefixes['input_ids'].shape=}, {suffixes['input_ids'].shape=}, {embedded_prefixes=}, {embedded_suffixes=}, {dot_products=}") + # raise Exception("stop") + return dot_products diff --git a/model/reward/instructor/rank_datasets.py b/model/reward/instructor/rank_datasets.py index 965893ce..a63c9e02 100644 --- a/model/reward/instructor/rank_datasets.py +++ b/model/reward/instructor/rank_datasets.py @@ -23,19 +23,20 @@ from dataclasses import dataclass from typing import Optional, Union import numpy as np -from datasets import load_dataset import torch +from datasets import load_dataset from torch.utils.data import Dataset from transformers.tokenization_utils_base import PaddingStrategy, PreTrainedTokenizerBase + @dataclass -class RankGenCollator(): +class RankGenCollator: tokenizer: PreTrainedTokenizerBase padding: Union[bool, str, PaddingStrategy] = True max_length: Optional[int] = None max_examples: Optional[int] = None - def __call__(self, batch : list[dict[str, str]]) -> dict[str, torch.Tensor]: + def __call__(self, batch: list[dict[str, str]]) -> dict[str, torch.Tensor]: prefixes = [] better_answers = [] worse_answers = [] @@ -44,13 +45,18 @@ class RankGenCollator(): prefixes.append("pre " + question) better_answers.append("suffi " + pos) worse_answers.append("suffi " + neg) - - tokenized_prefixes = self.tokenizer(prefixes, return_tensors="pt", padding=self.padding, max_length=self.max_length, truncation=True) - tokenized_pos = self.tokenizer(better_answers, return_tensors="pt", padding=self.padding, max_length=self.max_length, truncation=True) - tokenized_neg = self.tokenizer(worse_answers, return_tensors="pt", padding=self.padding, max_length=self.max_length, truncation=True) - return {"prefix" : tokenized_prefixes, - "positive": tokenized_pos, - "negative": tokenized_neg} + + tokenized_prefixes = self.tokenizer( + prefixes, return_tensors="pt", padding=self.padding, max_length=self.max_length, truncation=True + ) + tokenized_pos = self.tokenizer( + better_answers, return_tensors="pt", padding=self.padding, max_length=self.max_length, truncation=True + ) + tokenized_neg = self.tokenizer( + worse_answers, return_tensors="pt", padding=self.padding, max_length=self.max_length, truncation=True + ) + return {"prefix": tokenized_prefixes, "positive": tokenized_pos, "negative": tokenized_neg} + @dataclass class DataCollatorForPairRank: diff --git a/model/reward/instructor/requirements.txt b/model/reward/instructor/requirements.txt index eaaf36e6..ca3935e4 100644 --- a/model/reward/instructor/requirements.txt +++ b/model/reward/instructor/requirements.txt @@ -1,7 +1,7 @@ datasets==2.8.0 evaluate==0.4.0 scikit-learn==1.2.0 +sentencepiece==0.1.97 torch>=1.12.1 transformers==4.25.1 wandb==0.13.7 -sentencepiece==0.1.97 diff --git a/model/reward/instructor/trainer.py b/model/reward/instructor/trainer.py index c6f58f66..124c28f8 100644 --- a/model/reward/instructor/trainer.py +++ b/model/reward/instructor/trainer.py @@ -7,11 +7,11 @@ from typing import Any, Callable, Dict, List, Optional, Tuple, Union import evaluate import numpy as np import torch +from models import RankGenModel from rank_datasets import DataCollatorForPairRank, HFSummary, RankGenCollator, WebGPT from torch import nn from torch.utils.data import ConcatDataset, Dataset from transformers import ( - AutoModel, AutoModelForSequenceClassification, DataCollator, EvalPrediction, @@ -21,7 +21,6 @@ from transformers import ( TrainerCallback, TrainingArguments, ) -from models import RankGenModel from utils import argument_parsing, freeze_top_n_layers, get_tokenizer, train_val_dataset os.environ["WANDB_PROJECT"] = "reward-model" @@ -95,7 +94,7 @@ class RankTrainer(Trainer): loss = self.loss_fct(positive_outputs, negative_outputs) else: raise NotImplementedError("Only ranking loss has been implemented for rankgen model") - outputs = torch.hstack((positive_outputs, negative_outputs)) #logits + outputs = torch.hstack((positive_outputs, negative_outputs)) # logits else: outputs = model(**inputs) logits = outputs.get("logits").view(-1, 2) @@ -133,7 +132,7 @@ class RankTrainer(Trainer): loss = self.loss_fct(positive_outputs, negative_outputs) else: raise NotImplementedError("Only ranking loss has been implemented for rankgen model") - outputs = torch.hstack((positive_outputs, negative_outputs)) # logits + outputs = torch.hstack((positive_outputs, negative_outputs)) # logits return (loss, outputs, None) else: # compute loss on predict data @@ -161,7 +160,7 @@ if __name__ == "__main__": model_parameters = filter(lambda p: p.requires_grad, model.parameters()) params = sum([np.prod(p.size()) for p in model_parameters]) print("Number of trainable : {}M".format(int(params / 1e6))) - + args = CustomTrainingArguments( output_dir=f"{model_name}-finetuned", num_train_epochs=training_conf["num_train_epochs"], @@ -196,20 +195,16 @@ if __name__ == "__main__": assert len(sum_eval) > 0 evals["hfsummary"] = sum_eval train = ConcatDataset(train_datasets) - + if "tokenizer_name" in training_conf: - tokenizer=get_tokenizer(training_conf["tokenizer_name"]) + tokenizer = get_tokenizer(training_conf["tokenizer_name"]) else: tokenizer = get_tokenizer(model_name) - + if "rankgen" in model_name: - collate_fn = RankGenCollator( - tokenizer, max_length=training_conf["max_length"] - ) + collate_fn = RankGenCollator(tokenizer, max_length=training_conf["max_length"]) else: - collate_fn = DataCollatorForPairRank( - tokenizer, max_length=training_conf["max_length"] - ) + collate_fn = DataCollatorForPairRank(tokenizer, max_length=training_conf["max_length"]) assert len(evals) > 0 trainer = RankTrainer( model=model, diff --git a/model/reward/instructor/utils.py b/model/reward/instructor/utils.py index 59165598..780ac9c8 100644 --- a/model/reward/instructor/utils.py +++ b/model/reward/instructor/utils.py @@ -26,7 +26,7 @@ def webgpt_return_format(row): def get_tokenizer(tokenizer_name): - if "t5" in tokenizer_name: #rankgen + if "t5" in tokenizer_name: # rankgen tokenizer = T5Tokenizer.from_pretrained(tokenizer_name, truncation_side="left") else: tokenizer = AutoTokenizer.from_pretrained(tokenizer_name) From 5ed4131720e60c38a4b0a5873f23d1c082d52d9f Mon Sep 17 00:00:00 2001 From: Jack Michaud Date: Tue, 3 Jan 2023 20:51:11 -0500 Subject: [PATCH 15/54] ci: run contract tests through separate cypress command and add into CI --- .github/workflows/test-api-contract.yaml | 10 +++++++++- scripts/frontend-development/run-contract-test.sh | 11 +++++++++++ website/cypress.config.contract.js | 9 +++++++++ .../{e2e => contract}/oasst_api_contract_tests.cy.ts | 1 + website/package.json | 1 + 5 files changed, 31 insertions(+), 1 deletion(-) create mode 100755 scripts/frontend-development/run-contract-test.sh create mode 100644 website/cypress.config.contract.js rename website/cypress/{e2e => contract}/oasst_api_contract_tests.cy.ts (93%) diff --git a/.github/workflows/test-api-contract.yaml b/.github/workflows/test-api-contract.yaml index 3707f4de..a541e887 100644 --- a/.github/workflows/test-api-contract.yaml +++ b/.github/workflows/test-api-contract.yaml @@ -15,6 +15,9 @@ jobs: - uses: actions/setup-python@v4 with: python-version: "3.10" + - uses: actions/setup-node@v3 + with: + node-version: 16 - run: cd oasst-shared && pip install -e . @@ -22,9 +25,14 @@ jobs: - run: cd backend && pip install -r requirements.txt + - run: cd frontend && npm install + - run: ./scripts/backend-development/start-mock-server.sh - - name: Run contract tests + - name: Run Python OasstApiClient contract tests run: ./scripts/oasst-shared-development/test.sh + - name: Run JavaScript OasstApiClient contract tests + run: ./scripts/frontend-development/run-contract-test.sh + - run: ./scripts/backend-development/stop-mock-server.sh diff --git a/scripts/frontend-development/run-contract-test.sh b/scripts/frontend-development/run-contract-test.sh new file mode 100755 index 00000000..6bedc903 --- /dev/null +++ b/scripts/frontend-development/run-contract-test.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +parent_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) + +# switch to website directory +pushd "$parent_path/../../website" + +set -xe + +npm run cypress:run:contract + +popd diff --git a/website/cypress.config.contract.js b/website/cypress.config.contract.js new file mode 100644 index 00000000..f4461158 --- /dev/null +++ b/website/cypress.config.contract.js @@ -0,0 +1,9 @@ +import { defineConfig } from "cypress"; + +export default defineConfig({ + e2e: { + // No baseUrl here, because we don't need it for contract testing + baseUrl: null, + specPattern: "cypress/contract/*.cy.{ts,js}", + }, +}); diff --git a/website/cypress/e2e/oasst_api_contract_tests.cy.ts b/website/cypress/contract/oasst_api_contract_tests.cy.ts similarity index 93% rename from website/cypress/e2e/oasst_api_contract_tests.cy.ts rename to website/cypress/contract/oasst_api_contract_tests.cy.ts index 94545358..2570acec 100644 --- a/website/cypress/e2e/oasst_api_contract_tests.cy.ts +++ b/website/cypress/contract/oasst_api_contract_tests.cy.ts @@ -1,6 +1,7 @@ import OasstApiClient from "src/lib/oasst_api_client"; describe("Contract test for Oasst API", function () { + // Assumes this is running the mock server. const oasstApiClient = new OasstApiClient("http://localhost:8080", "test"); it("can fetch a task", async () => { diff --git a/website/package.json b/website/package.json index c1d0c3d2..e5240727 100644 --- a/website/package.json +++ b/website/package.json @@ -12,6 +12,7 @@ "build-storybook": "build-storybook", "cypress": "cypress open", "cypress:run": "cypress run", + "cypress:run:contract": "cypress run --config-file ./cypress.config.contract.js", "cypress:image-baseline": "cypress-image-diff -u", "fix:lint": "eslint --fix src/ --ext .js,.jsx,.ts,.tsx", "fix:format": "prettier --write ./src", From 7b5f702a0013dd140953f24189f69a6d84ef9593 Mon Sep 17 00:00:00 2001 From: Jack Michaud Date: Tue, 3 Jan 2023 20:54:49 -0500 Subject: [PATCH 16/54] docs: add TODOs --- website/cypress/contract/oasst_api_contract_tests.cy.ts | 4 ++++ website/src/pages/api/update_task.ts | 1 + 2 files changed, 5 insertions(+) diff --git a/website/cypress/contract/oasst_api_contract_tests.cy.ts b/website/cypress/contract/oasst_api_contract_tests.cy.ts index 2570acec..0f9ddd00 100644 --- a/website/cypress/contract/oasst_api_contract_tests.cy.ts +++ b/website/cypress/contract/oasst_api_contract_tests.cy.ts @@ -22,4 +22,8 @@ describe("Contract test for Oasst API", function () { }); expect(await oasstApiClient.ackTask(task.id, "321")).to.be.null; }); + + // TODO Add test for 204 + // TODO Add test for parsing >=300, throwing an OasstError + // TODO Add test for parsing >=300, throwing a generic error }); diff --git a/website/src/pages/api/update_task.ts b/website/src/pages/api/update_task.ts index 9582040b..5e887175 100644 --- a/website/src/pages/api/update_task.ts +++ b/website/src/pages/api/update_task.ts @@ -36,6 +36,7 @@ const handler = async (req, res) => { // Send the interaction to the Task Backend. This automatically fetches the // next task in the sequence (or the done task). + // TODO Move this into OasstApiClient. const interactionRes = await fetch(`${process.env.FASTAPI_URL}/api/v1/tasks/interaction`, { method: "POST", headers: { From a95e71d6f9b8c46d8f6d875cb1cce12bfd549e63 Mon Sep 17 00:00:00 2001 From: Jack Michaud Date: Tue, 3 Jan 2023 20:59:37 -0500 Subject: [PATCH 17/54] ci: cd into correct directory to install deps --- .github/workflows/test-api-contract.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-api-contract.yaml b/.github/workflows/test-api-contract.yaml index a541e887..4ca36da0 100644 --- a/.github/workflows/test-api-contract.yaml +++ b/.github/workflows/test-api-contract.yaml @@ -25,7 +25,7 @@ jobs: - run: cd backend && pip install -r requirements.txt - - run: cd frontend && npm install + - run: cd website && npm install - run: ./scripts/backend-development/start-mock-server.sh From da79aa04a0e4d4293b1427395e4a4a5770ea577d Mon Sep 17 00:00:00 2001 From: Bobak Hashemi Date: Tue, 3 Jan 2023 21:45:16 -0500 Subject: [PATCH 18/54] Cleaned up default argument logic. --- model/reward/instructor/trainer.py | 9 +++------ model/reward/instructor/utils.py | 9 ++++++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/model/reward/instructor/trainer.py b/model/reward/instructor/trainer.py index 124c28f8..2eee8b8d 100644 --- a/model/reward/instructor/trainer.py +++ b/model/reward/instructor/trainer.py @@ -168,7 +168,7 @@ if __name__ == "__main__": loss_function=training_conf["loss"], learning_rate=training_conf["learning_rate"], # half_precision_backend="apex", - fp16=training_conf["fp16"] if "fp16" in training_conf else True, + fp16=training_conf["fp16"], 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"], @@ -180,7 +180,7 @@ if __name__ == "__main__": evaluation_strategy="steps", eval_steps=training_conf["eval_steps"], save_steps=1000, - report_to="wandb", + report_to="local", ) train_datasets, evals = [], {} if "webgpt" in training_conf["datasets"]: @@ -196,10 +196,7 @@ if __name__ == "__main__": evals["hfsummary"] = sum_eval train = ConcatDataset(train_datasets) - if "tokenizer_name" in training_conf: - tokenizer = get_tokenizer(training_conf["tokenizer_name"]) - else: - tokenizer = get_tokenizer(model_name) + tokenizer = get_tokenizer(training_conf["tokenizer_name"]) if "rankgen" in model_name: collate_fn = RankGenCollator(tokenizer, max_length=training_conf["max_length"]) diff --git a/model/reward/instructor/utils.py b/model/reward/instructor/utils.py index 780ac9c8..7946fbb2 100644 --- a/model/reward/instructor/utils.py +++ b/model/reward/instructor/utils.py @@ -71,6 +71,10 @@ def freeze_top_n_layers(model, target_layers): def argument_parsing(parser): + args = parser.parse_args() + with open(args.config, "r", encoding="utf-8") as f: + training_conf = yaml.safe_load(f.read()) + default_params = { "num_train_epochs": 4, "learning_rate": 3e-5, @@ -82,10 +86,9 @@ def argument_parsing(parser): "gradient_accumulation_steps": 8, "gradient_checkpointing": False, "datasets": ["webgpt"], + "fp16": True, + "tokenizer_name": training_conf["model_name"], } - args = parser.parse_args() - with open(args.config, "r", encoding="utf-8") as f: - training_conf = yaml.safe_load(f.read()) params = {**default_params, **training_conf} params["gradient_accumulation_steps"] = int(params["gradient_accumulation_steps"]) From 556391bf491054abc47f01f3eb40de2bdd652b6d Mon Sep 17 00:00:00 2001 From: Keith Stevens Date: Wed, 4 Jan 2023 17:20:51 +0900 Subject: [PATCH 19/54] Adding the Redis secret for running the backend on AWS --- copilot/api/manifest.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/copilot/api/manifest.yml b/copilot/api/manifest.yml index b9262b51..b6ff6cf7 100644 --- a/copilot/api/manifest.yml +++ b/copilot/api/manifest.yml @@ -36,3 +36,4 @@ environments: secrets: # Note: URI, not URL. DATABASE_URI: /copilot/${COPILOT_APPLICATION_NAME}/${COPILOT_ENVIRONMENT_NAME}/secrets/API_DATABASE_URL + REDIS_HOST: /copilot/${COPILOT_APPLICATION_NAME}/${COPILOT_ENVIRONMENT_NAME}/secrets/REDIS_HOST From a2adfd45515cf01bd7708dae5a1ab3c74b538e73 Mon Sep 17 00:00:00 2001 From: Karthik Raju Date: Wed, 4 Jan 2023 17:37:55 +0530 Subject: [PATCH 20/54] update readme --- discord-bot/README.md | 1 + website/README.md | 15 --------------- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/discord-bot/README.md b/discord-bot/README.md index 80f6c490..000155ae 100644 --- a/discord-bot/README.md +++ b/discord-bot/README.md @@ -50,6 +50,7 @@ Remember to save your changes. ``` https://discord.com/oauth2/authorize?client_id=YOUR_CLIENT_ID_HERE&permissions=8&scope=bot%20applications.commands ``` + ### Environment Setup To run the bot: diff --git a/website/README.md b/website/README.md index fb59c5d7..d861f552 100644 --- a/website/README.md +++ b/website/README.md @@ -74,21 +74,6 @@ OAuth. 1. Use the `Login` button in the top right to go to the login page. 1. You should see a section for debug credentials. Enter any username you wish, you will be logged in as that user. - -## Discord setup to enable discord authentication - -- Create `DISCORD_CLIENT_ID` and `DISCORD_CLIENT_SECRET` keys in the `.env` file - in `website`. -- Go to `https://discord.com/developers/applications` and click on - `New Application` and create a new application. -- Once the new application is created, you will have access to `Client ID` and - `Client Secret` in the `OAuth2` section. Copy those values and paste for the - respective fields in the `.env` file. -- In the `Oauth2` section, there is an field called `Redirects` which has to be - provided with the following URL. This URL is nothing but the discord callback - URL which NextAuth uses - `http://localhost:3000/api/auth/callback/discord` - (The PORT number for the localhost could be different based on your setup) - ### Using Storybook To develop components using [Storybook](https://storybook.js.org/) run From dda5514baebe7cf9a32fecafe32ca0743fcc7d1a Mon Sep 17 00:00:00 2001 From: Karthik Raju Date: Wed, 4 Jan 2023 17:38:55 +0530 Subject: [PATCH 21/54] update readme file --- website/.env | 2 +- website/README.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/website/.env b/website/.env index bcd39d12..9544836b 100644 --- a/website/.env +++ b/website/.env @@ -11,4 +11,4 @@ NEXTAUTH_SECRET=O/M2uIbGj+lDD2oyNa8ax4jEOJqCPJzO53UbWShmq98= # The SMTP host and port found by running the jobs in /scripts/frontend-development/docker-compose.yaml EMAIL_SERVER_HOST=localhost EMAIL_SERVER_PORT=1025 -EMAIL_FROM=info@example.com \ No newline at end of file +EMAIL_FROM=info@example.com diff --git a/website/README.md b/website/README.md index d861f552..5198a820 100644 --- a/website/README.md +++ b/website/README.md @@ -74,6 +74,7 @@ OAuth. 1. Use the `Login` button in the top right to go to the login page. 1. You should see a section for debug credentials. Enter any username you wish, you will be logged in as that user. + ### Using Storybook To develop components using [Storybook](https://storybook.js.org/) run From bdcfae54fc1ed5edd8d8c7235a859e169e65a8a9 Mon Sep 17 00:00:00 2001 From: Jack Michaud Date: Wed, 4 Jan 2023 08:04:27 -0500 Subject: [PATCH 22/54] docs: update TODOs to include issue numbers --- website/cypress/contract/oasst_api_contract_tests.cy.ts | 6 +++--- website/src/pages/api/update_task.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/website/cypress/contract/oasst_api_contract_tests.cy.ts b/website/cypress/contract/oasst_api_contract_tests.cy.ts index 0f9ddd00..ff5bb156 100644 --- a/website/cypress/contract/oasst_api_contract_tests.cy.ts +++ b/website/cypress/contract/oasst_api_contract_tests.cy.ts @@ -23,7 +23,7 @@ describe("Contract test for Oasst API", function () { expect(await oasstApiClient.ackTask(task.id, "321")).to.be.null; }); - // TODO Add test for 204 - // TODO Add test for parsing >=300, throwing an OasstError - // TODO Add test for parsing >=300, throwing a generic error + // 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 }); diff --git a/website/src/pages/api/update_task.ts b/website/src/pages/api/update_task.ts index 5e887175..2d371354 100644 --- a/website/src/pages/api/update_task.ts +++ b/website/src/pages/api/update_task.ts @@ -36,7 +36,7 @@ const handler = async (req, res) => { // Send the interaction to the Task Backend. This automatically fetches the // next task in the sequence (or the done task). - // TODO Move this into OasstApiClient. + // TODO(#353): Move this into OasstApiClient. const interactionRes = await fetch(`${process.env.FASTAPI_URL}/api/v1/tasks/interaction`, { method: "POST", headers: { From 088bc181bdb53d397221e24a26a71534c2b1ab63 Mon Sep 17 00:00:00 2001 From: Klotske Date: Wed, 4 Jan 2023 16:15:22 +0300 Subject: [PATCH 23/54] Add tooltip to Report button --- website/src/components/FlaggableElement.tsx | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/website/src/components/FlaggableElement.tsx b/website/src/components/FlaggableElement.tsx index c8fc17ce..bc245bd2 100644 --- a/website/src/components/FlaggableElement.tsx +++ b/website/src/components/FlaggableElement.tsx @@ -15,6 +15,7 @@ import { SliderThumb, SliderTrack, Spacer, + Tooltip, useBoolean, useId, } from "@chakra-ui/react"; @@ -69,11 +70,15 @@ export const FlaggableElement = (props) => { > {props.children} - - - + +
+ + + +
+
From b24bfd1d7421021e66896293f57a9d7289d8c234 Mon Sep 17 00:00:00 2001 From: jojopirker Date: Wed, 4 Jan 2023 15:02:45 +0100 Subject: [PATCH 24/54] new page to display recent messages --- website/src/components/Dashboard/SideMenu.tsx | 8 +- website/src/pages/api/messages.ts | 34 ++++++ website/src/pages/messages.tsx | 106 ++++++++++++++++++ 3 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 website/src/pages/api/messages.ts create mode 100644 website/src/pages/messages.tsx diff --git a/website/src/components/Dashboard/SideMenu.tsx b/website/src/components/Dashboard/SideMenu.tsx index 30a45777..499117a2 100644 --- a/website/src/components/Dashboard/SideMenu.tsx +++ b/website/src/components/Dashboard/SideMenu.tsx @@ -1,6 +1,6 @@ import { Box, Button, Link, Text, Tooltip, useColorMode } from "@chakra-ui/react"; import { useRouter } from "next/router"; -import { FiLayout, FiSun } from "react-icons/fi"; +import { FiLayout, FiSun, FiMessageSquare } from "react-icons/fi"; import { colors } from "styles/Theme/colors"; export function SideMenu() { @@ -13,6 +13,12 @@ export function SideMenu() { desc: "Dashboard Home", icon: FiLayout, }, + { + label: "Messages", + pathname: "/messages", + desc: "Messages Dashboard", + icon: FiMessageSquare, + }, // { // label: "Leaderboard", // pathname: "#", diff --git a/website/src/pages/api/messages.ts b/website/src/pages/api/messages.ts new file mode 100644 index 00000000..a6a6d981 --- /dev/null +++ b/website/src/pages/api/messages.ts @@ -0,0 +1,34 @@ +import { boolean } from "boolean"; +import { getToken } from "next-auth/jwt"; + + +const handler = async (req, res) => { + const token = await getToken({ req }); + + // Return nothing if the user isn't registered. + if (!token) { + res.status(401).end(); + return; + } + + //TODO: add params if needed + const reqParams = req.query; + console.log(reqParams); + const params = new URLSearchParams({ + username: boolean(reqParams.allUsers)? "" : token.sub, + }); +console.log(params); + const messagesRes = await fetch(`${process.env.FASTAPI_URL}/api/v1/messages?${params}`, { + method: "GET", + headers: { + "X-API-Key": process.env.FASTAPI_KEY, + "Content-Type": "application/json", + }, + },); + const messages = await messagesRes.json(); + + // Send recieved messages to the client. + res.status(200).json(messages); +} + +export default handler; diff --git a/website/src/pages/messages.tsx b/website/src/pages/messages.tsx new file mode 100644 index 00000000..f8512ae3 --- /dev/null +++ b/website/src/pages/messages.tsx @@ -0,0 +1,106 @@ +import { Box, CircularProgress, HStack, Image, SimpleGrid, Stack, StackDivider, Text, useColorMode, useColorModeValue } from "@chakra-ui/react"; +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 { FlaggableElement } from "src/components/FlaggableElement"; +import { Header } from "src/components/Header"; +import { colors } from "styles/Theme/colors"; + +const MessageTable = ({ messages, isLoading }) => { + const backgroundColor = useColorModeValue("white", "gray.700"); + const accentColor = useColorModeValue("gray.200", "gray.900"); + + return ( + + {isLoading ? + : + } spacing="4"> + {messages.map((item, idx) => + + )} + + } + + ); +} + +const MesssageTableEntry = ({ item, idx }) => { + const bgColor = useColorModeValue(idx % 2 === 0 ? "bg-slate-800" : "bg-black", "bg-sky-900"); + + return ( + + + {item.isAssistant ? + Profile Picture + : Profile Picture} + + {item.text} + + + + ) +} + +const MessagesDashboard = () => { + const bgColor = useColorModeValue(colors.light.bg, colors.dark.bg); + + const [messages, setMessages] = useState([]); + const [userMessages, setUserMessages] = useState([]); + + const { isLoading: isLoadingAll } = useSWRImmutable("/api/messages?allUsers=true", fetcher, { + onSuccess: (data) => { + setMessages(data); + }, + }); + + const { isLoading: isLoadingUser } = useSWRImmutable(`/api/messages`, fetcher, { + onSuccess: (data) => { + setUserMessages(data); + }, + }); + + return ( + <> + + Messages - Open Assistant + + + + + + + + + + + Most recent messages + + + + Your most recent messages + + + + + + + + ); +}; + +MessagesDashboard.getLayout = (page) => ( +
+
+ {page} +
+); + +export default MessagesDashboard; From dba693ee8511c1736cf414145f155e816e69d726 Mon Sep 17 00:00:00 2001 From: jojopirker Date: Wed, 4 Jan 2023 15:13:08 +0100 Subject: [PATCH 25/54] using avatar instead of image & pre-commit --- website/src/pages/api/messages.ts | 48 ++++----- website/src/pages/messages.tsx | 171 ++++++++++++++++-------------- 2 files changed, 115 insertions(+), 104 deletions(-) diff --git a/website/src/pages/api/messages.ts b/website/src/pages/api/messages.ts index a6a6d981..53b529b5 100644 --- a/website/src/pages/api/messages.ts +++ b/website/src/pages/api/messages.ts @@ -1,34 +1,32 @@ import { boolean } from "boolean"; import { getToken } from "next-auth/jwt"; - const handler = async (req, res) => { - const token = await getToken({ req }); + const token = await getToken({ req }); - // Return nothing if the user isn't registered. - if (!token) { - res.status(401).end(); - return; - } + // Return nothing if the user isn't registered. + if (!token) { + res.status(401).end(); + return; + } - //TODO: add params if needed - const reqParams = req.query; - console.log(reqParams); - const params = new URLSearchParams({ - username: boolean(reqParams.allUsers)? "" : token.sub, - }); -console.log(params); - const messagesRes = await fetch(`${process.env.FASTAPI_URL}/api/v1/messages?${params}`, { - method: "GET", - headers: { - "X-API-Key": process.env.FASTAPI_KEY, - "Content-Type": "application/json", - }, - },); - const messages = await messagesRes.json(); + //TODO: add params if needed + const reqParams = req.query; + const params = new URLSearchParams({ + username: boolean(reqParams.allUsers) ? "" : token.sub, + }); - // Send recieved messages to the client. - res.status(200).json(messages); -} + const messagesRes = await fetch(`${process.env.FASTAPI_URL}/api/v1/messages?${params}`, { + method: "GET", + headers: { + "X-API-Key": process.env.FASTAPI_KEY, + "Content-Type": "application/json", + }, + }); + const messages = await messagesRes.json(); + + // Send recieved messages to the client. + res.status(200).json(messages); +}; export default handler; diff --git a/website/src/pages/messages.tsx b/website/src/pages/messages.tsx index f8512ae3..7509922d 100644 --- a/website/src/pages/messages.tsx +++ b/website/src/pages/messages.tsx @@ -1,4 +1,16 @@ -import { Box, CircularProgress, HStack, Image, SimpleGrid, Stack, StackDivider, Text, useColorMode, useColorModeValue } from "@chakra-ui/react"; +import { + Avatar, + Box, + CircularProgress, + HStack, + Image, + SimpleGrid, + Stack, + StackDivider, + Text, + useColorMode, + useColorModeValue, +} from "@chakra-ui/react"; import Head from "next/head"; import { useState } from "react"; import useSWRImmutable from "swr/immutable"; @@ -10,97 +22,98 @@ import { Header } from "src/components/Header"; import { colors } from "styles/Theme/colors"; const MessageTable = ({ messages, isLoading }) => { - const backgroundColor = useColorModeValue("white", "gray.700"); - const accentColor = useColorModeValue("gray.200", "gray.900"); + const backgroundColor = useColorModeValue("white", "gray.700"); + const accentColor = useColorModeValue("gray.200", "gray.900"); - return ( - - {isLoading ? - : - } spacing="4"> - {messages.map((item, idx) => - - )} - - } - - ); -} + return ( + + {isLoading ? ( + + ) : ( + } spacing="4"> + {messages.map((item, idx) => ( + + ))} + + )} + + ); +}; const MesssageTableEntry = ({ item, idx }) => { - const bgColor = useColorModeValue(idx % 2 === 0 ? "bg-slate-800" : "bg-black", "bg-sky-900"); + const bgColor = useColorModeValue(idx % 2 === 0 ? "bg-slate-800" : "bg-black", "bg-sky-900"); - return ( - - - {item.isAssistant ? - Profile Picture - : Profile Picture} - - {item.text} - - - - ) -} + return ( + + + + {item.text} + + + ); +}; const MessagesDashboard = () => { - const bgColor = useColorModeValue(colors.light.bg, colors.dark.bg); + const bgColor = useColorModeValue(colors.light.bg, colors.dark.bg); - const [messages, setMessages] = useState([]); - const [userMessages, setUserMessages] = useState([]); + const [messages, setMessages] = useState([]); + const [userMessages, setUserMessages] = useState([]); - const { isLoading: isLoadingAll } = useSWRImmutable("/api/messages?allUsers=true", fetcher, { - onSuccess: (data) => { - setMessages(data); - }, - }); + const { isLoading: isLoadingAll } = useSWRImmutable("/api/messages?allUsers=true", fetcher, { + onSuccess: (data) => { + setMessages(data); + }, + }); - const { isLoading: isLoadingUser } = useSWRImmutable(`/api/messages`, fetcher, { - onSuccess: (data) => { - setUserMessages(data); - }, - }); + const { isLoading: isLoadingUser } = useSWRImmutable(`/api/messages`, fetcher, { + onSuccess: (data) => { + setUserMessages(data); + }, + }); - return ( - <> - - Messages - Open Assistant - - - - - - - - - - - Most recent messages - - - - Your most recent messages - - - - - - - - ); + return ( + <> + + Messages - Open Assistant + + + + + + + + + + + Most recent messages + + + + Your most recent messages + + + + + + + + ); }; MessagesDashboard.getLayout = (page) => ( -
-
- {page} -
+
+
+ {page} +
); export default MessagesDashboard; From b08e1b986a3e619d457e350fe860c36aa1a043ef Mon Sep 17 00:00:00 2001 From: jojopirker Date: Wed, 4 Jan 2023 15:19:46 +0100 Subject: [PATCH 26/54] split api into multiple files --- website/src/pages/api/messages/index.tsx | 26 +++++++++++++++++++ .../api/{messages.ts => messages/user.tsx} | 4 +-- website/src/pages/messages.tsx | 6 ++--- 3 files changed, 29 insertions(+), 7 deletions(-) create mode 100644 website/src/pages/api/messages/index.tsx rename website/src/pages/api/{messages.ts => messages/user.tsx} (84%) diff --git a/website/src/pages/api/messages/index.tsx b/website/src/pages/api/messages/index.tsx new file mode 100644 index 00000000..b5a82bb1 --- /dev/null +++ b/website/src/pages/api/messages/index.tsx @@ -0,0 +1,26 @@ +import { boolean } from "boolean"; +import { getToken } from "next-auth/jwt"; + +const handler = async (req, res) => { + const token = await getToken({ req }); + + // Return nothing if the user isn't registered. + if (!token) { + res.status(401).end(); + return; + } + + const messagesRes = await fetch(`${process.env.FASTAPI_URL}/api/v1/messages`, { + method: "GET", + headers: { + "X-API-Key": process.env.FASTAPI_KEY, + "Content-Type": "application/json", + }, + }); + const messages = await messagesRes.json(); + + // Send recieved messages to the client. + res.status(200).json(messages); +}; + +export default handler; diff --git a/website/src/pages/api/messages.ts b/website/src/pages/api/messages/user.tsx similarity index 84% rename from website/src/pages/api/messages.ts rename to website/src/pages/api/messages/user.tsx index 53b529b5..58a8f10e 100644 --- a/website/src/pages/api/messages.ts +++ b/website/src/pages/api/messages/user.tsx @@ -1,4 +1,3 @@ -import { boolean } from "boolean"; import { getToken } from "next-auth/jwt"; const handler = async (req, res) => { @@ -11,9 +10,8 @@ const handler = async (req, res) => { } //TODO: add params if needed - const reqParams = req.query; const params = new URLSearchParams({ - username: boolean(reqParams.allUsers) ? "" : token.sub, + username: token.sub, }); const messagesRes = await fetch(`${process.env.FASTAPI_URL}/api/v1/messages?${params}`, { diff --git a/website/src/pages/messages.tsx b/website/src/pages/messages.tsx index 7509922d..0182b8c5 100644 --- a/website/src/pages/messages.tsx +++ b/website/src/pages/messages.tsx @@ -3,12 +3,10 @@ import { Box, CircularProgress, HStack, - Image, SimpleGrid, Stack, StackDivider, Text, - useColorMode, useColorModeValue, } from "@chakra-ui/react"; import Head from "next/head"; @@ -68,13 +66,13 @@ const MessagesDashboard = () => { const [messages, setMessages] = useState([]); const [userMessages, setUserMessages] = useState([]); - const { isLoading: isLoadingAll } = useSWRImmutable("/api/messages?allUsers=true", fetcher, { + const { isLoading: isLoadingAll } = useSWRImmutable("/api/messages", fetcher, { onSuccess: (data) => { setMessages(data); }, }); - const { isLoading: isLoadingUser } = useSWRImmutable(`/api/messages`, fetcher, { + const { isLoading: isLoadingUser } = useSWRImmutable(`/api/messages/user`, fetcher, { onSuccess: (data) => { setUserMessages(data); }, From be22c77b076da78a831de26d687ee4b8de7779af Mon Sep 17 00:00:00 2001 From: jojopirker Date: Wed, 4 Jan 2023 15:38:26 +0100 Subject: [PATCH 27/54] moved components into new folder --- .../src/components/Messages/MessageTable.tsx | 28 ++++++++++ .../components/Messages/MessageTableEntry.tsx | 19 +++++++ website/src/pages/messages.tsx | 55 +------------------ 3 files changed, 49 insertions(+), 53 deletions(-) create mode 100644 website/src/components/Messages/MessageTable.tsx create mode 100644 website/src/components/Messages/MessageTableEntry.tsx diff --git a/website/src/components/Messages/MessageTable.tsx b/website/src/components/Messages/MessageTable.tsx new file mode 100644 index 00000000..2572a5fa --- /dev/null +++ b/website/src/components/Messages/MessageTable.tsx @@ -0,0 +1,28 @@ +import { Box, CircularProgress, Stack, StackDivider, useColorModeValue } from "@chakra-ui/react"; +import { MessageTableEntry } from "./MessageTableEntry"; + +export function MessageTable ({ messages, isLoading }) { + const backgroundColor = useColorModeValue("white", "gray.700"); + const accentColor = useColorModeValue("gray.200", "gray.900"); + + return ( + + {isLoading ? ( + + ) : ( + } spacing="4"> + {messages.map((item, idx) => ( + + ))} + + )} + + ); + }; + \ No newline at end of file diff --git a/website/src/components/Messages/MessageTableEntry.tsx b/website/src/components/Messages/MessageTableEntry.tsx new file mode 100644 index 00000000..7bda7af9 --- /dev/null +++ b/website/src/components/Messages/MessageTableEntry.tsx @@ -0,0 +1,19 @@ +import { Avatar, Box, HStack, useColorModeValue } from "@chakra-ui/react"; + +import { FlaggableElement } from "../FlaggableElement"; + +export function MessageTableEntry ({ item, idx }) { + const bgColor = useColorModeValue(idx % 2 === 0 ? "bg-slate-800" : "bg-black", "bg-sky-900"); + + return ( + + + + {item.text} + + + ); + }; \ No newline at end of file diff --git a/website/src/pages/messages.tsx b/website/src/pages/messages.tsx index 0182b8c5..ff13d8a4 100644 --- a/website/src/pages/messages.tsx +++ b/website/src/pages/messages.tsx @@ -1,64 +1,13 @@ -import { - Avatar, - Box, - CircularProgress, - HStack, - SimpleGrid, - Stack, - StackDivider, - Text, - useColorModeValue, -} from "@chakra-ui/react"; +import { Box, SimpleGrid, Text, useColorModeValue } from "@chakra-ui/react"; 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 { FlaggableElement } from "src/components/FlaggableElement"; import { Header } from "src/components/Header"; import { colors } from "styles/Theme/colors"; - -const MessageTable = ({ messages, isLoading }) => { - const backgroundColor = useColorModeValue("white", "gray.700"); - const accentColor = useColorModeValue("gray.200", "gray.900"); - - return ( - - {isLoading ? ( - - ) : ( - } spacing="4"> - {messages.map((item, idx) => ( - - ))} - - )} - - ); -}; - -const MesssageTableEntry = ({ item, idx }) => { - const bgColor = useColorModeValue(idx % 2 === 0 ? "bg-slate-800" : "bg-black", "bg-sky-900"); - - return ( - - - - {item.text} - - - ); -}; +import { MessageTable } from "src/components/Messages/MessageTable"; const MessagesDashboard = () => { const bgColor = useColorModeValue(colors.light.bg, colors.dark.bg); From 910c1c88cec120c8a264a5218bce7d343ae059d8 Mon Sep 17 00:00:00 2001 From: jojopirker Date: Wed, 4 Jan 2023 15:42:51 +0100 Subject: [PATCH 28/54] pre-commit --- .../src/components/Messages/MessageTable.tsx | 49 +++++++++---------- .../components/Messages/MessageTableEntry.tsx | 30 ++++++------ website/src/pages/api/messages/index.tsx | 1 - 3 files changed, 39 insertions(+), 41 deletions(-) diff --git a/website/src/components/Messages/MessageTable.tsx b/website/src/components/Messages/MessageTable.tsx index 2572a5fa..fcbecbba 100644 --- a/website/src/components/Messages/MessageTable.tsx +++ b/website/src/components/Messages/MessageTable.tsx @@ -1,28 +1,27 @@ import { Box, CircularProgress, Stack, StackDivider, useColorModeValue } from "@chakra-ui/react"; import { MessageTableEntry } from "./MessageTableEntry"; -export function MessageTable ({ messages, isLoading }) { - const backgroundColor = useColorModeValue("white", "gray.700"); - const accentColor = useColorModeValue("gray.200", "gray.900"); - - return ( - - {isLoading ? ( - - ) : ( - } spacing="4"> - {messages.map((item, idx) => ( - - ))} - - )} - - ); - }; - \ No newline at end of file +export function MessageTable({ messages, isLoading }) { + const backgroundColor = useColorModeValue("white", "gray.700"); + const accentColor = useColorModeValue("gray.200", "gray.900"); + + return ( + + {isLoading ? ( + + ) : ( + } spacing="4"> + {messages.map((item, idx) => ( + + ))} + + )} + + ); +} diff --git a/website/src/components/Messages/MessageTableEntry.tsx b/website/src/components/Messages/MessageTableEntry.tsx index 7bda7af9..e68be0e8 100644 --- a/website/src/components/Messages/MessageTableEntry.tsx +++ b/website/src/components/Messages/MessageTableEntry.tsx @@ -2,18 +2,18 @@ import { Avatar, Box, HStack, useColorModeValue } from "@chakra-ui/react"; import { FlaggableElement } from "../FlaggableElement"; -export function MessageTableEntry ({ item, idx }) { - const bgColor = useColorModeValue(idx % 2 === 0 ? "bg-slate-800" : "bg-black", "bg-sky-900"); - - return ( - - - - {item.text} - - - ); - }; \ No newline at end of file +export function MessageTableEntry({ item, idx }) { + const bgColor = useColorModeValue(idx % 2 === 0 ? "bg-slate-800" : "bg-black", "bg-sky-900"); + + return ( + + + + {item.text} + + + ); +} diff --git a/website/src/pages/api/messages/index.tsx b/website/src/pages/api/messages/index.tsx index b5a82bb1..cf55dcc3 100644 --- a/website/src/pages/api/messages/index.tsx +++ b/website/src/pages/api/messages/index.tsx @@ -1,4 +1,3 @@ -import { boolean } from "boolean"; import { getToken } from "next-auth/jwt"; const handler = async (req, res) => { From 3905f75d6c3e9476eb8dc225f8bf262f71001e76 Mon Sep 17 00:00:00 2001 From: jojopirker Date: Wed, 4 Jan 2023 16:01:37 +0100 Subject: [PATCH 29/54] moved messages page into subfolder --- website/src/pages/{messages.tsx => messages/index.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename website/src/pages/{messages.tsx => messages/index.tsx} (100%) diff --git a/website/src/pages/messages.tsx b/website/src/pages/messages/index.tsx similarity index 100% rename from website/src/pages/messages.tsx rename to website/src/pages/messages/index.tsx From 8f4c9637a747a3f8badf6665436445dc51bff0f6 Mon Sep 17 00:00:00 2001 From: chs20 Date: Wed, 4 Jan 2023 16:14:53 +0100 Subject: [PATCH 30/54] Fix dark mode display for some pages --- website/src/pages/account/edit.tsx | 34 ++++++++++++++------------ website/src/pages/account/index.tsx | 16 ++++++------ website/src/pages/privacy-policy.tsx | 2 +- website/src/pages/terms-of-service.tsx | 2 +- 4 files changed, 29 insertions(+), 25 deletions(-) diff --git a/website/src/pages/account/edit.tsx b/website/src/pages/account/edit.tsx index f695fce7..497e8238 100644 --- a/website/src/pages/account/edit.tsx +++ b/website/src/pages/account/edit.tsx @@ -36,22 +36,24 @@ export default function Account() { content="Conversational AI for everyone. An open source project to create a chat enabled GPT LLM run by LAION and contributors around the world." /> -
-

{session.user.name || "No username"}

-
- - setUsername(e.target.value)} - placeholder="Edit Username" - type="text" - value={username} - > - - -
-
+
+
+

{session.user.name || "No username"}

+
+ + setUsername(e.target.value)} + placeholder="Edit Username" + type="text" + value={username} + > + + +
+
+
); } diff --git a/website/src/pages/account/index.tsx b/website/src/pages/account/index.tsx index 9f8dc4a3..d26fc842 100644 --- a/website/src/pages/account/index.tsx +++ b/website/src/pages/account/index.tsx @@ -19,13 +19,15 @@ export default function Account() { content="Conversational AI for everyone. An open source project to create a chat enabled GPT LLM run by LAION and contributors around the world." /> -
-

{session.user.name || "No username"}

- -

{session.user.email}

-
+
+
+

{session.user.name || "No username"}

+ +

{session.user.email}

+
+
); } diff --git a/website/src/pages/privacy-policy.tsx b/website/src/pages/privacy-policy.tsx index dcb3bc19..164fab93 100644 --- a/website/src/pages/privacy-policy.tsx +++ b/website/src/pages/privacy-policy.tsx @@ -14,7 +14,7 @@ const PrivacyPolicy = () => { content="Conversational AI for everyone. An open source project to create a chat enabled GPT LLM run by LAION and contributors around the world." /> -
+
Privacy Policy diff --git a/website/src/pages/terms-of-service.tsx b/website/src/pages/terms-of-service.tsx index d97c8d34..ce60a20e 100644 --- a/website/src/pages/terms-of-service.tsx +++ b/website/src/pages/terms-of-service.tsx @@ -14,7 +14,7 @@ const TermsOfService = () => { content="Conversational AI for everyone. An open source project to create a chat enabled GPT LLM run by LAION and contributors around the world." /> -
+
Terms Of Service From 96be0edb1eaf104201026d18626775cccc6bae9a Mon Sep 17 00:00:00 2001 From: Kostia Date: Wed, 4 Jan 2023 18:58:54 +0200 Subject: [PATCH 31/54] Fix for #343 --- website/src/pages/create/assistant_reply.tsx | 8 +++++++- website/src/pages/create/initial_prompt.tsx | 8 +++++++- website/src/pages/create/user_reply.tsx | 8 +++++++- website/src/pages/evaluate/rank_assistant_replies.tsx | 8 +++++++- website/src/pages/evaluate/rank_initial_prompts.tsx | 8 +++++++- website/src/pages/evaluate/rank_user_replies.tsx | 8 +++++++- 6 files changed, 42 insertions(+), 6 deletions(-) diff --git a/website/src/pages/create/assistant_reply.tsx b/website/src/pages/create/assistant_reply.tsx index ceac45be..2f7ca748 100644 --- a/website/src/pages/create/assistant_reply.tsx +++ b/website/src/pages/create/assistant_reply.tsx @@ -1,6 +1,6 @@ import { Container, Textarea } from "@chakra-ui/react"; import { useColorMode } from "@chakra-ui/react"; -import { useRef, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { LoadingScreen } from "src/components/Loading/LoadingScreen"; import { Messages } from "src/components/Messages"; import { TaskControls } from "src/components/Survey/TaskControls"; @@ -21,6 +21,12 @@ const AssistantReply = () => { }, }); + useEffect(() => { + if (tasks.length == 0) { + mutate(); + } + }, [tasks]); + const { trigger } = useSWRMutation("/api/update_task", poster, { onSuccess: async (data) => { const newTask = await data.json(); diff --git a/website/src/pages/create/initial_prompt.tsx b/website/src/pages/create/initial_prompt.tsx index 5aecf98c..a4aed9c3 100644 --- a/website/src/pages/create/initial_prompt.tsx +++ b/website/src/pages/create/initial_prompt.tsx @@ -1,6 +1,6 @@ import { Container, Textarea } from "@chakra-ui/react"; import { useColorMode } from "@chakra-ui/react"; -import { useRef, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { LoadingScreen } from "src/components/Loading/LoadingScreen"; import { TaskControls } from "src/components/Survey/TaskControls"; import { TwoColumnsWithCards } from "src/components/Survey/TwoColumnsWithCards"; @@ -27,6 +27,12 @@ const InitialPrompt = () => { }, }); + useEffect(() => { + if (tasks.length == 0) { + mutate(); + } + }, [tasks]); + const submitResponse = (task: { id: string }) => { const text = inputRef.current.value.trim(); trigger({ diff --git a/website/src/pages/create/user_reply.tsx b/website/src/pages/create/user_reply.tsx index b9022e86..40409189 100644 --- a/website/src/pages/create/user_reply.tsx +++ b/website/src/pages/create/user_reply.tsx @@ -1,6 +1,6 @@ import { Textarea } from "@chakra-ui/react"; import { useColorMode } from "@chakra-ui/react"; -import { useRef, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { LoadingScreen } from "src/components/Loading/LoadingScreen"; import { Messages } from "src/components/Messages"; import { TaskControls } from "src/components/Survey/TaskControls"; @@ -21,6 +21,12 @@ const UserReply = () => { }, }); + useEffect(() => { + if (tasks.length == 0) { + mutate(); + } + }, [tasks]); + const { trigger } = useSWRMutation("/api/update_task", poster, { onSuccess: async (data) => { const newTask = await data.json(); diff --git a/website/src/pages/evaluate/rank_assistant_replies.tsx b/website/src/pages/evaluate/rank_assistant_replies.tsx index e8558c00..0ed69b09 100644 --- a/website/src/pages/evaluate/rank_assistant_replies.tsx +++ b/website/src/pages/evaluate/rank_assistant_replies.tsx @@ -1,6 +1,6 @@ import { useColorMode } from "@chakra-ui/react"; import Head from "next/head"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import { ContextMessages } from "src/components/ContextMessages"; import { LoadingScreen } from "src/components/Loading/LoadingScreen"; import { Message } from "src/components/Messages"; @@ -26,6 +26,12 @@ const RankAssistantReplies = () => { }, }); + useEffect(() => { + if (tasks.length == 0) { + mutate(); + } + }, [tasks]); + const { trigger } = useSWRMutation("/api/update_task", poster, { onSuccess: async (data) => { const newTask = await data.json(); diff --git a/website/src/pages/evaluate/rank_initial_prompts.tsx b/website/src/pages/evaluate/rank_initial_prompts.tsx index 48a67e90..a9d590ac 100644 --- a/website/src/pages/evaluate/rank_initial_prompts.tsx +++ b/website/src/pages/evaluate/rank_initial_prompts.tsx @@ -1,6 +1,6 @@ import { useColorMode } from "@chakra-ui/react"; import Head from "next/head"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import { LoadingScreen } from "src/components/Loading/LoadingScreen"; import { Sortable } from "src/components/Sortable/Sortable"; import { SurveyCard } from "src/components/Survey/SurveyCard"; @@ -32,6 +32,12 @@ const RankInitialPrompts = () => { }, }); + useEffect(() => { + if (tasks.length == 0) { + mutate(); + } + }, [tasks]); + const submitResponse = (task) => { trigger({ id: task.id, diff --git a/website/src/pages/evaluate/rank_user_replies.tsx b/website/src/pages/evaluate/rank_user_replies.tsx index 3f806a8a..9a0577cb 100644 --- a/website/src/pages/evaluate/rank_user_replies.tsx +++ b/website/src/pages/evaluate/rank_user_replies.tsx @@ -1,6 +1,6 @@ import { useColorMode } from "@chakra-ui/react"; import Head from "next/head"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import { ContextMessages } from "src/components/ContextMessages"; import { LoadingScreen } from "src/components/Loading/LoadingScreen"; import { Message } from "src/components/Messages"; @@ -26,6 +26,12 @@ const RankUserReplies = () => { }, }); + useEffect(() => { + if (tasks.length == 0) { + mutate(); + } + }, [tasks]); + const { trigger } = useSWRMutation("/api/update_task", poster, { onSuccess: async (data) => { const newTask = await data.json(); From efdfb09695a3512ba06f6adce82e4b88c4561811 Mon Sep 17 00:00:00 2001 From: jojopirker Date: Wed, 4 Jan 2023 20:04:26 +0100 Subject: [PATCH 32/54] fixed typo --- .../src/components/Messages/MessageTableEntry.tsx | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/website/src/components/Messages/MessageTableEntry.tsx b/website/src/components/Messages/MessageTableEntry.tsx index e68be0e8..2bc17201 100644 --- a/website/src/components/Messages/MessageTableEntry.tsx +++ b/website/src/components/Messages/MessageTableEntry.tsx @@ -1,5 +1,6 @@ -import { Avatar, Box, HStack, useColorModeValue } from "@chakra-ui/react"; - +import { Avatar, Box, HStack, LinkBox, useColorModeValue } from "@chakra-ui/react"; +import { boolean } from "boolean"; +import NextLink from "next/link"; import { FlaggableElement } from "../FlaggableElement"; export function MessageTableEntry({ item, idx }) { @@ -9,10 +10,14 @@ export function MessageTableEntry({ item, idx }) { - {item.text} + + + {item.text} + + ); From 173356929af33af5b0509cb5b324b4b64ae61a4c Mon Sep 17 00:00:00 2001 From: jojopirker Date: Wed, 4 Jan 2023 20:09:03 +0100 Subject: [PATCH 33/54] removed content-type --- website/src/pages/api/messages/index.tsx | 1 - website/src/pages/api/messages/user.tsx | 1 - 2 files changed, 2 deletions(-) diff --git a/website/src/pages/api/messages/index.tsx b/website/src/pages/api/messages/index.tsx index cf55dcc3..3c8d1c17 100644 --- a/website/src/pages/api/messages/index.tsx +++ b/website/src/pages/api/messages/index.tsx @@ -13,7 +13,6 @@ const handler = async (req, res) => { method: "GET", headers: { "X-API-Key": process.env.FASTAPI_KEY, - "Content-Type": "application/json", }, }); const messages = await messagesRes.json(); diff --git a/website/src/pages/api/messages/user.tsx b/website/src/pages/api/messages/user.tsx index 58a8f10e..e3d22f3c 100644 --- a/website/src/pages/api/messages/user.tsx +++ b/website/src/pages/api/messages/user.tsx @@ -18,7 +18,6 @@ const handler = async (req, res) => { method: "GET", headers: { "X-API-Key": process.env.FASTAPI_KEY, - "Content-Type": "application/json", }, }); const messages = await messagesRes.json(); From 695e1f3932837a49190568ecc10eacf5315fa57f Mon Sep 17 00:00:00 2001 From: Jack Michaud Date: Wed, 4 Jan 2023 14:17:02 -0500 Subject: [PATCH 34/54] refactor: avoid using Response.clone() --- website/src/lib/oasst_api_client.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/website/src/lib/oasst_api_client.ts b/website/src/lib/oasst_api_client.ts index ce61e591..45a0859e 100644 --- a/website/src/lib/oasst_api_client.ts +++ b/website/src/lib/oasst_api_client.ts @@ -30,11 +30,12 @@ export default class OasstApiClient { } if (resp.status >= 300) { + const errorText = await resp.text(); try { - const error = await resp.clone().json(); + const error = JSON.parse(errorText); throw new OasstError(error.message, error.error_code, resp.status); } catch (e) { - throw new OasstError(await resp.text(), 0, resp.status); + throw new OasstError(errorText, 0, resp.status); } } From 8e61cef81dd3f7b9664c2f003d60c558d290843b Mon Sep 17 00:00:00 2001 From: jojopirker Date: Wed, 4 Jan 2023 20:22:06 +0100 Subject: [PATCH 35/54] added getDashboardLayout --- website/src/components/Layout.tsx | 7 +++++++ website/src/pages/dashboard.tsx | 10 +++------- website/src/pages/messages/index.tsx | 13 ++++--------- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/website/src/components/Layout.tsx b/website/src/components/Layout.tsx index 3564d765..bf662113 100644 --- a/website/src/components/Layout.tsx +++ b/website/src/components/Layout.tsx @@ -25,4 +25,11 @@ export const getTransparentHeaderLayout = (page: React.ReactElement) => (
); +export const getDashboardLayout = (page: React.ReactElement) => ( +
+
+ {page} +
+); + export const noLayout = (page: React.ReactElement) => page; diff --git a/website/src/pages/dashboard.tsx b/website/src/pages/dashboard.tsx index dfc5cb03..8b1f6861 100644 --- a/website/src/pages/dashboard.tsx +++ b/website/src/pages/dashboard.tsx @@ -1,6 +1,7 @@ import { Box, useColorMode } from "@chakra-ui/react"; import Head from "next/head"; -import { Header } from "src/components/Header"; + +import { getDashboardLayout } from "src/components/Layout"; import { LeaderboardTable, SideMenu, TaskOption } from "src/components/Dashboard"; import { colors } from "styles/Theme/colors"; @@ -27,11 +28,6 @@ const Dashboard = () => { ); }; -Dashboard.getLayout = (page) => ( -
-
- {page} -
-); +Dashboard.getLayout = (page) => getDashboardLayout(page); export default Dashboard; diff --git a/website/src/pages/messages/index.tsx b/website/src/pages/messages/index.tsx index ff13d8a4..a466b710 100644 --- a/website/src/pages/messages/index.tsx +++ b/website/src/pages/messages/index.tsx @@ -5,9 +5,9 @@ import useSWRImmutable from "swr/immutable"; import fetcher from "src/lib/fetcher"; import { SideMenu } from "src/components/Dashboard"; -import { Header } from "src/components/Header"; -import { colors } from "styles/Theme/colors"; 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); @@ -39,7 +39,7 @@ const MessagesDashboard = () => { - + Most recent messages @@ -56,11 +56,6 @@ const MessagesDashboard = () => { ); }; -MessagesDashboard.getLayout = (page) => ( -
-
- {page} -
-); +MessagesDashboard.getLayout = (page) => getDashboardLayout(page); export default MessagesDashboard; From b4034e56e2c58853a2bbc2409645f9060255408b Mon Sep 17 00:00:00 2001 From: jojopirker Date: Wed, 4 Jan 2023 20:35:20 +0100 Subject: [PATCH 36/54] moved isLoading --- .../src/components/Messages/MessageTable.tsx | 27 +++++-------------- website/src/pages/messages/index.tsx | 24 ++++++++++++++--- 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/website/src/components/Messages/MessageTable.tsx b/website/src/components/Messages/MessageTable.tsx index fcbecbba..65f6efb1 100644 --- a/website/src/components/Messages/MessageTable.tsx +++ b/website/src/components/Messages/MessageTable.tsx @@ -1,27 +1,12 @@ import { Box, CircularProgress, Stack, StackDivider, useColorModeValue } from "@chakra-ui/react"; import { MessageTableEntry } from "./MessageTableEntry"; -export function MessageTable({ messages, isLoading }) { - const backgroundColor = useColorModeValue("white", "gray.700"); - const accentColor = useColorModeValue("gray.200", "gray.900"); - +export function MessageTable({ messages }) { return ( - - {isLoading ? ( - - ) : ( - } spacing="4"> - {messages.map((item, idx) => ( - - ))} - - )} - + } spacing="4"> + {messages.map((item, idx) => ( + + ))} + ); } diff --git a/website/src/pages/messages/index.tsx b/website/src/pages/messages/index.tsx index a466b710..9ecdeb35 100644 --- a/website/src/pages/messages/index.tsx +++ b/website/src/pages/messages/index.tsx @@ -1,4 +1,4 @@ -import { Box, SimpleGrid, Text, useColorModeValue } from "@chakra-ui/react"; +import { Box, CircularProgress, SimpleGrid, Text, useColorModeValue } from "@chakra-ui/react"; import Head from "next/head"; import { useState } from "react"; import useSWRImmutable from "swr/immutable"; @@ -11,6 +11,8 @@ 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"); const [messages, setMessages] = useState([]); const [userMessages, setUserMessages] = useState([]); @@ -42,11 +44,27 @@ const MessagesDashboard = () => { Most recent messages - + + {isLoadingAll ? : } + Your most recent messages - + + {isLoadingUser ? : } +
From 9ce4348263663fe56a966721b8b80a795a70599a Mon Sep 17 00:00:00 2001 From: Yannic Kilcher Date: Wed, 4 Jan 2023 22:40:31 +0100 Subject: [PATCH 37/54] set rate limit to false while we don't have redis yet --- ansible/dev.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/ansible/dev.yaml b/ansible/dev.yaml index d022ba3c..577abd68 100644 --- a/ansible/dev.yaml +++ b/ansible/dev.yaml @@ -54,6 +54,7 @@ DEBUG_ALLOW_ANY_API_KEY: "true" DEBUG_USE_SEED_DATA: "true" MAX_WORKERS: "1" + RATE_LIMIT: "false" ports: - 8080:8080 From 8fab6cfe25b7f25113ddb5c6e2e4a6f9e2a26be7 Mon Sep 17 00:00:00 2001 From: Klotske Date: Thu, 5 Jan 2023 00:50:17 +0300 Subject: [PATCH 38/54] Fixed broken link in the dashboard --- website/src/components/Dashboard/TaskOption.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/src/components/Dashboard/TaskOption.tsx b/website/src/components/Dashboard/TaskOption.tsx index 6b17a079..8cf9977f 100644 --- a/website/src/components/Dashboard/TaskOption.tsx +++ b/website/src/components/Dashboard/TaskOption.tsx @@ -6,7 +6,7 @@ const crTasks = [ label: "Reply as User", desc: "Chat with Open Assistant and help improve it’s responses as you interact with it.", type: "create", - pathname: "/create/assistant_reply", + pathname: "/create/user_reply", }, { label: "Reply as Assistant", From 061d6219530102d8703e463e0b93b3b9c630e97d Mon Sep 17 00:00:00 2001 From: Bobak Hashemi Date: Wed, 4 Jan 2023 20:51:19 -0500 Subject: [PATCH 39/54] removed old precommit pragma requirement --- model/reward/instructor/models.py | 1 - 1 file changed, 1 deletion(-) diff --git a/model/reward/instructor/models.py b/model/reward/instructor/models.py index 084cfa51..c1891ed2 100644 --- a/model/reward/instructor/models.py +++ b/model/reward/instructor/models.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import torch from transformers import AutoModel From ab434ee79b0c2fac5a7fd6e27a0ea348a3dea4a8 Mon Sep 17 00:00:00 2001 From: Karthik Raju Date: Thu, 5 Jan 2023 12:46:07 +0530 Subject: [PATCH 40/54] remove package-lock.json file from the root folder --- package-lock.json | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 0c313854..00000000 --- a/package-lock.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "Open-Assistant", - "lockfileVersion": 3, - "requires": true, - "packages": {} -} From c2b6cdb12a3830c5f17139e15c358049b0811c44 Mon Sep 17 00:00:00 2001 From: Graeme Harris Date: Thu, 5 Jan 2023 10:19:57 +0200 Subject: [PATCH 41/54] Bugfix #173 - Empty task interactions (#373) * Added pydantic checks for interaction protocols * Small updated to BE API README for redis and guide to local scripts * isort linting --- backend/README.md | 6 ++++-- oasst-shared/oasst_shared/schemas/protocol.py | 8 ++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/backend/README.md b/backend/README.md index 45d16d68..863090f7 100644 --- a/backend/README.md +++ b/backend/README.md @@ -11,10 +11,12 @@ Example contents of a `.env` file for the backend: ``` DATABASE_URI="postgresql://:@/" BACKEND_CORS_ORIGINS=["http://localhost", "http://localhost:4200", "http://localhost:3000", "http://localhost:8080", "https://localhost", "https://localhost:4200", "https://localhost:3000", "https://localhost:8080", "http://dev.oasst.laion.ai", "https://stag.oasst.laion.ai", "https://oasst.laion.ai"] - +REDIS_HOST=localhost +REDIS_PORT=6379 ``` ## Running the REST Server locally for development Have a look into the main `README.md` file for more information on how to set up -the backend for development. +the backend for development. Use the scripts within the +scripts/backend-development folder to run the BE API locally. diff --git a/oasst-shared/oasst_shared/schemas/protocol.py b/oasst-shared/oasst_shared/schemas/protocol.py index 83375d8f..e035d387 100644 --- a/oasst-shared/oasst_shared/schemas/protocol.py +++ b/oasst-shared/oasst_shared/schemas/protocol.py @@ -5,7 +5,7 @@ from uuid import UUID, uuid4 import pydantic from oasst_shared.exceptions import OasstErrorCode -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, conint, conlist, constr class TaskRequestType(str, enum.Enum): @@ -203,7 +203,7 @@ class TextReplyToMessage(Interaction): type: Literal["text_reply_to_message"] = "text_reply_to_message" message_id: str user_message_id: str - text: str + text: constr(min_length=1, strip_whitespace=True) class MessageRating(Interaction): @@ -211,7 +211,7 @@ class MessageRating(Interaction): type: Literal["message_rating"] = "message_rating" message_id: str - rating: int + rating: conint(gt=0) class MessageRanking(Interaction): @@ -219,7 +219,7 @@ class MessageRanking(Interaction): type: Literal["message_ranking"] = "message_ranking" message_id: str - ranking: list[int] + ranking: conlist(item_type=int, min_items=1) AnyInteraction = Union[ From cba0925564eb4751b8bf011faf633ddb37c1507e Mon Sep 17 00:00:00 2001 From: Jac-Zac Date: Thu, 5 Jan 2023 10:33:29 +0100 Subject: [PATCH 42/54] prittier readme --- README.md | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index b619c931..ec1d7b3c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,29 @@ -# Open-Assistant +

+ Open-Assistant +

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

+ +

+ + +Table of Contents +======================= + +* [What is Open Assistant?](#what-is-open-assistant) +* [Do you want to try it out?](#do-you-want-to-try-it-out) +* [The Plan](#the-plan) +* [The Vision](#the-vision) +* [How can you help?](#how-can-you-help) +* [I’m in! Now what?](#i'm-in-now-what) + +--- + +What is Open Assistant? +------ + +_Open Assistant is a project meant to give everyone access to a great chat based +large language model._ We believe that by doing this we will create a revolution in innovation in language. In the same way that stable-diffusion helped the world make art and @@ -14,7 +36,7 @@ If you are interested in taking a look at the current state of the project, you can set up an entire stack needed to run **Open-Assistant**, including the website, backend, and associated dependent services. -To start the demo, run this in the root directory of the repository: +##### To start the demo, run this in the root directory of the repository: ```sh docker compose up --build @@ -23,10 +45,10 @@ docker compose up --build Then, navigate to `http://localhost:3000` (It may take some time to boot up) and interact with the website. -**Note:** When logging in via email, navigate to `http://localhost:1080` to get +> **Note:** When logging in via email, navigate to `http://localhost:1080` to get the magic email login link. -**Note:** If you would like to run this in a standardized development +> **Note:** If you would like to run this in a standardized development environment (a ["devcontainer"](https://code.visualstudio.com/docs/devcontainers/containers)) using @@ -37,8 +59,7 @@ provided [`.devcontainer`](.devcontainer/) folder. ## The Plan -We want to get to an initial MVP as fast as possible, by following the 3-steps -outlined in the InstructGPT paper. +##### We want to get to an initial MVP as fast as possible, by following the 3-steps outlined in the InstructGPT paper. 1. Collect high-quality human generated Instruction-Fulfillment samples (prompt + response), goal >50k. We design a crowdsourced process to collect From ac8ae2652ee6422a6f356f7af7056193ea390c10 Mon Sep 17 00:00:00 2001 From: jojopirker Date: Thu, 5 Jan 2023 10:33:35 +0100 Subject: [PATCH 43/54] spacing for message dashboard --- website/src/pages/messages/index.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/website/src/pages/messages/index.tsx b/website/src/pages/messages/index.tsx index 9ecdeb35..39430caf 100644 --- a/website/src/pages/messages/index.tsx +++ b/website/src/pages/messages/index.tsx @@ -41,9 +41,11 @@ const MessagesDashboard = () => {
- + - Most recent messages + + Most recent messages + { - Your most recent messages + + Your most recent messages + Date: Thu, 5 Jan 2023 10:39:27 +0100 Subject: [PATCH 44/54] small fix in readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ec1d7b3c..68c77a11 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@

- +

@@ -15,7 +15,7 @@ Table of Contents * [The Plan](#the-plan) * [The Vision](#the-vision) * [How can you help?](#how-can-you-help) -* [I’m in! Now what?](#i'm-in-now-what) +* [I’m in! Now what?](#I'm-in-now-what) --- From 6df5b1f19866219fe5589859ab1a7c73f94995c0 Mon Sep 17 00:00:00 2001 From: Jac-Zac Date: Thu, 5 Jan 2023 10:48:27 +0100 Subject: [PATCH 45/54] adding CONTRIBUTING.md --- CONTRIBUTING.md | 110 ++++++++++++++++++++++++++++++++++++++++++++ README.md | 118 ++---------------------------------------------- 2 files changed, 114 insertions(+), 114 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..608afe25 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,110 @@ +# I’m in! Now what? + +[Join the OpenAssistant Contributors Discord Server!](https://ykilcher.com/open-assistant-discord), +this is for work coordination. + +[Join the LAION Discord Server!](https://discord.com/invite/mVcgxMPD7e), it has +a dedicated channel and is more public. + +[and / or the YK Discord Server](https://ykilcher.com/discord), also has a +dedicated, but not as active, channel. + +[Visit the Notion](https://ykilcher.com/open-assistant) + +### Taking on Tasks + +We have a growing task list +[of issues](https://github.com/LAION-AI/Open-Assistant/issues). Find an issue +that appeals to you and make a comment that you'd like to work on it. Include in +your comment a brief description of how you'll solve the problem and if there +are any open questions you want to discuss. Once a project coordinator has +assigned the issue to you, start working on it. + +If the issue is currently unclear but you are interested, please post in Discord +and someone can help clarify the issue with more detail. + +**Always Welcome:** Documentation markdowns in `docs/`, docstrings, diagrams of +the system architecture, and other documentation. + +### Submitting Work + +We're all working on different parts of Open Assistant together. To make +contributions smoothly we recommend the following: + +1. [Fork this project repository](https://docs.github.com/en/get-started/quickstart/fork-a-repo) + and clone it to your local machine. (Read more + [About Forks](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/about-forks)) +1. Before working on any changes, try to + [sync the forked repository](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork) + to keep it up-to-date with the upstream repository. +1. Work on a small focused change that only touches on a few files. +1. Run `pre-commit` and make sure all files have formatting fixed. This + simplifies life for reviewers. +1. Package up a small bit of work that solves part of the problem + [into a Pull Request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork) + and + [send it out for review](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/requesting-a-pull-request-review). +1. If you're lucky, we can merge your change into `main` without any problems. + If there's changes to files you're working on, resolve them by: +1. First try rebase as suggested + [in these instructions](https://timwise.co.uk/2019/10/14/merge-vs-rebase/#should-you-rebase). +1. If rebase feels too painful, merge as suggested + [in these instructions](https://timwise.co.uk/2019/10/14/merge-vs-rebase/#should-you-merge). +1. Once you've resolved any conflicts, finish the review and merge into `main`. +1. Merge in your change and move onto a new issue or the second step of your + current issue. + +Additionally, if someone is working on an issue that interests you, ask if they +need help on it or would like suggestions on how to approach the issue. If so, +share wildly. If they seem to have a good handle on it, let them work on their +solution until a challenge comes up. + +### When does a review finish + +A review finishes when all blocking comments are addressed and at least one +owning reviewer has approved the PR. Be sure to acknowledge any non-blocking +comments either by making the request change, explaining why it's not being +addressed now, or filing an issue to handle it later. + +## Developer Setup + +Work is organized in the +[project board](https://github.com/orgs/LAION-AI/projects/3). + +**Anything that is in the `Todo` column and not assigned, is up for grabs. +Meaning we'd be happy for anyone to do these tasks.** + +If you want to work on something, assign yourself to it or write a comment that +you want to work on it and what you plan to do. + +- To get started with development, if you want to work on the backend, have a + look at `scripts/backend-development/README.md`. +- If you want to work on any frontend, have a look at + `scripts/frontend-development/README.md` to make a backend available. + +There is also a minimal implementation of a frontend in the `text-frontend` +folder. + +We are using Python 3.10 for the backend. + +Check out the +[High-Level Protocol Architecture](https://www.notion.so/High-Level-Protocol-Architecture-6f1fd3551da74213b560ead369f132dc) + +### Website + +The website is built using Next.js and is in the `website` folder. + +### Pre-commit + +Install `pre-commit` and run `pre-commit install` to install the pre-commit +hooks. + +In case you haven't done this, have already committed, and CI is failing, you +can run `pre-commit run --all-files` to run the pre-commit hooks on all files. + +### Deployment + +Upon making a release on GitHub, all docker images are automatically built and +pushed to ghcr.io. The docker images are tagged with the release version, and +the `latest` tag. Further, the ansible playbook in `ansible/dev.yaml` is run to +automatically deploy the built release to the dev machine. diff --git a/README.md b/README.md index 68c77a11..a0ff857c 100644 --- a/README.md +++ b/README.md @@ -15,15 +15,16 @@ Table of Contents * [The Plan](#the-plan) * [The Vision](#the-vision) * [How can you help?](#how-can-you-help) -* [I’m in! Now what?](#I'm-in-now-what) +* [I’m in! Now what?](CONTRIBUTING.md) --- What is Open Assistant? ------ -_Open Assistant is a project meant to give everyone access to a great chat based -large language model._ +

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

We believe that by doing this we will create a revolution in innovation in language. In the same way that stable-diffusion helped the world make art and @@ -100,114 +101,3 @@ hardware. All open source projects begin with people like you. Open source is the belief that if we collaborate we can together gift our knowledge and technology to the world for the benefit of humanity. - -## I’m in! Now what? - -[Join the OpenAssistant Contributors Discord Server!](https://ykilcher.com/open-assistant-discord), -this is for work coordination. - -[Join the LAION Discord Server!](https://discord.com/invite/mVcgxMPD7e), it has -a dedicated channel and is more public. - -[and / or the YK Discord Server](https://ykilcher.com/discord), also has a -dedicated, but not as active, channel. - -[Visit the Notion](https://ykilcher.com/open-assistant) - -### Taking on Tasks - -We have a growing task list -[of issues](https://github.com/LAION-AI/Open-Assistant/issues). Find an issue -that appeals to you and make a comment that you'd like to work on it. Include in -your comment a brief description of how you'll solve the problem and if there -are any open questions you want to discuss. Once a project coordinator has -assigned the issue to you, start working on it. - -If the issue is currently unclear but you are interested, please post in Discord -and someone can help clarify the issue with more detail. - -**Always Welcome:** Documentation markdowns in `docs/`, docstrings, diagrams of -the system architecture, and other documentation. - -### Submitting Work - -We're all working on different parts of Open Assistant together. To make -contributions smoothly we recommend the following: - -1. [Fork this project repository](https://docs.github.com/en/get-started/quickstart/fork-a-repo) - and clone it to your local machine. (Read more - [About Forks](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/about-forks)) -1. Before working on any changes, try to - [sync the forked repository](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork) - to keep it up-to-date with the upstream repository. -1. Work on a small focused change that only touches on a few files. -1. Run `pre-commit` and make sure all files have formatting fixed. This - simplifies life for reviewers. -1. Package up a small bit of work that solves part of the problem - [into a Pull Request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork) - and - [send it out for review](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/requesting-a-pull-request-review). -1. If you're lucky, we can merge your change into `main` without any problems. - If there's changes to files you're working on, resolve them by: -1. First try rebase as suggested - [in these instructions](https://timwise.co.uk/2019/10/14/merge-vs-rebase/#should-you-rebase). -1. If rebase feels too painful, merge as suggested - [in these instructions](https://timwise.co.uk/2019/10/14/merge-vs-rebase/#should-you-merge). -1. Once you've resolved any conflicts, finish the review and merge into `main`. -1. Merge in your change and move onto a new issue or the second step of your - current issue. - -Additionally, if someone is working on an issue that interests you, ask if they -need help on it or would like suggestions on how to approach the issue. If so, -share wildly. If they seem to have a good handle on it, let them work on their -solution until a challenge comes up. - -### When does a review finish - -A review finishes when all blocking comments are addressed and at least one -owning reviewer has approved the PR. Be sure to acknowledge any non-blocking -comments either by making the request change, explaining why it's not being -addressed now, or filing an issue to handle it later. - -## Developer Setup - -Work is organized in the -[project board](https://github.com/orgs/LAION-AI/projects/3). - -**Anything that is in the `Todo` column and not assigned, is up for grabs. -Meaning we'd be happy for anyone to do these tasks.** - -If you want to work on something, assign yourself to it or write a comment that -you want to work on it and what you plan to do. - -- To get started with development, if you want to work on the backend, have a - look at `scripts/backend-development/README.md`. -- If you want to work on any frontend, have a look at - `scripts/frontend-development/README.md` to make a backend available. - -There is also a minimal implementation of a frontend in the `text-frontend` -folder. - -We are using Python 3.10 for the backend. - -Check out the -[High-Level Protocol Architecture](https://www.notion.so/High-Level-Protocol-Architecture-6f1fd3551da74213b560ead369f132dc) - -### Website - -The website is built using Next.js and is in the `website` folder. - -### Pre-commit - -Install `pre-commit` and run `pre-commit install` to install the pre-commit -hooks. - -In case you haven't done this, have already committed, and CI is failing, you -can run `pre-commit run --all-files` to run the pre-commit hooks on all files. - -### Deployment - -Upon making a release on GitHub, all docker images are automatically built and -pushed to ghcr.io. The docker images are tagged with the release version, and -the `latest` tag. Further, the ansible playbook in `ansible/dev.yaml` is run to -automatically deploy the built release to the dev machine. From cb8fec868c07c2705f63137beb11c0a95df83979 Mon Sep 17 00:00:00 2001 From: Adrian Cowan Date: Thu, 5 Jan 2023 20:51:45 +1100 Subject: [PATCH 46/54] website: Add Create Initial Prompts link to the dashboard. --- website/src/components/Dashboard/TaskOption.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/website/src/components/Dashboard/TaskOption.tsx b/website/src/components/Dashboard/TaskOption.tsx index 8cf9977f..50f707a6 100644 --- a/website/src/components/Dashboard/TaskOption.tsx +++ b/website/src/components/Dashboard/TaskOption.tsx @@ -2,6 +2,12 @@ import { Box, Flex, GridItem, Heading, SimpleGrid, Text, useColorModeValue } fro import Link from "next/link"; const crTasks = [ + { + label: "Create Initial Prompts", + desc: "Write initial prompts to help Open Assistant to try replying to diverse messages.", + type: "create", + pathname: "/create/initial_prompt", + }, { label: "Reply as User", desc: "Chat with Open Assistant and help improve it’s responses as you interact with it.", From 59fd3720634d30993074c19ed56b3e01c6f00d9d Mon Sep 17 00:00:00 2001 From: Jac-Zac Date: Thu, 5 Jan 2023 10:55:06 +0100 Subject: [PATCH 47/54] fixing style --- README.md | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index a0ff857c..c48f67ba 100644 --- a/README.md +++ b/README.md @@ -6,21 +6,18 @@

+# Table of Contents -Table of Contents -======================= - -* [What is Open Assistant?](#what-is-open-assistant) -* [Do you want to try it out?](#do-you-want-to-try-it-out) -* [The Plan](#the-plan) -* [The Vision](#the-vision) -* [How can you help?](#how-can-you-help) -* [I’m in! Now what?](CONTRIBUTING.md) +- [What is Open Assistant?](#what-is-open-assistant) +- [Do you want to try it out?](#do-you-want-to-try-it-out) +- [The Plan](#the-plan) +- [The Vision](#the-vision) +- [How can you help?](#how-can-you-help) +- [I’m in! Now what?](CONTRIBUTING.md) --- -What is Open Assistant? ------- +## What is Open Assistant?

Open Assistant is a project meant to give everyone access to a great chat based large language model. @@ -46,17 +43,17 @@ docker compose up --build Then, navigate to `http://localhost:3000` (It may take some time to boot up) and interact with the website. -> **Note:** When logging in via email, navigate to `http://localhost:1080` to get -the magic email login link. +> **Note:** When logging in via email, navigate to `http://localhost:1080` to +> get the magic email login link. > **Note:** If you would like to run this in a standardized development -environment (a -["devcontainer"](https://code.visualstudio.com/docs/devcontainers/containers)) -using -[vscode locally](https://code.visualstudio.com/docs/devcontainers/create-dev-container#_create-a-devcontainerjson-file) -or in a web browser using -[GitHub Codespaces](https://github.com/features/codespaces), you can use the -provided [`.devcontainer`](.devcontainer/) folder. +> environment (a +> ["devcontainer"](https://code.visualstudio.com/docs/devcontainers/containers)) +> using +> [vscode locally](https://code.visualstudio.com/docs/devcontainers/create-dev-container#_create-a-devcontainerjson-file) +> or in a web browser using +> [GitHub Codespaces](https://github.com/features/codespaces), you can use the +> provided [`.devcontainer`](.devcontainer/) folder. ## The Plan From 881af7a999ffa07165891e82d41f0c823c44a012 Mon Sep 17 00:00:00 2001 From: Yannic Kilcher Date: Thu, 5 Jan 2023 10:58:35 +0100 Subject: [PATCH 48/54] updated readme --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c48f67ba..a369fee0 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ - [The Plan](#the-plan) - [The Vision](#the-vision) - [How can you help?](#how-can-you-help) -- [I’m in! Now what?](CONTRIBUTING.md) +- [I’m in! How do I contribute?](CONTRIBUTING.md) --- @@ -98,3 +98,5 @@ hardware. All open source projects begin with people like you. Open source is the belief that if we collaborate we can together gift our knowledge and technology to the world for the benefit of humanity. + +Check out our [contributing guide](CONTRIBUTING.md) to get started. From f85d5705e54a34c2efcc238b9b18352c0d3fdf31 Mon Sep 17 00:00:00 2001 From: Yannic Kilcher Date: Thu, 5 Jan 2023 10:59:44 +0100 Subject: [PATCH 49/54] updated logo --- README.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a369fee0..3c17b704 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,7 @@

- Open-Assistant -

- -

+ Open-Assistant -

+ # Table of Contents From dbedf4ea0df23149ec356606710485946f2b26fb Mon Sep 17 00:00:00 2001 From: Yannic Kilcher Date: Thu, 5 Jan 2023 11:00:26 +0100 Subject: [PATCH 50/54] updated logo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3c17b704..0952df71 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

Open-Assistant - +

# Table of Contents From cd4e67c7eb715dcdb25bc3547b675d6e83f05c34 Mon Sep 17 00:00:00 2001 From: Yannic Kilcher Date: Thu, 5 Jan 2023 11:01:20 +0100 Subject: [PATCH 51/54] updated logo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0952df71..54dbaa1a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

Open-Assistant - +

# Table of Contents From 9686ff6165d97a713b84c7a32d5939e40b2cea12 Mon Sep 17 00:00:00 2001 From: Yannic Kilcher Date: Thu, 5 Jan 2023 11:01:45 +0100 Subject: [PATCH 52/54] updated logo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 54dbaa1a..4deacb50 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

Open-Assistant - +

# Table of Contents From 0a0fe2fce2ab789b284f5dc6c9788388d0011f19 Mon Sep 17 00:00:00 2001 From: Yannic Kilcher Date: Thu, 5 Jan 2023 11:05:07 +0100 Subject: [PATCH 53/54] updated logo --- README.md | 2 +- assets/logo_crop.png | Bin 0 -> 24144 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 assets/logo_crop.png diff --git a/README.md b/README.md index 4deacb50..f8d85464 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

Open-Assistant - +

# Table of Contents diff --git a/assets/logo_crop.png b/assets/logo_crop.png new file mode 100644 index 0000000000000000000000000000000000000000..20630d6b2a99148cb4b8079fa7ba53fcd5468ab7 GIT binary patch literal 24144 zcmeFXWl)^Kwl+GrySqb>!QI^*f;$ZE?h-tBAh;7O1PksOhT!f53&Ab8-%0j9`|Mr! ztGacn?)UFN&D6ZzYdziT>D8-yb-%Ids&Z(^#K-^u08K$&S`z?(3WR)Q5#b>vcJQ=N z0041`zqX#IrnwKLtGkP}og;|S)6W$|3G%hG1^|3lN^@+yj|IIWUoCOTq1_45w+-^c zqR(8y=_+yCSJ%65jo-$8gYLue2HcIk6ugT5>bXlbY^E>M^h*VXSj3I{{Lp@UI=C%w zxOnLd842w2*htFBG<6ZF4O=`9RKL7D6iw{uKEF5=Jeye+_v#iU+z_+9@VmYSi`SyS zjSpX1cfs4&hP?|Bb*+VZ@G#_UG5FzPe!O>m{mg*+#Pz^&OSam+I3N5%jxuc+8{YRw z#y>WR`NS1g7}6IpLE;laU+XjZI6R5D;y@4kvz9;TWlPwDd;V@uVdLxX`%}5*1>bM` zHqFH$dDy|Q0wLWmerv5)K040(8#SayHd%o}-FXg^@xjujuM_&$YXQGl{l_8%dz=q< z@Xv%Mc7}>sjnTA5o6p%eq}xJBLYUu*Fh+vP$)56R1k~CSxdkJ6Rw2Vqwh63Z#dm^Qw^R=j@J5Qk910Xt!#~UX zjk9ZUeblbMdn~zG zyXWiMjp6J7(D;V(!Qk&Kp2YOu91%FVw7c;UemG=AY>H5DCm*>CjpI$Sv?d&3UV4S; zv(g%N(>OQ@TP_b;lh%k{t+cZqLw3j0?tm^YV&l0QhcDw}&l9Vr`i8|teq1-6Mepok z)?0kC1dcc5svfUe0crEw@#Aeg@dX~aGb@Cf#Os0Hl>y3pLor^HT{iYr&>CE)OZ}&9 z_iO&7O&0-ben5&NLc5+8LhkVjZR2L%)4G zCDUELUGoE1>?~5!%IorOu&PX`#@z3OPPmyT@a%mbSWGAIF%S|LjaBk|9`ttM=S)W1aW5-q-mOSM)N*%QH>Piae z?@Eq!JZ|p1D&&gYtIGE{Nw+YXZZ=mh8a+4s`l{m;vZr`^2bWJuG&Q43)@_+jQ`zDd z1gy)q$&iRnLWT2+Yl7n6Ze4S)+7H$04Rn-wT0))0cE3n_7qxaTLDQuiYH|%10!rxh#1;31}dAh)`0;v`U&#f;k^= zs!gX!(Jr_ns`TB_DHk60Gfcc-{M7EQ)C@DL{lU2v4&JIcJqkUPrce2&*`AqCi3cT~ zZ}naSV+-1+{ANYOAdM^rsxHp7dW<~Tw8_~=e) zceYDCMGCGsrz+yc6pvtyLtjy*eAr)5>ED*>?vLZ0}9oi+3FNuS(8p$l>N;+Wx{DZ^B zjO7!KRu#6v3FSs8TDBDC(*9SSX66J!7*Cki6z}MKY>O`%iq5-+0v>L?Sv^QhuAesPaM^dd>VSU{tnz0kQYC_}vc;8JtJiNVY?gvffLp z@6quz)Osz>PVktM1L8Yyc=rt0p(eb+)L|0tL6Tv~o1Mv&e2SYGw615q-k%1Oj6K`@ zz;&a)j}Sha$G6apB~Fz5C|e->O|RD67?x&)Kh+2x3;!jq^t~SM5U&zW^h<^8jScXW zUj3+)BCPjxvsSIab4>+Ukt^wJ;e*$As$dgs%e$E(*UCf}dB`*@al~bBd`LR9&aD|9 zq%P*C5x+`fNED6F{(S(G1Zogh4sGoenTvLAUHo^#XL_+X>mMNX_=wxy-&Vz1pAz)0 z)7aBTr*`1X3UMt~x`lLcH0M$+43dSILb(gIn{c7&+VxX_r6hr#@Xbd_V|xkDdeL-F5YMEz7J887&NkJos-Ix#^=#B)3TtcNh$^wJ2?PR|oXZa#n%1aL>`37z@Y6!^KI?9{w%} z*|nMh0Jtp&Jhhb0Jo#eu24LO0sdogPnCj%c`t~!t&Op>yZ7c!*a>VU=>mbK3&^$Ts zU?E3TPG#fsOIyy$ z-V#*bh}4!R@aNIE{}?r81pN(6TN-U!1UyU zD~i$V_ybCi2t}i!g!4^*5N5?4xiTf==2yGfp8`H+tz5NnMpovd(&~ePDLI(c9u6~Z z)Xig+>gqTivP@u~{Vu1v#o5`$hpaSit|%kxA>J{GDosS)Jj`S53SGUan*2?luE;l( zqC}u=4kQ9oYLrkqo~`qCg(Xq7C)7=mV62`So+_e(30Frfoj$#qRcv=`KVJQM1@YJ4^c z*h4>k&}Uf2qR!J^WM$fwk*Grk68Fiz4^VZ`t7Mae1Gplb7@ZBodSi|y5OiaRKO#yK zZGgg?GpwkzR5WT3e@O1mBCwie$gsgOgFLZLM?c%1L_8eOp4Lv|lYEP+EIHxq=NosU z!tEp99D-6EM`huwgT6sEN_>C#bx-_bE($sHduozan!?}##3y;gD>7X7SlCnfW2G{U z2zFRb(a2a{XbBW3@~;=3Dd|$JX;9liWo`;wpHzalDc=jOrE{;+``TeBe{45_=QX2o}6YcMA za)a|1%@Z*5Oj%dpt1(>41io{SPtm0ECCN-P1_@IFy}=&7Ek3L6xcAi|(YDS8!F!m= zO>!i|fVfZMhEz+u!$b%(>s|eT&n(tuT1|y&1#1RFvv@{3{R`nyy=cVv5<#KGoUV1; za6|7aK7}#L$T9%=D2vRDz{dFd0*)Rtv_(v#F%xpX?c30fT@74^;*BW3C7H!Nf}M z#7#>YHz>ew_++ceyd%>b&XX81*Ecl3oV8O?I?`)W%9(|;3yb~MN+v1lz}BrE)n*`f z!eJ|^)j|l12TC0-SN&sL9aad&s51}EX(_#(j6WGP&2z96FRn)`N_HK3RJ^%{(&LI; z8GnjsX)`6*YJ+N$&*ACruyvoNeN`4+(M&u$8Bvtl|-lk z*jmeGVB4&si!=N}O%+DDTd|)=EymUXMx^BK%D;(i+5xZ$tKH(!l$WPjxzrOm>C;#k z$xGZRw%k!(mqoU;2(*g6*|+8vADafennwUG?}_DWqVPqu)ogxZRi+dB@Lt)@e_nY5 z#qiSnWblb}#?<(+``7(_hu^Mp;&16(m^4We1C|5-bn_x#_IfB8Us^`TcZ9a*a^&M! zd_egJzHbV%Hf*q)4nK(+qhXmmpm%M^OuTl;-VWpr)DXebAS;99)znLAj1d&FYYF{6uOuE8eZ`%uHF zaLO)X({=leXEBiyEf{kY(@w;BcTi+bUh!3rUP1W2Au^pR)MSWa}lk=wbzE|2&V2DvXWW z9fuuUMHq_B?4?_CcWXmV>lWWD%t)0p@OnB=5-5h{65(!z`a_ZejlVA|-c~ zx0EPYgWt{IqkJVDw)4=-mEO}7Hp7jUpA-AH1Ey)_IBeY!uIS?4$-y-i znicsaKcTCHYrQtVeh6Rz!Kt4jCoPZXCH~zt);{vZ>BuDu zFX#osfu-Z7dMN&?U`$j?v_u5YyMLV?))d<;bm;D-Nb&pZ&dTS)4}}O4KuHA$RuQrg z>&F-{9Oz1R-8oRm`v?|4Y=U4|wipwsX&z+eJE>d}(1=kz-ZoCc9nN6MEm@)i^ZPm;VpCi?4OqHh@57Xgfe5@t;L6Gh zjBd5Jyj!w+B+*-aFBjb=i?=W2=*$5>3*ciID{~l%e><WX2E2lzgr{Pkzb2*&vbyCG z8)FEo3n&)nqrW}g4OF&JIl`p&0%3bxF`;D5z)1=*P&ymKx+@XP09DlXd=H*D05m^Wox8!&Ec14n{)L5_Xz|fj6h* zJ@DI$3{_f=HlsGK=9Z<=ZtYB51X&MK@0dDm689EK*G&sDLqwlTyS5jvOp8@I(gqp2 zzsc?8)ZR6ogPVHBO(&T}`P6?no1vcQXS%VK$dH{>E}YnjaH~j`5Kw&8acY@=ae%Tl zAUDWr@v{7oOsHVzsS{yanDqASMVDBf9wFsUe6+X8qI|)&h-(ZXt1d;%XZ(&``;y<@$3^OpZ&b<5zbp+GXTpOLaHI(WR z$-A|CH{Gu>%w5%UlEOb65|woW;q))?<`p(1W#u^oO%{H6gv^bkfd~!OQ&31466HS9 zRjg7fip6*$HuQU=NqgND0bs+49L%2vXAF}-R81ZDEFO+p>0NvB-WFW^ILAnbB&_ed z#|fOPeSMbKe9^Me8}S%9%rIjWKrzuI*ibcYPBYXRiRUawf#RNaV8W;P;K=wrdSM)_ zxzm~a{P+FehqU@yPfyS9zxoEOyjXmjuSYv`Nmq6uo6L?GOAkl^;PFMUr8Hk-$Y3KI>^l2T zIUh~S^~TV%;sM>SUDMP8?-9Zx&B@^EjT5>XfvTS|U;5e~>Dt*J|rDy$~@t&&$ikZY8J7o3AN zr&w$Jy9+gg}(Z~Z78|kG(mqp^DTyS%<$rrKezV4GYt0EyS@{9d4a)N zhow(@O17nm*V8qkL-H>S0ZhzrcW61}jnAqkTK3-N=NXuv8 zY6I~nMKPn!Uh9HI(KBypCSfwp9tTyMA5AF$J>z?CcHWO^&dVVUBsUonBZVNuRIO{P z{P2$_JC=UMHW-u-e`U_~{gMKYh&dIcGsffOt6Q0Q@Bayup@4V@mV8!)+izLfW#l%k z+!UbsW*0LzSQ*+g47F{#sIK&bfxXU7J~lIBmMQO%g+`Csfyf@8Pkct9i%%;lzX%ue zM|qa4YpySWLt}P?2lVj%O;{@OyItPaJ`EFGiCoi;jiDEV+b6Led!Xi#lJdPRk>HLC z@|S7mdBkW;_Tq9GsX5;io0*kr^3hA=BKO&{nKHQRM{eyrl`5V+XV!fzW$p2PA*Ykp z1(Pdno@R>7+5U%VCR&d-C+dSACrB{Q_E=D7drjEUZh0|I;-+-4aX+D&Zd`uAV)D!* z*O^IORB#YeU-4&!zT2zhF;Ndc{5Iy4&5sm{-X)RWaVZx!1sj;&flPLoiCJr0@qyK9 z8^1U#F-89q5=GUkR}6JgC#^byt7i}_ySuKN35gG`Or%fx%N|x}c;yY*^`q9G~)wj1)!|6z`oK>g=UA(JZ73 zI4^NdiBiR#C`KVB`?;bk5_0Yt4W7XFDw!+;2K{R)+@lR%*RU51)x1CV0Dc#~ZuHWp zy;&h@Q$(bZNtGLvA|gMajvJ{qo32>NuO~f7CFS*%l4-l*?HkERb>_xHv)>OWD#Raf z3FP{kHa90N=KH?s>muk%U_L>)dAVtNreL2bAp8Rin)ZwVPwPV5+$o z2<`=KzhNHr+xFPz2xpal8ut3O%Qrz64VO>E!+GGh{ia^;(4sU>e_1`YJjT-RZAl>2 zk$e6nzD<*X%}Ky-0$Jph5za+BG@4i$7KwL=a9-N#k&*l-#EkSap84nJ7y`kuM#ybU zWCksfMA@DVgi9HLj)}jh7 zo{dXbQ!QvlMn2pijMgOZ}i&{Xi*h-MQ{ixm|9^_il-wtwpsHdVVXzAj_YHsCX0b=!aa)n$k z0sz8dzL1T91IUxo0%T+7EJA(O(L+sXXC*?d%cH`s;wlBQwUhUE2Wk1MYFqj{SPEEC zi-{r&`wBt;oIsxDl)g@m&K`okBGi9z1tGux6thuN{uS|b5TVvnQKyu0aR*UyvvRYt z17&>eyg8{wktv1Ut*iw#rDgvK0eKRkw)OOM6=Y-c@$q5x;bL`hw_)QD5D;Kv=VasL z1VSW$9)8ZA=Dt8@51Ky^|G$pIXSWYyM~9Sj5h@2pB?%?YItZvZeiIpK^`t%?v@}KZ;-Pm z&A&rfS^i7k)yv)SuQ^thY#>LF6GYSl(kjP)G%2T`qW&+9KNQ&5Il2DTf{^_mlAdGh|33eqCff7%zca>Uj2cx zfTL73;t%1166y*f)SRsB|6QW)XzpnZQ4pb4F}I{t)B5ikZ96BBmZ$k2Iyrdx zI0ZPk*!kJ{Ie0lZ|Iz8cfI1*|4~Q%N0p(z4<>LIS{%2eSA=W^cHUHzN5P-ks5Mu%S{r(pHV@hd(-2UN{wwfrVFsf2k1|O3fYy|?}ml4`+QD9K3!UjO_Tc9o|>N|0RT^*sOp^npJgD6mU~6{HZsQ$a-rVGjxp z8TL&IsE!=~padvLOKAJ9oOJo*uFcKAzP1sMkLd&$FT$a}8qn%J;+RLND z<}-*toGHlnD!l1QV@M9AB1D9jl*A~@R9~+cS|Vg`^>I9FKlbkez5n$1Z0Tg=Z7s)D zl2Y?&a_@RY*Tj6c?N3JvA^rErXPoX_;Y5 z{_bXnSc$#aHmdOlEOpX+`H1PDOh4^sW7y!@9-abLN*4t7C@9I^CGv>96j4p#rwpm# zjk%D9-xQdV*voXV!HbW?9UtvddK5OP;Ss^C{72=_`yt;bgqVxvYSvtJ?(Ho)V0&x3 zLLL#3F0dm=2LV)Ok{BTm1$&p6;xM$VpM`1DjPqdJT) z_S(P?psIhbUp^S%@fOxW_IoPxsItM@|yoNX!^!d8IXas zdozdb$EbERSLa>Zdrx|_u%tR!l4cp*7o1qlF#v1V*-VImqN(uN@d# z==}B**nW1Bu}jcsO1b!k=U6It6(IUWo`+jukE#&Hv4`}0&x8E6R%68e4`el>r}?xW zVW7FbWt8cZTwsqZl}s9cODe<-*Gy?n%7=-U4{-MDD^)|)dfCM#9mEh0pRdH4a9BR$ zY~rxPdfJ{4OhtE@Ux+awoF|aU(wQOJuBegSHN+1qcYh)hg~D+b$6^20S3uyF?iOe` zLuKZRRs6hkfP?#5;tnVXr7E=R5#E|qrBDOYUtI#aY5(Zgw*Fz3GqHd(P!I!ZoagIo zG$Izq1F%ci8sup|A&bntTOw8{NufA z!toW=T;2qvk(!hn%W^gKW4UFP159>U)RR4&Kfbfch7ZAqiMY8edL})-y4W!meLVk% z0hxemj=39@3k0JIVa`D4sL(A4hXb6d!-|5C9!WjIO$Ae(UKI&KiNZvLYB(PM*o6k4 z;qQ!;sLfjVe`bnfws5= zrv#BTUQf?hO((AbJ5(H<-9x>mqAEK({;4<2h%Zn}3t?Z80^#K=0P8)9oBQ2Z@zn{1 zYNg^Wt>XQ#3w(xmU;Fx*nb890SwxyPWTd>FwlX=>R^=67LucXl`JF-&33Q3??gJFK zX-f@6i#uQ891RghtfSP#5iAn|63F5Jd#KYFdEq7e#cH&pVz#emJtBgMw|tqc*Uz+~ z>yB;qVpL&b(Fr_9%_)o)`nDA;OX=Kn^?FP^EPG z=$(*3LHO;OrX-QyTN>a)P9_Jb5)xINe(j$+kXnWng(7RM2VW;`hG~sVbrj#p-~YlS zOJ^Bqa-mmj9x~@44))O=b3^DkNG5Ei3sWd4-498u(YR2h7_nA}29a)T6c-4pgHvrj z3PC^S3HpeUZHQ7DeC7Eez?z8M#hvnWL}S4pB9|2N}x=bzaX^B{OPL0r6( zGCPWy#DB)pk`PxE^3gRgp&WhcBH<E<~lg-To0KT)tX&` zD8Ls3Emf6ON}D;MK^qJkEKL9FjyOb+UdLUIZ80n$G0En5ul)5Rb+Cx8AZQbW*-~txFf7abpN>leeM_?_Q900ix{fjFH<7M;GsR#W3Rg$Svd_HoGY=(g-n>2H+_H25q~^|=4)?>Qo?Y@ z$kjzlDt0i!?HW4F?7f3(uR`D8p?4ji(s(Gqb{IR4liV{8V5l9oV~KHu`JU7S2>Raxyb|qW^Dl#SO(UE`$TPS`KRU z8J#hscFx5cv@0>V?TmAumQPaR%wo+1%g?IDhSI=5D>XD8w=UqPM^Yt==}}@?n}KLsCAyn zy_&QoVB(eOfaj_M>D=2qHa)9nYXB5kJmH_{ zO%(6)uC_>pEJ@5G<~gIzKfMy>X$2XW`2yx-`6$uuRC0%NDF4Vu z&&)nt;hSpkH@EqUrY1Za=|>bBjDg?d(WGVPwt876~<|HHsx^L`gTom+3K3+Be3Tszj`u3yel z2$xbfDRKK@0Qr$nfg5^FL^RO^5BhdYWjR}}OR_f!(afO2v7VW(SS;crLR|0SS-b-I_nM@@rL*vjX!})ZixJc z(g4JmI6*4t-rB?j^LOKUZ{CiaLv6-%r>O3AnI=Suems;uTabP+H9RE7xjRAv`R=a$ zRyE(CT7q?5pd_1pts>nZE^CM%&{0kbrO*@?5=e3<^W7Zd93)jVaS@_Q)Pmpp5v$3w znY8p6{xe`az=}ftKIt^!C^WgE>!JL65_QL(=+AzSLYM3{u>sDOI{p{1W(2Cq<(X-xv< zdOmNTsl$evl9LZagqOr~v<#Iwo<^(f9bDZNt#ca*jNB*1EDplGcq;DE74M?iLxmKu z8kBETuD2LeTO4m!r=Lu6hz8mdF?rhTy>_^{#UBxv$KwSE|5@!t^U5pgT?AO+yQE`} zD#N{Un@qjm)oie)aL>8Dfhu&7xk3Bh`84vD9=Kw^D|{w*6T7)}0m=&*Xc3ozb)7^4 z1$5{15?T_0KcW`!^mc+p?F$f9S8E$7cDgS=@qaF+-VtU&`m$1{<3HQNB-FWm$h~i- z5Z<&WwTikIB;FJyr|NOKtUT0F%oIi4D=HMKEcHx4LRO6u~#-16jyCLAa zZLQxITWyp(SN)6|sgZ&uzgn@Qm#U3|;4YQXoqcl5&Cz`q%!q7${)QS*4 z(z4hj@ywS5cUwtwKUUqNSLsIf(hYEJ+)Qp%;GbI>ct7&c&&6z>FvcD@BzzI&EaQE5 zgdW>2SU!po-z&wh3;v+(jAiNlvWETo zPbT!hbhqE4FH2DE3=GFlFxT;uXup`W7xdTPG_1H>=%CmomHRJ(55DX@rC&9r^P{^8>!-hYf zhCloDuuP9qTRUddDfWf;UBm6HNXwSQI#0SAVn~J(goiI+jQ!6=GXu4}&sP#;!sl{wC)fAVNW!nBx4i{zLt}P5GKMTH6+W-dRx_PrO^>#)ci%J) z1AXd!A~!ua`!^y{3qYiXP@JtpkHBCA&v|y|+9Vg26-V>@=wR;M;;)aEiB= zGYX&K$r-a%=BSUHG)HTd;3}`Kj0`^xOS+pd%fXc2S8~IWVLpAfvGenri^-{q_v zUvu2q48^V^!>U=kBd(Jo58_2N@o-x09F;2z*I*#dkNHvqD40lnyQ{&Y`eXU==mZ$i zF@C?yHnTK!91QyjxLKs8**vMDq`3X!cWQMX1=ra+H_V?z0+ya`FscUTFc-(ycY=Qf z-$8PU8Nc7gmTfigl%Fl3ccS+L#solskenR%AXY8>PxUnlzjkRbLbfNq**)09`aTqUE{1u2Y^hjdj=Wxy-+C?SDjA6u#o zXRyTrnDu|(i*zgv^JfSp#sxflc+1N}D+T|Lcu*Igdc71OX_~q6*arkOmWIW0rPe$> zM?fk2GKM)R=qFci=-F-VVmGJqmeZOTPP^%j?c648UQxAwkXTN(lhar@M|%ALE%pUO zN&)3@F{+7 zc;`b(q09RQrOP);Ft}1UbduQ21gaPtvZxg18w~Nhm>ED{152|UMqq0S(tGE=$xeqZ zrzhV{#34DV2?*&TZHC<+pnDLBNrR`Hv%gBTb?bkPm%8gn-nvXMFa&>wf9eYuZ~*7> zwaBmC*+Zuf%&xD{yKQkf`I^^nWmac6H=RRLHd{qOzGb2+6_ynXuk)0%#CRsMG$mq) zoBW+BEIU>r-Y2|Goz%pflIyK5Y>}0yyz(ZGK_nwflAN1M(d63qhmT9%ou|gzG|=q7_Jmiccama%Enf` zX{UF?4=30J!mCjfq~rr=mL(M<9yT-gBr0q7CY(D<1MO8RkJsbwy86t696ECs+UT3J ze!m619dZVL>BQ#a+54JGH}IVy{A#}4+wia^#hX6%*ho*yY#;8`LPd7NuY&*LFH0(S2L;erIF1#>u>V5P+S%ot)XUfXG&;MAQSZ=1%~> z;L@#t30yUn5<)jzHrwh9Y1%r5@q}=H*Ek_lr1Rmod|KjFU61DR1UjcvRcqu zJ>2iVnzBG&RV_@lC>Ay=7AhGPIv5oS{@_UErJKsN@8Rbur(1BzSXyGKFItwI8Gc=x zYm%R9%Jh5OcrWW!a0*@F?N9zF{^p;VhNU)X@B` z8BrQR)ckejQ7{4aOFhvGMPD>4(jmV6B8YQV?etpd@R3d^$%8Lbo>++7lex@p6pdbw zWye>2#vE;GVF}Vr*I9lSA4BD=UK_8n5{Efh_I4Lo2DHS@m=`S41+~!*EqV&HqZ)I_ zda0XeA3|lntb=k^&BftV8Mf@8LYVHcCz1khUCrV8+2V0u9`Jg690y|AVLxIcI)5wN zhiuLM`ZU;^u+#Z`e%*OZ|KVVJkuU+kfVSW^REMa#u6El9V`uAwCgo!UAYP#@19F{j zW5b;Ngn3mhd)+TTgnC`~vJS9I3}tn6l-!_)oFold5HXxt>hkFMhBU>x!E^<9c&uQT zRPZDo5cIV~9r3?XlC6)-$7MV3LT4nzZ+}fWIS&a=31dW=(fc~jbSK>1G}5~b#7EyR zQdN5q^9fFY@N0cpV+D!txZF2lq6z`^pmZ@!P_3vL=!AChn-W$+F~bzgb+rbrQVkB~rO_k5p15@eef?C*(sSyS2T#4t2nuDK+dBo@Gx_4rQTV!laU`F_qU z9#4q(cIG?G9R#|f-c-+aGtS>fQJURS5{_KB78S;!u~*PtMtd`x zLnH9KOL5KHf;7nIT~F%eQf6}N!|q_sEkJn6>5P85JwWCCMg@>Hm@$V468hjMTog8& zTRzm}H4gE{u9h(f<@u->ZN91|Ssw20@)wrl6GyWqK`+L~T1?D;Y`wnYw;z!%-@DjB zqj&ZlzjuLOnvOnF5l*ebOGUyT<)>pifGl0N7cM})i@EYrH9WdditZ26K@5hlq+IOp zh;(%~e@{%5Q&ONm_KC5e%;y^N z_Ic*_fDe$=oRsJ>fe!wKefGX4uUw4F_0l2h=my4tyE72fR?ZfBFS7&5BTkrCh5R|4 z2skW1#(EUkGwa_@fs=1GGsagT!q2{%*ZIPTF@_8=6Bkw|QG?ma3;i-cN?e?8KLgN&ks2k3U6>g0Pc`ZOYRR?X`+BIR^<+dsC> zAKqc-d-K(uj!)>asvVL5a*HOGv$BA@jT=QKcAgUGRf9XxQbiT1@(^wpc@zSV+IXK{ zy0nMfNiE~gbxS@;?YXhs&Ct89Um^)}^5tC?0GA!NKHFgPzh3Zu8U9LQm66%IL+}yn zhHmBph9=YRtw0SSUgXk=aB~@5un==(Duu(h00BiW6@}QIz|8)7Erat3;?z)`E%XTJ z&8y(MH6gK`izuY0vqoC$YVM1@hMj=kl@Gl_Rm#HL3<-Sc#2qFt-`OGOm$!KY;JW-k zaYMzova1cx4L6GyC9kw~Mf$-(KL}i!UHfL8_=74EOKxJ#AQ6}Y&6bNc-LanCr6jUN zhxkTpbzdj`o-r6D3*oXrIFzlgtHqcwU?l_oS1s2CxM7F=%MC`6nxR!8>#SizKmri( zbnt~=m8@*m!FVP%oI3L*5Bce7oYussvx_rk0fl(RO}8{_ioPF0|EvckJlv z$Ke3-9wA_&lAPQ@J!_R=TW|OV%K)Pk>eF!oDoHig)Zvd^Ei&9O;%=r(SKDJ|u(aDa zRzz1uyKie^B|%1sPbp&F>wR#--_1%IQGll@nollyc{x*|)T8YAnhfuc0LsF3iq%gc zD+rLR{K|pF?ARB!^|q5!y3O((=Soj>6vG6Ylr~M_N79_Xr-38Y7iE@~$HN-jvpu_a z{7}{Y?_~ZQ@lWL^K~n$ChZ6l3-M6|)u@;^b386Ow#FeDI422>X$s~yhMbslYupk&Y zK#w46O>jC%I$??9_h1T9Lk{B{IhD5F77X*AuEU3+UPSccO7m%J=2o}Oa>7xmp!_B8 zh_n?DHorPRr1PS18=sbH7H~+_R13?lCi_$AIzwlE7hW_^u97F%AuVx)upvNfzj#Y9 zScpbOysCMKh4`g0ex4zRK_8#~nDkpcNkr$~&nZ)Uokw~6FD6NRJ=n3srjDJB{zcCp z&|Nr0jCbH@veqAE7|<7B_CmVf*9&L0F}6KRKp7=ycDB=rJT3QmQ2pcsqM-OcTRu8v zzTpYzy?sQwaSOfrjLY9h@yPyM9PV#Nb~c1)dkb!^kFzTPw-f+km|+fL0!#qZlH0$2 zJo1R~J=qbh;@r_gRnHKE(cX*KW$%z60i@bvd+!Qr&whIhzv~>?L0xmo(=Bk!PZJnm zDEvtHSobn^`dnt>-NQdbc=;*5;beRw$<*?a)P3>C zGwm7_XoKCm^=p^Ucq85GsdQ(@x1nE_xCSO0{nOoyC<4UMjs$|W`jf*h#5RU%F&bPJ zS^1%#h!8Z=^yAJ{Ppkaxri)Zp=y!Pi*O!Yfve99=e5jle%<6;h~ zOBmQxN}s(gY53F9T>X;#M4szK6fQ^fJFaj>-{W-LsnU4L>$o4rPS{gSUpV&ZC>Ww* zZJ!%EUu=gA45CcWCA9l2U2HAhpD}c`ts$np-U|j_5{+&=Uq`OL$t*VVxS_3^EnlkF zb=qAB3D)pX+-@p`hpM)CYR?LKtQe4E9!Ih+S1Yode0{%Re;Jp5zb0;&?Nd1dc1ixY z_pPXxI5=@<`9SM@$wLR{E;!j7>NY6zX5zK~ypC_9ervNnaZY{i(hw~wN7oF2^EK3l z9c95#uP320?$F=UgxG-?c=nV8$~6ORuzn2SK2vo?zl-J;{{(AdVjHTdYiLOK!Y?Ev z{#YH4(+tGp{GzM11KTib2Lo0BbppvQ{2%-zB2JHu^~-5$-ad;zsqp&;iFnr|)6c(VajDo=+^v`{DI zoT^2kXcgQc{7KOm0V2rBbl3Rg-xuH{{fbSyQlwX?3xkY@ko?-@E(sXZpxDYwrsW+&aPk>GXk;$`>cI^s`cYr?2cmZQnT)4+;2tY&Kbq9Jx&k zHZeUP^zhH;0=(%2cU=jDoqcSkqkVA#zrZ%O z`%s$Ns3AIrbaBz;iG$c*POkK|Z7#N|<^1i#!WPnK3*Ywi)~{KP;@>Zx!)y({99?C# z#pitX!)nnIRmGW-VBCrDQi{wIlcnN$>V@dnNLMxM`2}is+DU zG>%*FcYxb@yfrR|&rxI+?p}~_%HX8A)maa=1P*RI2A$%bJ7n=1Trlv8iGz!>dWap? zAZfU#NJC&tqjS!np%R+)RIq+Y*4`4;s-`)?xr(6Lo`d$D`I*otQeex~ZPE-o9n?ux zecnhfjM4v7!i2p7yZ4^wJllOf=bm%#_uS3Vs7Kds4D=aOhpr7-iE8Gk z#>RZMR$PivI=)~3xb(_^DYS=;+><=>kp6r^whUsFA?n7U+aR8rc=ZyTMFLeA?+04= z?8b#@$v>YKjvdF=Ie7h%y}4hW14C11w~G0B^@Me5FmO(ItEuA4(u7S3N57Wvy%7GD zz49-L?!r!W6AE?ds=emjg~fw)GiRQ@%Mpj0H@td}P-Hu>U7n4wg)|aXLfm|o^sbPF z^~8+$HSZ-a(;9N?)7%w?oPxaG-B*k~6j;~`*J0c>Ket+}_5h7*4kt`2{rze3^>1OW z3|eQ_{LF)6o31Frpl$`me-F)?(LFbe41YdphbfEj*@ZOHzxw_>@7CXJ4-8Y-tx70; z#sX^b#8(Hvl5$-LIhWa`aax;L=(1WY*p{}tvPDuxX=-2v{h-{~40_JfIcH3R5J-CH$EjluyQg}HnJc4rJ zd>&rqY6a852Jw>g__jW=4@YbW&Qn$rW?1b5Wk$x7qswlDIvhTJH{#q5!Mo7G~YyVCrXA z?}aw{<>Jn3V|4QllP4sBu%C=bsqtTakjC}6;JXbyyQhNRb4)rED=mw;BX4%13*61U zu9uWfR&W)MX7~m?adLq+#xyn}F_ipcA}-bQwMZ#J9q~9RW|LZLIf1r5ee=aUoOI?H z)+Kyz{&+~3d5UY!b~~3bTu~@`Ycl`MLFUy9hckC_Oed_7YnZ~`9>0M&Qr?+Z>(2U( z0ml=k4vT4$BiKiH0-$#SN3-&l?z7x%SPBU^NRZ zGO1ivIjWKi>S~i#Z4K*+zD>CW@I!|DY5sJoBH-Bq_FIRCVOuzQ`bpjZ71fs(AT%v?Ir?`-`=-XS>5ztKd+!)s{YDF+D*YwVKACC3?0A zuN|7ic*8rphbhMY0~BLbjB0a!#RX%Gq*Lit4YZe7}|phXTF@+#{w}dr6A?PCUe}iH5MY*b`vkl#Orfzf)*skekZq zRPQFE>*YhXxkaU@epk^et zF&)Ucf;KKZh>hQa2j)SMWE>axy&mvH4ZZdRtXl&Qx)w0PftD4ce(sx)|##5-~p z{LCNHVz;s6RXPqJH3>C!GO*FCET``yhjij>=qNW>D~!n6dDotg9`4IG+FwME-Xw*Q z*~Ux-y{(Ao$jqmT(KegSiBPfBA?>jOE zZL>Af_f;8Hc0QQMc?gO(V){0)={cx?T*RIBNMpR}9zZYMqwEExUM=!gc!tjFEc3=6 zq5z8LflAnLl)=;yqZ9H`x9yuH&^_9vv=Ep!E$Yx4IA&_Y_MA{v^XhP4eIF7)7ZLX{ z(E8vAbL~%egv1-gbdYfiF-p6H|Km$PHf{-x($1vuhHwE7x__V%j?e!Giwk&5Z3`5> zyJTR_VNe+r$0j(HE*A2F79gTXI(ze@Rp-2D;+=F*7~3lc)j=5}KYf8-U}iVad*W(C zs#>-EFtCYmEE6v)!6pcPM!q|ViYBc5AdGd(o|JN6shYOKGnsy`am0Z#5~tOop7t56 zo^oFzRDF7n^Bza()w$;acvpbfNwm}SH~m$Ox=wcZ`8e69eNYfrwy`rj+8ns+$ktx` zHx-rxA@8SYGtHi%6IoW!Nd;(Zd_PBq)q%V$7pN>F__!+MEJNut+L?nm@?e=zq)C3E zD0gx{W6to=Smg`yxc1fVALsP4y;ssgkD(b;Yh;DG1WUKkr7G`#=p`^545feh#LIIR zh$E3~hYSFavUm0F$i}A41Q)u!lh&n|r0=C1Y0WF_N#Fm;Y-Q6fL=&o$(#cH>sbYXSrNdXKS0NiZRFy0_`lkycuK{wD#~cks-x&7_D#Bb9%JV%| zta*-@FW{c1ljNj>W+F{@wq59DExvkU_$$Qng>vOTq&U=$I(Sp=U(D!2z1vgk=(jpm z!Na;md?1ui;vpsO%t2ulqHM*Ld!dUV|35h>!l4e65mVJwx_n+Y}xr>-oWn{x{kp_)HUm~-Fi6p8Q~$ero=}i> zNw)Z?rV?!@&E1{qGNIZZkIIg1=Y*@=^!s$judDX)>Gu4-duD${&Q3eTm`-5#4_;0Y z&st{KUf`Ruce^pXu|*u(cNSXp3DCFAUVvCQOF?=(KpF%p_04CUP)>L-NDbWkwGL+X z#ZbJQ)V`Hj(H~{+7chkM&4C<(ZQ}z2w^p`k)rk4 zg=&o31KsWB+VYS+w`IUgVURYP!GSPd?Gs4-l#Z#{!E0@2T5mnVh=-4#XToY)4~$oW zjSkT@_`xxGvgWVuS#HhnY%x&U$T`z)o%n(6M+xmY&1QaWcBB{ngxH*lxZ3 zeLh$ASG;OAaqG%Of}O+{qnEqQ&tjP7CS#%cSJpO}`gZXwv9m;fXJ}hTGedk=#=`8| zfp}g1`WlqiAR)nn z6i^N#8XC<|R%Q)a-MApjOSX6VLY=fUWt?h~eLM&<(luAsedxM`UY7ze3-S*NOBGIv z^s@LiMr>d+%x6#yWR;a6VBqCswZPob)k*NxjAM`90j>Tp_Ps;9(XE%wY?(_z3;%bwa8k z0U@vfkwgnhQhD}fyJPOqq~bdoj>~~XYax=W*B>LECcBGV2A@Q`~6eGYy~9Vvqc4MO`u*K8msq8;!g5JORQd> z-q!HD-NWp;?x->A+Q7vF>lYR8-{EBzyuWWqtqOEE#CazIefE3IJg6q{V;<)ve|*zi zEwFngqshG&-!W{ResBfM*v(xFDd{FV5Ss_j&DwP&%ZPT*64jJ{+UH6~U&$&C>}X!! z0*OuWkWuOC>gOGdJ}!g4oH4vXwxr&)pGukY1bG}8aV}U}nc;+?201f@v{nnk|3ZTf z@8-z>`4D5B*sXcS+23+hYvk3925Fg^f(WYzK-y@yj>M`aY)_4S0s-1FYH zHVq&#$l}Je*RQXa@+^YdErEh;&-YIDn=IP{WiH4D^Gv9 zYdO@US~Dr3`q6qTH^VxpYUR%PqT>|;F~r?(X%JzK0TLS`WfQkdw{^f`L(fR-?Tl_E z1j6hNTO4blkPU{vp`Eq2gdUb0;C8ax0=|xG3E{yi>Yp#5W{0r{26MMEg3861GHO5E zhpkDFUuj%z0#=Wh8G;{;~U#B+#E-Y}@WvWHOHrh25R?NCPV^BMH#2 zFg>E;6ZH_e$7gKAeoWKAv|27*SR>=(~hP-Gu1hgB!WJH4Q=FbC12_OpW z{RJ+byK!$G$fB)n_?k4gEw>tvJw<`s`j5<&kPI+Fdd4>PsxL5Uf{W_85z)?T5=Ra7 zm7aHY1N~nV3$+PvP|ML|LriD!lLX$Mf6bE5$dV`<&gq+*3&Bj^4yYrRFwkxgm se;%{;sM!`@H0V64nR128t8|85<_OYG<~kFwZ3uM9*vja&fk)i`0CI)x8vp Date: Thu, 5 Jan 2023 11:05:29 +0100 Subject: [PATCH 54/54] updated logo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f8d85464..d612940a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

Open-Assistant - +

# Table of Contents