From fe6adc7d10ad0f74fbb9573757c604a77b764395 Mon Sep 17 00:00:00 2001 From: Nikolay <99244955+Kolpnick@users.noreply.github.com> Date: Fri, 4 Aug 2023 16:38:20 +0300 Subject: [PATCH 1/9] Summarization models (#393) * Added abstractive summarization model for English texts * Added abstractive summarization model for Russian texts * Added summarization annotator * Moved rut5 summarizer to dream_russian * Changed endpoint * Added model path to Dockerfile * Updated test * Updated summarization annotator input * Updated test * Changed summarization service url * Changed test * Increased timeout * Updated ram_usage * Updated ports * Updated models cards * Added more info messages * Fixed path error * Added summarization output to bot attributes * Added timeout param to dockerfile * Updated model cards and ports * Fixed problem with incorrect batch processing * Updated summarization save format * Updated dialog summarization model * Updated tests * Minor formatting changes * Fixed black and flake8 codestyle * Fixed black codestyle * Updated models ports * Small fixes --- .env | 3 +- .env_ru | 1 + annotators/summarization_annotator/Dockerfile | 9 +++ .../summarization_annotator/requirements.txt | 7 ++ annotators/summarization_annotator/server.py | 75 +++++++++++++++++++ .../summarization-annotator/environment.yml | 4 + .../summarization-annotator/service.yml | 26 +++++++ annotators/summarization_annotator/test.py | 70 +++++++++++++++++ annotators/summarization_annotator/test.sh | 3 + assistant_dists/dream/dev.yml | 10 +++ .../dream/docker-compose.override.yml | 39 +++++++++- assistant_dists/dream/pipeline_conf.json | 18 +++++ assistant_dists/dream_russian/dev.yml | 10 +++ .../dream_russian/docker-compose.override.yml | 39 +++++++++- .../dream_russian/pipeline_conf.json | 18 +++++ components.tsv | 6 +- components/riRfdGz86P51B9bL7fO6JR.yml | 24 ++++++ services/dialog_summarizer/Dockerfile | 9 +++ services/dialog_summarizer/requirements.txt | 9 +++ services/dialog_summarizer/server.py | 40 ++++++++++ .../dialog-summarizer/environment.yml | 5 ++ .../dialog-summarizer/service.yml | 27 +++++++ services/dialog_summarizer/test.py | 37 +++++++++ services/dialog_summarizer/test.sh | 3 + services/ruT5_summarizer/Dockerfile | 9 +++ services/ruT5_summarizer/requirements.txt | 9 +++ services/ruT5_summarizer/server.py | 46 ++++++++++++ .../rut5-summarizer/environment.yml | 5 ++ .../rut5-summarizer/service.yml | 27 +++++++ services/ruT5_summarizer/test.py | 34 +++++++++ services/ruT5_summarizer/test.sh | 3 + state_formatters/dp_formatters.py | 33 +++++++- 32 files changed, 649 insertions(+), 9 deletions(-) create mode 100644 annotators/summarization_annotator/Dockerfile create mode 100644 annotators/summarization_annotator/requirements.txt create mode 100644 annotators/summarization_annotator/server.py create mode 100644 annotators/summarization_annotator/service_configs/summarization-annotator/environment.yml create mode 100644 annotators/summarization_annotator/service_configs/summarization-annotator/service.yml create mode 100644 annotators/summarization_annotator/test.py create mode 100644 annotators/summarization_annotator/test.sh create mode 100644 components/riRfdGz86P51B9bL7fO6JR.yml create mode 100644 services/dialog_summarizer/Dockerfile create mode 100644 services/dialog_summarizer/requirements.txt create mode 100644 services/dialog_summarizer/server.py create mode 100644 services/dialog_summarizer/service_configs/dialog-summarizer/environment.yml create mode 100644 services/dialog_summarizer/service_configs/dialog-summarizer/service.yml create mode 100644 services/dialog_summarizer/test.py create mode 100644 services/dialog_summarizer/test.sh create mode 100644 services/ruT5_summarizer/Dockerfile create mode 100644 services/ruT5_summarizer/requirements.txt create mode 100644 services/ruT5_summarizer/server.py create mode 100644 services/ruT5_summarizer/service_configs/rut5-summarizer/environment.yml create mode 100644 services/ruT5_summarizer/service_configs/rut5-summarizer/service.yml create mode 100644 services/ruT5_summarizer/test.py create mode 100644 services/ruT5_summarizer/test.sh diff --git a/.env b/.env index a796ceb6e1..c7d1f5cde6 100644 --- a/.env +++ b/.env @@ -32,4 +32,5 @@ INFILLING_SERVICE_URL=http://infilling:8106/respond DIALOGPT_CONTINUE_SERVICE_URL=http://dialogpt:8125/continue PROMPT_STORYGPT_SERVICE_URL=http://prompt-storygpt:8127/respond STORYGPT_SERVICE_URL=http://storygpt:8126/respond -FILE_SERVER_URL=http://files:3000 \ No newline at end of file +FILE_SERVER_URL=http://files:3000 +SUMMARIZATION_SERVICE_URL=http://dialog-summarizer:8059/respond_batch diff --git a/.env_ru b/.env_ru index 95f9e27359..e2c4475164 100644 --- a/.env_ru +++ b/.env_ru @@ -21,3 +21,4 @@ BADLIST_ANNOTATOR_URL=http://badlisted-words-ru:8018/badlisted_words_batch DP_WIKIDATA_URL=http://wiki-parser-ru:8077/model DP_ENTITY_LINKING_URL=http://entity-linking-ru:8075/model FILE_SERVER_URL=http://files:3000 +SUMMARIZATION_SERVICE_URL=http://rut5-summarizer:8060/respond_batch diff --git a/annotators/summarization_annotator/Dockerfile b/annotators/summarization_annotator/Dockerfile new file mode 100644 index 0000000000..808dc28999 --- /dev/null +++ b/annotators/summarization_annotator/Dockerfile @@ -0,0 +1,9 @@ +FROM python:3.7.4 + +ARG SUMMARIZATION_REQUEST_TIMEOUT +ENV SUMMARIZATION_REQUEST_TIMEOUT ${SUMMARIZATION_REQUEST_TIMEOUT} + +COPY ${WORK_DIR}/requirements.txt /src/requirements.txt +RUN pip install -r /src/requirements.txt +COPY ${WORK_DIR} /src +WORKDIR /src diff --git a/annotators/summarization_annotator/requirements.txt b/annotators/summarization_annotator/requirements.txt new file mode 100644 index 0000000000..41b2e8696f --- /dev/null +++ b/annotators/summarization_annotator/requirements.txt @@ -0,0 +1,7 @@ +sentry-sdk[flask]==0.14.1 +flask==1.1.1 +itsdangerous==2.0.1 +gunicorn==19.9.0 +requests==2.22.0 +jinja2<=3.0.3 +Werkzeug<=2.0.3 diff --git a/annotators/summarization_annotator/server.py b/annotators/summarization_annotator/server.py new file mode 100644 index 0000000000..2f880b14f2 --- /dev/null +++ b/annotators/summarization_annotator/server.py @@ -0,0 +1,75 @@ +import logging +import time +from os import getenv + +import sentry_sdk +import requests +from flask import Flask, jsonify, request + + +sentry_sdk.init(getenv("SENTRY_DSN")) +logging.basicConfig(format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO) +logger = logging.getLogger(__name__) +app = Flask(__name__) + +SUMMARIZATION_REQUEST_TIMEOUT = int(getenv("SUMMARIZATION_REQUEST_TIMEOUT")) +SUMMARIZATION_SERVICE_URL = getenv("SUMMARIZATION_SERVICE_URL") +logger.info(f"summarization-annotator considered summarizer: {SUMMARIZATION_SERVICE_URL}") + + +def get_summary(dialog): + summary = "" + if len(dialog) != 11: + logger.debug( + f"summarization-annotator is not ready to summarize dialog as the length of unsummarized dialog is " + f"{len(dialog)} != 11" + ) + return summary + + logger.debug("summarization-annotator is ready to summarize dialog as the length of unsummarized dialog is 11") + dialog = dialog[:6] + for i in range(len(dialog)): + if i % 2 == 0: + dialog[i] = "User: " + dialog[i] + else: + dialog[i] = "Bot: " + dialog[i] + dialog = ["\n".join(dialog)] + logger.debug(f"summarization-annotator will summarize this: {dialog}") + + try: + summary = requests.post( + SUMMARIZATION_SERVICE_URL, + json={"sentences": dialog}, + timeout=SUMMARIZATION_REQUEST_TIMEOUT, + ).json()[0]["batch"][0] + except Exception as exc: + logger.exception(exc) + sentry_sdk.capture_exception(exc) + + return summary + + +@app.route("/respond", methods=["POST"]) +def respond(): + start_time = time.time() + dialogs_batch = request.json.get("dialogs", []) + summaries_batch = request.json.get("previous_summaries", []) + summarization_attribute = [] + + for dialog, prev_summary in zip(dialogs_batch, summaries_batch): + logger.debug(f"summarization-annotator received dialog: {dialog}") + logger.debug(f"summarization-annotator received previous summary: {[prev_summary]}") + result = prev_summary + new_summary = get_summary(dialog) + if new_summary: + result = f"{result} {new_summary}".strip() + summarization_attribute.append({"bot_attributes": {"summarized_dialog": result}}) + logger.info(f"summarization-annotator output: {summarization_attribute}") + + total_time = time.time() - start_time + logger.info(f"summarization-annotator exec time: {total_time:.2f}s") + return jsonify(summarization_attribute) + + +if __name__ == "__main__": + app.run(debug=False, host="0.0.0.0", port=8058) diff --git a/annotators/summarization_annotator/service_configs/summarization-annotator/environment.yml b/annotators/summarization_annotator/service_configs/summarization-annotator/environment.yml new file mode 100644 index 0000000000..5c68ead334 --- /dev/null +++ b/annotators/summarization_annotator/service_configs/summarization-annotator/environment.yml @@ -0,0 +1,4 @@ +SERVICE_PORT: 8058 +SERVICE_NAME: summarization_annotator +SUMMARIZATION_REQUEST_TIMEOUT: 10 +FLASK_APP: server diff --git a/annotators/summarization_annotator/service_configs/summarization-annotator/service.yml b/annotators/summarization_annotator/service_configs/summarization-annotator/service.yml new file mode 100644 index 0000000000..53f1b3d6ed --- /dev/null +++ b/annotators/summarization_annotator/service_configs/summarization-annotator/service.yml @@ -0,0 +1,26 @@ +name: summarization-annotator +endpoints: +- respond +compose: + env_file: + - .env + build: + args: + SERVICE_PORT: 8058 + SERVICE_NAME: summarization_annotator + SUMMARIZATION_REQUEST_TIMEOUT: 10 + context: ./annotators/summarization_annotator/ + command: flask run -h 0.0.0.0 -p 8058 + environment: + - FLASK_APP=server + deploy: + resources: + limits: + memory: 256M + reservations: + memory: 256M + volumes: + - ./annotators/summarization_annotator:/src + ports: + - 8058:8058 +proxy: null diff --git a/annotators/summarization_annotator/test.py b/annotators/summarization_annotator/test.py new file mode 100644 index 0000000000..b67e534e66 --- /dev/null +++ b/annotators/summarization_annotator/test.py @@ -0,0 +1,70 @@ +import requests +from os import getenv + + +SUMMARIZATION_SERVICE_URL = getenv("SUMMARIZATION_SERVICE_URL") + + +def test_skill(): + url = "http://0.0.0.0:8058/respond" + + if SUMMARIZATION_SERVICE_URL == "http://dialog-summarizer:8059/respond_batch": + input_data = { + "dialogs": [ + [ + "Hi, my name is Mark!", + "Good morning, Mark! How can I assist you today?", + "Let's talk about cooking.", + "Sure! What is your favourite type of cuisine to cook or experiment with in the " "kitchen?", + "I like a wide range of cooking styles, such as Italian, Chinese, French and many " "more.", + "May I recommend you any Italian dish?", + "No. Better tell me what do you have in mind?", + "I've recently found a couple easy and healthy meals. How about cooking quinoa with " + "turkey and broccoli?", + "That sounds like a healthy and tasty meal! Quinoa is a great source of protein, and " + "when paired with lean turkey and broccoli, it's a well-rounded and balanced meal.", + "I am glad for you! I listened to my favorite music all day. " + "Such a great thing you know! Has anything extraordinary happened today?", + "I can tell you more about what made your day great or we can just chat?" "I'm happy to listen!", + ] + ], + "previous_summaries": [""], + } + + desired_output = [ + "Bot wants to know what is Mark's favorite type of cuisine to cook. Mark likes Italian, " + "Chinese, French and many other cooking styles." + ] + else: + input_data = { + "dialogs": [ + [ + "Привет! У тебя есть хобби?", + "Мое хобби — кулинария.", + "Здорово! А ты любишь готовить?", + "Ага, я могу отлично приготовить разные блюда.", + "Ты собираешь кулинарные рецепты?", + "Да, уже есть большая коллекция.", + "А какая национальная кухня тебе нравится?", + "Конечно, русская.", + "Русские блюда очень оригинальные, вкусные и полезные.", + "А что ты любишь готовить больше всего?", + "Я люблю готовить мясные блюда. Так что приглашаю в гости!", + ] + ], + "previous_summaries": [""], + } + + desired_output = [ + "У тебя есть хобби — кулинария, а у тебя есть большая коллекция кулинарных рецептов. Bot: Я " + "собираю кулинарные рецепты, собираю кулинарные рецепты, собираю кулинарные рецепты." + ] + + result = requests.post(url, json=input_data).json() + + assert result == [{"bot_attributes": {"summarized_dialog": desired_output[0]}}] + print("SUCCESS!") + + +if __name__ == "__main__": + test_skill() diff --git a/annotators/summarization_annotator/test.sh b/annotators/summarization_annotator/test.sh new file mode 100644 index 0000000000..61672db785 --- /dev/null +++ b/annotators/summarization_annotator/test.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +python test.py diff --git a/assistant_dists/dream/dev.yml b/assistant_dists/dream/dev.yml index 0bcad056d3..37a460fd0d 100644 --- a/assistant_dists/dream/dev.yml +++ b/assistant_dists/dream/dev.yml @@ -141,6 +141,16 @@ services: - "./common:/src/common" ports: - 8170:8170 + summarization-annotator: + volumes: + - "./annotators/summarization_annotator:/src" + ports: + - 8058:8058 + dialog-summarizer: + volumes: + - "./services/dialog_summarizer:/src" + ports: + - 8059:8059 openai-api-chatgpt-16k: volumes: - "./services/openai_api_lm:/src" diff --git a/assistant_dists/dream/docker-compose.override.yml b/assistant_dists/dream/docker-compose.override.yml index 5f12efd38b..c203a6b206 100644 --- a/assistant_dists/dream/docker-compose.override.yml +++ b/assistant_dists/dream/docker-compose.override.yml @@ -8,7 +8,7 @@ services: combined-classification:8087, fact-retrieval:8100, entity-detection:8103, sentence-ranker:8128, property-extraction:8136, prompt-selector:8135, openai-api-chatgpt:8145, dff-dream-persona-chatgpt-prompted-skill:8137, dff-dream-faq-prompted-skill:8170, - openai-api-chatgpt-16k:8167" + openai-api-chatgpt-16k:8167, summarization-annotator:8058, dialog-summarizer:8059" WAIT_HOSTS_TIMEOUT: ${WAIT_TIMEOUT:-1000} HIGH_PRIORITY_INTENTS: 1 RESTRICTION_FOR_SENSITIVE_CASE: 1 @@ -445,4 +445,41 @@ services: reservations: memory: 100M + summarization-annotator: + env_file: [ .env ] + build: + args: + SERVICE_PORT: 8058 + SERVICE_NAME: summarization_annotator + SUMMARIZATION_REQUEST_TIMEOUT: 10 + context: ./annotators/summarization_annotator/ + command: flask run -h 0.0.0.0 -p 8058 + environment: + - FLASK_APP=server + deploy: + resources: + limits: + memory: 256M + reservations: + memory: 256M + + dialog-summarizer: + env_file: [ .env ] + build: + args: + SERVICE_PORT: 8059 + SERVICE_NAME: dialog_summarizer + PRETRAINED_MODEL_NAME: "knkarthick/MEETING_SUMMARY" + context: ./services/dialog_summarizer/ + command: flask run -h 0.0.0.0 -p 8059 + environment: + - CUDA_VISIBLE_DEVICES=0 + - FLASK_APP=server + deploy: + resources: + limits: + memory: 4G + reservations: + memory: 4G + version: '3.7' diff --git a/assistant_dists/dream/pipeline_conf.json b/assistant_dists/dream/pipeline_conf.json index a218816f12..35825f1f75 100644 --- a/assistant_dists/dream/pipeline_conf.json +++ b/assistant_dists/dream/pipeline_conf.json @@ -263,6 +263,24 @@ "component": "components/PbLNvh4hrvs47rPaf2bfYQ.yml", "service": "annotators/combined_classification/service_configs/combined-classification" } + }, + "summarization_annotator": { + "connector": { + "protocol": "http", + "timeout": 10.0, + "url": "http://summarization-annotator:8058/respond" + }, + "dialog_formatter": "state_formatters.dp_formatters:summarization_annotator_formatter", + "response_formatter": "state_formatters.dp_formatters:simple_formatter_service", + "previous_services": [ + "annotators.spelling_preprocessing" + ], + "state_manager_method": "update_attributes", + "is_enabled": true, + "source": { + "component": "components/riRfdGz86P51B9bL7fO6JR.yml", + "service": "annotators/summarization_annotator/service_configs/summarization-annotator" + } } }, "response_annotators": { diff --git a/assistant_dists/dream_russian/dev.yml b/assistant_dists/dream_russian/dev.yml index fe7d76f117..da74533765 100644 --- a/assistant_dists/dream_russian/dev.yml +++ b/assistant_dists/dream_russian/dev.yml @@ -137,4 +137,14 @@ services: - "~/.deeppavlov:/root/.deeppavlov" ports: - 8078:8078 + summarization-annotator: + volumes: + - "./annotators/summarization_annotator:/src" + ports: + - 8058:8058 + rut5-summarizer: + volumes: + - "./services/ruT5_summarizer:/src" + ports: + - 8060:8060 version: "3.7" diff --git a/assistant_dists/dream_russian/docker-compose.override.yml b/assistant_dists/dream_russian/docker-compose.override.yml index c930fef992..eda745fd07 100644 --- a/assistant_dists/dream_russian/docker-compose.override.yml +++ b/assistant_dists/dream_russian/docker-compose.override.yml @@ -8,7 +8,7 @@ services: spelling-preprocessing-ru:8074, entity-linking-ru:8075, wiki-parser-ru:8077, dff-generative-ru-skill:8092, dff-friendship-ru-skill:8086, entity-detection-ru:8103, dialogpt-ru:8125, dff-template-skill:8120, spacy-annotator-ru:8129, dialogrpt-ru:8122, toxic-classification-ru:8118, - fact-retrieval-ru:8110, text-qa-ru:8078" + fact-retrieval-ru:8110, text-qa-ru:8078, summarization-annotator:8058, rut5-summarizer:8060" WAIT_HOSTS_TIMEOUT: ${WAIT_TIMEOUT:-1200} HIGH_PRIORITY_INTENTS: 1 RESTRICTION_FOR_SENSITIVE_CASE: 1 @@ -445,4 +445,41 @@ services: reservations: memory: 3G + summarization-annotator: + env_file: [ .env ] + build: + args: + SERVICE_PORT: 8058 + SERVICE_NAME: summarization_annotator + SUMMARIZATION_REQUEST_TIMEOUT: 10 + context: ./annotators/summarization_annotator/ + command: flask run -h 0.0.0.0 -p 8058 + environment: + - FLASK_APP=server + deploy: + resources: + limits: + memory: 256M + reservations: + memory: 256M + + rut5-summarizer: + env_file: [ .env ] + build: + args: + SERVICE_PORT: 8060 + SERVICE_NAME: ruT5_summarizer + PRETRAINED_MODEL_NAME: "IlyaGusev/rut5_base_sum_gazeta" + context: ./services/ruT5_summarizer/ + command: flask run -h 0.0.0.0 -p 8060 + environment: + - CUDA_VISIBLE_DEVICES=0 + - FLASK_APP=server + deploy: + resources: + limits: + memory: 4G + reservations: + memory: 4G + version: '3.7' diff --git a/assistant_dists/dream_russian/pipeline_conf.json b/assistant_dists/dream_russian/pipeline_conf.json index 3d0aa0fbe1..b9e3dc3bc0 100644 --- a/assistant_dists/dream_russian/pipeline_conf.json +++ b/assistant_dists/dream_russian/pipeline_conf.json @@ -266,6 +266,24 @@ "component": "components/feDgqHKLibnMNM3HSbnmA.yml", "service": "annotators/wiki_parser/service_configs/wiki-parser-ru" } + }, + "summarization_annotator": { + "connector": { + "protocol": "http", + "timeout": 10.0, + "url": "http://summarization-annotator:8058/respond" + }, + "dialog_formatter": "state_formatters.dp_formatters:summarization_annotator_formatter", + "response_formatter": "state_formatters.dp_formatters:simple_formatter_service", + "previous_services": [ + "annotators.spelling_preprocessing" + ], + "state_manager_method": "update_attributes", + "is_enabled": true, + "source": { + "component": "components/riRfdGz86P51B9bL7fO6JR.yml", + "service": "annotators/summarization_annotator/service_configs/summarization-annotator" + } } }, "response_annotators": { diff --git a/components.tsv b/components.tsv index cd869e0fa3..57dc68ea06 100644 --- a/components.tsv +++ b/components.tsv @@ -59,9 +59,9 @@ 8055 8056 8057 dff-short-story-skill -8058 -8059 -8060 +8058 summarization-annotator +8059 dialog-summarizer +8060 rut5-summarizer 8061 dff-coronavirus-skill 8062 small-talk-skill 8063 diff --git a/components/riRfdGz86P51B9bL7fO6JR.yml b/components/riRfdGz86P51B9bL7fO6JR.yml new file mode 100644 index 0000000000..4bc18d1b50 --- /dev/null +++ b/components/riRfdGz86P51B9bL7fO6JR.yml @@ -0,0 +1,24 @@ +name: summarization-annotator +display_name: Summarization Annotator +container_name: summarization-annotator +component_type: null +model_type: NN-based +is_customizable: false +author: DeepPavlov +description: Annotator that accesses summarization services +ram_usage: 256M +gpu_usage: null +connector: + protocol: http + timeout: 10.0 + url: http://summarization-annotator:8058/respond +dialog_formatter: state_formatters.dp_formatters:summarization_annotator_formatter +response_formatter: state_formatters.dp_formatters:simple_formatter_service +previous_services: +- annotators.spelling_preprocessing +required_previous_services: null +state_manager_method: add_annotation +tags: null +endpoint: respond +service: annotators/summarization_annotator/service_configs/aummarization-annotator +date_created: '2023-07-04T11:39:32' \ No newline at end of file diff --git a/services/dialog_summarizer/Dockerfile b/services/dialog_summarizer/Dockerfile new file mode 100644 index 0000000000..71eb8a97a7 --- /dev/null +++ b/services/dialog_summarizer/Dockerfile @@ -0,0 +1,9 @@ +FROM python:3.7.4 + +ARG PRETRAINED_MODEL_NAME +ENV PRETRAINED_MODEL_NAME ${PRETRAINED_MODEL_NAME} + +COPY ${WORK_DIR}/requirements.txt /src/requirements.txt +RUN pip install -r /src/requirements.txt +COPY ${WORK_DIR} /src +WORKDIR /src diff --git a/services/dialog_summarizer/requirements.txt b/services/dialog_summarizer/requirements.txt new file mode 100644 index 0000000000..79d28c80fe --- /dev/null +++ b/services/dialog_summarizer/requirements.txt @@ -0,0 +1,9 @@ +torch==1.13.1 +transformers==4.27.0 +sentry-sdk[flask]==0.14.1 +flask==1.1.1 +itsdangerous==2.0.1 +gunicorn==19.9.0 +requests==2.22.0 +jinja2<=3.0.3 +Werkzeug<=2.0.3 diff --git a/services/dialog_summarizer/server.py b/services/dialog_summarizer/server.py new file mode 100644 index 0000000000..8fd9fff9a1 --- /dev/null +++ b/services/dialog_summarizer/server.py @@ -0,0 +1,40 @@ +import logging +import time +import os + +from transformers import BartTokenizer, BartForConditionalGeneration +import torch +import sentry_sdk +from flask import Flask, jsonify, request + + +sentry_sdk.init(os.getenv("SENTRY_DSN")) + +logging.basicConfig(format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO) +logger = logging.getLogger(__name__) +device = "cuda" if torch.cuda.is_available() else "cpu" +PRETRAINED_MODEL_NAME = os.environ.get("PRETRAINED_MODEL_NAME") + +app = Flask(__name__) + +model = BartForConditionalGeneration.from_pretrained(PRETRAINED_MODEL_NAME) +model.to(device) +tokenizer = BartTokenizer.from_pretrained(PRETRAINED_MODEL_NAME) +logger.info("Model is loaded.") + + +@app.route("/respond_batch", methods=["POST"]) +def respond_batch(): + start_time = time.time() + sentences = request.json.get("sentences", []) + logger.debug(f"Sentences: {sentences}") + tokenized_text = tokenizer(sentences, return_tensors="pt", truncation=True, padding="max_length").to(device) + summary = model.generate(tokenized_text["input_ids"]) + summary = tokenizer.batch_decode(summary, skip_special_tokens=True) + total_time = time.time() - start_time + logger.info(f"dialog-summarizer exec time: {round(total_time, 2)} sec") + return jsonify([{"batch": summary}]) + + +if __name__ == "__main__": + app.run(debug=False, host="0.0.0.0", port=8059) diff --git a/services/dialog_summarizer/service_configs/dialog-summarizer/environment.yml b/services/dialog_summarizer/service_configs/dialog-summarizer/environment.yml new file mode 100644 index 0000000000..b2eae2fb2f --- /dev/null +++ b/services/dialog_summarizer/service_configs/dialog-summarizer/environment.yml @@ -0,0 +1,5 @@ +SERVICE_PORT: 8059 +SERVICE_NAME: dialog_summarizer +PRETRAINED_MODEL_NAME: "knkarthick/MEETING_SUMMARY" +CUDA_VISIBLE_DEVICES: '0' +FLASK_APP: server diff --git a/services/dialog_summarizer/service_configs/dialog-summarizer/service.yml b/services/dialog_summarizer/service_configs/dialog-summarizer/service.yml new file mode 100644 index 0000000000..7bbad19bba --- /dev/null +++ b/services/dialog_summarizer/service_configs/dialog-summarizer/service.yml @@ -0,0 +1,27 @@ +name: dialog-summarizer +endpoints: +- respond_batch +compose: + env_file: + - .env + build: + args: + SERVICE_PORT: 8059 + SERVICE_NAME: dialog_summarizer + PRETRAINED_MODEL_NAME: "knkarthick/MEETING_SUMMARY" + context: ./services/dialog_summarizer/ + command: flask run -h 0.0.0.0 -p 8059 + environment: + - CUDA_VISIBLE_DEVICES=0 + - FLASK_APP=server + deploy: + resources: + limits: + memory: 4G + reservations: + memory: 4G + volumes: + - ./services/dialog_summarizer:/src + ports: + - 8059:8059 +proxy: null diff --git a/services/dialog_summarizer/test.py b/services/dialog_summarizer/test.py new file mode 100644 index 0000000000..908f14acd3 --- /dev/null +++ b/services/dialog_summarizer/test.py @@ -0,0 +1,37 @@ +import requests + + +def test_skill(): + url = "http://0.0.0.0:8059/respond_batch" + + input_data = { + "sentences": [ + "Generative pre-trained transformers (GPT) are a family of large language models (LLMs)" + ", which was introduced in 2018 by the American artificial intelligence organization " + "OpenAI. GPT models are artificial neural networks that are based on the transformer " + "architecture, pre-trained on large datasets of unlabelled text, and able to generate " + "novel human-like text. At this point, most LLMs have these characteristics.", + "ChatGPT is an artificial-intelligence (AI) chatbot developed by OpenAI and launched " + "in November 2022. It is built on top of OpenAI's GPT-3.5 and GPT-4 families of large " + "language models (LLMs) and has been fine-tuned (an approach to transfer learning) " + "using both supervised and reinforcement learning techniques. The original release of " + "ChatGPT was based on GPT-3.5. A version based on GPT-4, the newest OpenAI model, was " + "released on March 14, 2023, and is available for paid subscribers on a limited basis.", + ] + } + desired_output = [ + "Generative pre-trained transformers are a family of large language models (LLMs) introduced by " + "the American artificial intelligence organization OpenAI.", + "ChatGPT is an artificial-intelligence chatbot developed by OpenAI. It was launched in November " + "2022. A version based on GPT-4 model was released on March 14, 2023 and is available for paid " + "subscribers.", + ] + + result = requests.post(url, json=input_data).json()[0]["batch"] + + assert result == desired_output + print("SUCCESS!") + + +if __name__ == "__main__": + test_skill() diff --git a/services/dialog_summarizer/test.sh b/services/dialog_summarizer/test.sh new file mode 100644 index 0000000000..61672db785 --- /dev/null +++ b/services/dialog_summarizer/test.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +python test.py diff --git a/services/ruT5_summarizer/Dockerfile b/services/ruT5_summarizer/Dockerfile new file mode 100644 index 0000000000..71eb8a97a7 --- /dev/null +++ b/services/ruT5_summarizer/Dockerfile @@ -0,0 +1,9 @@ +FROM python:3.7.4 + +ARG PRETRAINED_MODEL_NAME +ENV PRETRAINED_MODEL_NAME ${PRETRAINED_MODEL_NAME} + +COPY ${WORK_DIR}/requirements.txt /src/requirements.txt +RUN pip install -r /src/requirements.txt +COPY ${WORK_DIR} /src +WORKDIR /src diff --git a/services/ruT5_summarizer/requirements.txt b/services/ruT5_summarizer/requirements.txt new file mode 100644 index 0000000000..79d28c80fe --- /dev/null +++ b/services/ruT5_summarizer/requirements.txt @@ -0,0 +1,9 @@ +torch==1.13.1 +transformers==4.27.0 +sentry-sdk[flask]==0.14.1 +flask==1.1.1 +itsdangerous==2.0.1 +gunicorn==19.9.0 +requests==2.22.0 +jinja2<=3.0.3 +Werkzeug<=2.0.3 diff --git a/services/ruT5_summarizer/server.py b/services/ruT5_summarizer/server.py new file mode 100644 index 0000000000..88a3f1117c --- /dev/null +++ b/services/ruT5_summarizer/server.py @@ -0,0 +1,46 @@ +import logging +import time +import os + +from transformers import AutoTokenizer, T5ForConditionalGeneration +import torch +import sentry_sdk +from flask import Flask, jsonify, request + + +sentry_sdk.init(os.getenv("SENTRY_DSN")) + +logging.basicConfig(format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO) +logger = logging.getLogger(__name__) +device = "cuda" if torch.cuda.is_available() else "cpu" +PRETRAINED_MODEL_NAME = os.environ.get("PRETRAINED_MODEL_NAME") + +app = Flask(__name__) + +model = T5ForConditionalGeneration.from_pretrained(PRETRAINED_MODEL_NAME) +model.to(device) +tokenizer = AutoTokenizer.from_pretrained(PRETRAINED_MODEL_NAME) +logger.info("Model is loaded.") + + +@app.route("/respond_batch", methods=["POST"]) +def respond_batch(): + start_time = time.time() + sentences = request.json.get("sentences", []) + logger.debug(f"Sentences: {sentences}") + tokenized_text = tokenizer( + sentences, + add_special_tokens=True, + return_tensors="pt", + truncation=True, + padding="max_length", + ).to(device) + summary = model.generate(tokenized_text["input_ids"]) + summary = tokenizer.batch_decode(summary, skip_special_tokens=True) + total_time = time.time() - start_time + logger.info(f"rut5-summarizer exec time: {round(total_time, 2)} sec") + return jsonify([{"batch": summary}]) + + +if __name__ == "__main__": + app.run(debug=False, host="0.0.0.0", port=8060) diff --git a/services/ruT5_summarizer/service_configs/rut5-summarizer/environment.yml b/services/ruT5_summarizer/service_configs/rut5-summarizer/environment.yml new file mode 100644 index 0000000000..785f3e52e4 --- /dev/null +++ b/services/ruT5_summarizer/service_configs/rut5-summarizer/environment.yml @@ -0,0 +1,5 @@ +SERVICE_PORT: 8060 +SERVICE_NAME: rut5_summarizer +PRETRAINED_MODEL_NAME: "IlyaGusev/rut5_base_sum_gazeta" +CUDA_VISIBLE_DEVICES: '0' +FLASK_APP: server diff --git a/services/ruT5_summarizer/service_configs/rut5-summarizer/service.yml b/services/ruT5_summarizer/service_configs/rut5-summarizer/service.yml new file mode 100644 index 0000000000..8755b34ea9 --- /dev/null +++ b/services/ruT5_summarizer/service_configs/rut5-summarizer/service.yml @@ -0,0 +1,27 @@ +name: rut5-summarizer +endpoints: +- respond_batch +compose: + env_file: + - .env + build: + args: + SERVICE_PORT: 8060 + SERVICE_NAME: ruT5_summarizer + PRETRAINED_MODEL_NAME: "IlyaGusev/rut5_base_sum_gazeta" + context: ./services/ruT5_summarizer/ + command: flask run -h 0.0.0.0 -p 8060 + environment: + - CUDA_VISIBLE_DEVICES=0 + - FLASK_APP=server + deploy: + resources: + limits: + memory: 4G + reservations: + memory: 4G + volumes: + - ./services/ruT5_summarizer:/src + ports: + - 8060:8060 +proxy: null diff --git a/services/ruT5_summarizer/test.py b/services/ruT5_summarizer/test.py new file mode 100644 index 0000000000..865442f20f --- /dev/null +++ b/services/ruT5_summarizer/test.py @@ -0,0 +1,34 @@ +import requests + + +def test_skill(): + url = "http://0.0.0.0:8060/respond_batch" + + input_data = { + "sentences": [ + "ChatGPT (Generative Pre-trained Transformer или генеративный предварительно обученный " + "трансформер) — чат-бот с искусственным интеллектом, разработанный компанией OpenAI и " + "способный работать в диалоговом режиме, поддерживающий запросы на естественных языках." + " ChatGPT — большая языковая модель, для тренировки которой использовались методы " + "обучения с учителем и обучения с подкреплением. Данный чат-бот основывается на другой " + "языковой модели от OpenAI — GPT-3.5 — улучшенной версии модели GPT-3. 14 марта 2023 " + "года была выпущена языковая модель GPT-4, доступная тестировщикам и платным " + "подписчикам ChatGPT Plus. В новой версии у ИИ появилась возможность обработки не " + "только текста, но и картинок." + ] + } + desired_output = [ + "Компания OpenAI разработала чат-бот с искусственным интеллектом ChatGPT — чат-бот с " + "искусственным интеллектом, способный работать в диалоговом режиме, поддерживающий запросы на " + "естественных языках. Это чат-бот с искусственным интеллектом, разработанный компанией OpenAI и " + "способный работать в диалоговом режиме, поддерживающий запросы на естественных языках." + ] + + result = requests.post(url, json=input_data).json()[0]["batch"] + + assert result == desired_output + print("SUCCESS!") + + +if __name__ == "__main__": + test_skill() diff --git a/services/ruT5_summarizer/test.sh b/services/ruT5_summarizer/test.sh new file mode 100644 index 0000000000..61672db785 --- /dev/null +++ b/services/ruT5_summarizer/test.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +python test.py diff --git a/state_formatters/dp_formatters.py b/state_formatters/dp_formatters.py index d693e3956e..1707c1690a 100755 --- a/state_formatters/dp_formatters.py +++ b/state_formatters/dp_formatters.py @@ -30,7 +30,12 @@ def eliza_formatter_dialog(dialog: Dict) -> List[Dict]: last_utterance = dialog["human_utterances"][-1]["annotations"].get( "spelling_preprocessing", dialog["human_utterances"][-1]["text"] ) - return [{"last_utterance_batch": [last_utterance], "human_utterance_history_batch": [history]}] + return [ + { + "last_utterance_batch": [last_utterance], + "human_utterance_history_batch": [history], + } + ] def cobot_qa_formatter_service(payload: List): @@ -426,6 +431,17 @@ def last_utt_and_history_dialog(dialog: Dict) -> List: ] +def summarization_annotator_formatter(dialog: Dict): + # Used by: summarization annotator + sents = [utt["text"] for utt in dialog["utterances"]] + pointer = (len(sents) + 1) % 6 if (len(sents) + 1) % 6 != 0 else 6 + sents = sents[-(pointer + 5) :] + bot_attributes = dialog["bot_utterances"][-1]["user"]["attributes"] if len(dialog["bot_utterances"]) else {} + previous_summary = bot_attributes["summarized_dialog"] if "summarized_dialog" in bot_attributes.keys() else [] + previous_summary = previous_summary if previous_summary else "" + return [{"dialogs": [sents], "previous_summaries": [previous_summary]}] + + def convers_evaluator_annotator_formatter(dialog: Dict) -> List[Dict]: dialog = utils.get_last_n_turns(dialog) dialog = utils.remove_clarification_turns_from_dialog(dialog) @@ -721,7 +737,13 @@ def prepare_el_input(dialog: Dict): def el_formatter_dialog(dialog: Dict): # Used by: entity_linking annotator entity_substr_list, entity_tags_list, context = prepare_el_input(dialog) - return [{"entity_substr": [entity_substr_list], "entity_tags": [entity_tags_list], "context": [context]}] + return [ + { + "entity_substr": [entity_substr_list], + "entity_tags": [entity_tags_list], + "context": [context], + } + ] def custom_el_formatter_dialog(dialog: Dict): @@ -1191,7 +1213,12 @@ def prompts_goals_collector_formatter(dialog: Dict) -> List[Dict]: for prompts_goals_dict in [hyp.get("prompts_goals", None) for hyp in hypotheses]: if prompts_goals_dict: prompts_goals.update(deepcopy(prompts_goals_dict)) - return [{"prompts_goals": [prompts_goals], "human_attributes": [dialog["human"]["attributes"]]}] + return [ + { + "prompts_goals": [prompts_goals], + "human_attributes": [dialog["human"]["attributes"]], + } + ] def image_captioning_formatter(dialog: Dict) -> List[Dict]: From b63bba1a680d37ce7a666f0730895846bc713abe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anast=C3=A1sis?= <43078815+nstsj@users.noreply.github.com> Date: Sun, 6 Aug 2023 11:49:26 +0400 Subject: [PATCH 2/9] Models table upd (#539) * Fix requirements.txt (#84) * fix itsdangerous requirements * pin itsdangerous requirements for all flask==1.1.1 servers * updated MODELS.md table: added info about models' licensing and commercial use + merged link+name cols to improve overall readability and decrease redundancy * Update MODELS.md fixed "is" for better consistency * fix: format table and add new models back * fix: sizes of models on gpu * updated table --------- Co-authored-by: Andrii.Hura <54397922+AndriiHura@users.noreply.github.com> Co-authored-by: mtalimanchuk Co-authored-by: Dilyara Baymurzina --- MODELS.md | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/MODELS.md b/MODELS.md index 129c1c12df..8588da5e33 100644 --- a/MODELS.md +++ b/MODELS.md @@ -1,20 +1,16 @@ -## Models used in Dream - -Here you may find a list of models that currently available for use in Dream. - -| model name | container name | model link | open-source? | size (billion parameters) | GPU usage | max tokens (prompt + response) | description | -|-----------------------------|---------------------------------|-------------------------------------------------------------------------|--------------------------------------|---------------------------|---------------------------|--------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| BLOOMZ 7B | transformers-lm-bloomz7b | [link](https://huggingface.co/bigscience/bloomz-7b1) | yes | 7.1B | 33GB | 2,048 tokens | An open-source multilingual instruction-based large language model (46 languages). NB: free of charge. This model is up and running on our servers and can be used for free. | -| GPT-J 6B | transformers-lm-gptj | [link](https://huggingface.co/EleutherAI/gpt-j-6b) | yes | 6B | 25GB | 2,048 tokens | An open-source English-only large language model which is NOT fine-tuned for instruction following and NOT capable of code generation. NB: free of charge. This model is up and running on our servers and can be used for free. | -| GPT-3.5 | openai-api-davinci3 | [link](https://platform.openai.com/docs/models/gpt-3-5) | no (paid access via API) | supposedly, 175B | - (cannot be run locally) | 4,097 tokens | A multilingual instruction-based large language model which is capable of code generation. Unlike ChatGPT, not optimised for chat. NB: paid. You must provide your OpenAI API key to use the model. Your OpenAI account will be charged according to your usage. | -| ChatGPT | openai-api-chatgpt | [link](https://platform.openai.com/docs/models/gpt-3-5) | no (paid access via API) | supposedly, 175B | - (cannot be run locally) | 4,096 tokens | Based on gpt-3.5-turbo -- the most capable of the entire GPT-3/GPT-3.5 models family. Optimized for chat. Able to understand and generate code. NB: paid. You must provide your OpenAI API key to use the model. Your OpenAI account will be charged according to your usage. | -| Open-Assistant Pythia 12B | transformers-lm-oasst12b | [link](https://huggingface.co/OpenAssistant/pythia-12b-sft-v8-7k-steps) | yes | 12B | 29GB (half-precision) | 5,120 tokens | An open-source English-only instruction-based large language model which is NOT good at answering math and coding questions. NB: free of charge. This model is up and running on our servers and can be used for free. | -| Vicuna 13B | transformers-lm-vicuna13b | [link](https://huggingface.co/lmsys/vicuna-13b-v1.3) | yes, but only for non-commercial use | 13B | 29GB (half-precision) | 2,048 tokens | An instruction-based large language model fine-tuned on LLaMa that achieves [more than 90%* quality of OpenAI ChatGPT and Google Bard](https://lmsys.org/blog/2023-03-30-vicuna/). The model performs best in English and is NOT good at answering math, reasoning, and coding questions. NB-1: Free of charge. This model is up and running on our servers and can be used for free. NB-2: cannot be used for commercial purposes (license restriction). | -| GPT-4 | openai-api-gpt4 | [link](https://platform.openai.com/docs/models/gpt-4) | no (paid access via API) | supposedly, 175B | - (cannot be run locally) | 8,192 tokens | A multilingual instruction-based large language model which is capable of code generation and other complex tasks. More capable than any GPT-3.5 model, able to do more complex tasks, and optimized for chat. NB: paid. You must provide your OpenAI API key to use the model. Your OpenAI account will be charged according to your usage. | -| GPT-4 32K | openai-api-gpt4-32k | [link](https://platform.openai.com/docs/models/gpt-4) | no (paid access via API) | supposedly, 175B | - (cannot be run locally) | 32,768 tokens | A multilingual instruction-based large language model which is capable of code generation and other complex tasks. Same capabilities as the base gpt-4 mode but with 4x the context length. NB: paid. You must provide your OpenAI API key to use the model. Your OpenAI account will be charged according to your usage. | -| GPT-JT 6B | transformers-lm-gptjt | [link](https://huggingface.co/togethercomputer/GPT-JT-6B-v1) | yes | 6B | 14GB (half-precision) | 2,048 tokens | An open-source English-only large language model which was fine-tuned for instruction following but is NOT capable of code generation. NB: free of charge. This model is up and running on our servers and can be used for free. | -| ChatGPT 16k | openai-api-chatgpt-16k | [link](https://platform.openai.com/docs/models/gpt-3-5) | no (paid access via API) | supposedly, 175B | - (cannot be run locally) | 16,384 tokens | Same capabilities as the standard gpt-3.5-turbo model but with 4 times the context. NB: paid. You must provide your OpenAI API key to use the model. Your OpenAI account will be charged according to your usage. | -| Anthropic Claude-v1 | anthropic-api-claude-v1 | [link](https://docs.anthropic.com/claude/reference/complete_post) | no (paid access via API) | | - (cannot be run locally) | 9,000 tokens | The largest model, ideal for a wide range of more complex tasks. NB: paid. You must provide your Anthropic API key to use the model. Your Anthropic API account will be charged according to your usage. | -| Anthropic Claude Instant v1 | anthropic-api-claude-instant-v1 | [link](https://docs.anthropic.com/claude/reference/complete_post) | no (paid access via API) | | - (cannot be run locally) | 9,000 tokens | A smaller model with far lower latency, sampling at roughly 40 words/sec! Its output quality is somewhat lower than the latest claude-1 model, particularly for complex tasks. However, it is much less expensive and blazing fast. NB: paid. You must provide your Anthropic API key to use the model. Your Anthropic API account will be charged according to your usage. | -| Russian XGLM 4.5B | transformers-lm-ruxglm | unavailable (private weights) | no | 4.5B | 15GB | 2,048 tokens | A private large language model for the Russian language which was fine-tuned for instruction following by Dmitry Kosenko in Summer 2023. This model is up and running on our servers and can be used for free. | -| ruGPT-3.5-13B | transformers-lm-rugpt35 | [link](https://huggingface.co/ai-forever/ruGPT-3.5-13B) | yes | 13B | 35GB (half-precision) | 2,048 tokens | A large language model for the Russian language which was used for trainig GigaChat. This model is up and running on our servers and can be used for free. | +| model name and link | container name | open-source? | size | GPU usage | max tokens (prompt + response) | licence | description | +|----------------------------------------------------------------------------------------------|-----------------------------------|--------------------------------------|------------------|---------------------------|--------------------------------|------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [BLOOMZ 7B](https://huggingface.co/bigscience/bloomz-7b1) | transformers-lm-bloomz7b | yes | 7.1B | 33GB | 2,048 tokens | bigscience-bloom-rail-1.0, commercial use allowed | An open-source multilingual instruction-based large language model (46 languages). NB: free of charge. This model is up and running on our servers and can be used for free. | +| [GPT-J 6B](https://huggingface.co/EleutherAI/gpt-j-6b) | transformers-lm-gptj | yes | 6B | 25GB | 2,048 tokens | Apache 2.0 , commercial use allowed | An open-source English-only large language model which is NOT fine-tuned for instruction following and NOT capable of code generation. NB: free of charge. This model is up and running on our servers and can be used for free. | +| [GPT-3.5](https://platform.openai.com/docs/models/gpt-3-5) | openai-api-davinci3 | no | supposedly, 175B | - (cannot be run locally) | 4,097 tokens | available under subscription plan, commercial use allowed | A multilingual instruction-based large language model which is capable of code generation. Unlike ChatGPT, not optimized for chat. NB: paid. You must provide your OpenAI API key to use the model. Your OpenAI account will be charged according to your usage. | +| [ChatGPT](https://platform.openai.com/docs/models/gpt-3-5) | openai-api-chatgpt | no | supposedly, 175B | - (cannot be run locally) | 4,096 tokens | available under subscription plan, commercial use allowed | Based on gpt-3.5-turbo -- the most capable of the entire GPT-3/GPT-3.5 models family. Optimized for chat. Able to understand and generate code. NB: paid. You must provide your OpenAI API key to use the model. Your OpenAI account will be charged according to your usage. | +| [Open-Assistant Pythia 12B](https://huggingface.co/OpenAssistant/pythia-12b-sft-v8-7k-steps) | transformers-lm-oasst12b | yes | 12B | 29GB (half-precision) | 5,120 tokens | Apache 2.0 , commercial use allowed | An open-source English-only instruction-based large language model which is NOT good at answering math and coding questions. NB: free of charge. This model is up and running on our servers and can be used for free. | +| [Vicuna 13B](https://huggingface.co/lmsys/vicuna-13b-v1.3) | transformers-lm-vicuna13b | yes, but only for non-commercial use | 13B | 29GB (half-precision) | 2,048 tokens | Non-commercial license | An instruction-based large language model fine-tuned on LLaMa that achieves [more than 90%* quality of OpenAI ChatGPT and Google Bard](https://lmsys.org/blog/2023-03-30-vicuna/). The model performs best in English and is NOT good at answering math, reasoning, and coding questions. NB-1: Free of charge. This model is up and running on our servers and can be used for free. NB-2: cannot be used for commercial purposes due to license restriction. | +| [GPT-4](https://platform.openai.com/docs/models/gpt-4) | openai-api-gpt4 | no | supposedly, 175B | - (cannot be run locally) | 8,192 tokens | available under subscription plan, commercial use allowed | A multilingual instruction-based large language model which is capable of code generation and other complex tasks. More capable than any GPT-3.5 model, able to do more complex tasks, and optimized for chat. NB: paid. You must provide your OpenAI API key to use the model. Your OpenAI account will be charged according to your usage. | +| [GPT-4 32K](https://platform.openai.com/docs/models/gpt-4) | openai-api-gpt4-32k | no | supposedly, 175B | - (cannot be run locally) | 32,768 tokens | available under subscription plan, commercial use allowed | A multilingual instruction-based large language model which is capable of code generation and other complex tasks. Same capabilities as the base gpt-4 mode but with 4x the context length. NB: paid. You must provide your OpenAI API key to use the model. Your OpenAI account will be charged according to your usage. | +| [GPT-JT 6B](https://huggingface.co/togethercomputer/GPT-JT-6B-v1) | transformers-lm-gptjt | yes | 6B | 14GB (half-precision) | 2,048 tokens | Apache 2.0 , commercial use is allowed | An open-source English-only large language model which was fine-tuned for instruction following but is NOT capable of code generation. NB: free of charge. This model is up and running on our servers and can be used for free. | +| [ChatGPT 16k](https://platform.openai.com/docs/models/gpt-3-5) | openai-api-chatgpt-16k | no | supposedly, 175B | - (cannot be run locally) | 16,384 tokens | available under subscription plan, commercial use allowed | Same capabilities as the standard gpt-3.5-turbo model but with 4 times the context. NB: paid. You must provide your OpenAI API key to use the model. Your OpenAI account will be charged according to your usage. | +| [Anthropic Claude-v1](https://docs.anthropic.com/claude/reference/complete_post) | anthropic-api-claude-v1 | no | supposedly, 52B | - (cannot be run locally) | 9,000 tokens | available under subscription plan, commercial use allowed | The largest model, ideal for a wide range of more complex tasks. NB: paid. You must provide your Anthropic API key to use the model. Your Anthropic API account will be charged according to your usage. | +| [Anthropic Claude Instant v1](https://docs.anthropic.com/claude/reference/complete_post) | anthropic-api-claude-instant-v1 | no (paid access via API) | supposedly, 52B | - (cannot be run locally) | 9,000 tokens | available under subscription plan, commercial use allowed | A smaller model with far lower latency, sampling at roughly 40 words/sec! Its output quality is somewhat lower than the latest claude-1 model, particularly for complex tasks. However, it is much less expensive and blazing fast. NB: paid. You must provide your Anthropic API key to use the model. Your Anthropic API account will be charged according to your usage. | +| Russian XGLM 4.5B (private weights) | transformers-lm-ruxglm | no | 4.5B | 15GB | 2,048 tokens | Not available yet | A private large language model for the Russian language which was fine-tuned for instruction following by Dmitry Kosenko in Summer 2023. This model is up and running on our servers and can be used for free. | +| [ruGPT-3.5-13B](https://huggingface.co/ai-forever/ruGPT-3.5-13B) | transformers-lm-rugpt35 | yes | 13B | 35GB (half-precision) | 2,048 tokens | MIT | A large language model for the Russian language which was used for trainig GigaChat. This model is up and running on our servers and can be used for free. | From 244b85f97619e48ca419b95b2b823c7768d5312d Mon Sep 17 00:00:00 2001 From: "Dilyara Zharikova (Baymurzina)" Date: Mon, 7 Aug 2023 11:28:48 +0300 Subject: [PATCH 3/9] fix: anthropic model params (#547) --- .../anthropic_generative_config.json | 3 +++ services/anthropic_api_lm/server.py | 16 ++++++++-------- 2 files changed, 11 insertions(+), 8 deletions(-) create mode 100644 common/generative_configs/anthropic_generative_config.json diff --git a/common/generative_configs/anthropic_generative_config.json b/common/generative_configs/anthropic_generative_config.json new file mode 100644 index 0000000000..e0858bd0ea --- /dev/null +++ b/common/generative_configs/anthropic_generative_config.json @@ -0,0 +1,3 @@ +{ + "max_tokens_to_sample": 256 +} \ No newline at end of file diff --git a/services/anthropic_api_lm/server.py b/services/anthropic_api_lm/server.py index 8b430d0fbc..c701f6fddd 100644 --- a/services/anthropic_api_lm/server.py +++ b/services/anthropic_api_lm/server.py @@ -19,13 +19,13 @@ PRETRAINED_MODEL_NAME_OR_PATH = os.environ.get("PRETRAINED_MODEL_NAME_OR_PATH") logger.info(f"PRETRAINED_MODEL_NAME_OR_PATH = {PRETRAINED_MODEL_NAME_OR_PATH}") -NAMING = ["Assistant", "Human"] +NAMING = [anthropic.AI_PROMPT, anthropic.HUMAN_PROMPT] app = Flask(__name__) logging.getLogger("werkzeug").setLevel("WARNING") DEFAULT_CONFIGS = { - "claude-1": json.load(open("common/generative_configs/empty_generative_config.json", "r")), - "claude-instant-1": json.load(open("common/generative_configs/empty_generative_config.json", "r")), + "claude-1": json.load(open("common/generative_configs/anthropic_generative_config.json", "r")), + "claude-instant-1": json.load(open("common/generative_configs/anthropic_generative_config.json", "r")), } @@ -33,15 +33,15 @@ def generate_responses(context, anthropic_api_key, prompt, generation_params, co assert anthropic_api_key, logger.error("Error: Anthropic API key is not specified in env") outputs = [] - dialog_context = "" + dialog_context = f"{anthropic.HUMAN_PROMPT} " if prompt: - dialog_context += prompt + "\n" + dialog_context += prompt s = len(context) % 2 - context = [f"{NAMING[(s + uttr_id) % 2]}: {uttr}" for uttr_id, uttr in enumerate(context)] + context = [f"{NAMING[(s + uttr_id) % 2]} {uttr}" for uttr_id, uttr in enumerate(context)] if continue_last_uttr: - dialog_context += "\n".join(context) + dialog_context += "".join(context) else: - dialog_context += "\n".join(context) + f"\n{NAMING[0]}:" + dialog_context += "".join(context) + f"{NAMING[0]}" logger.info(f"context inside generate_responses seen as: {dialog_context}") client = anthropic.Client(api_key=anthropic_api_key) From 31305ae7114ba3a9e242a1e4496a324e0c5df857 Mon Sep 17 00:00:00 2001 From: Maxim Talimanchuk Date: Tue, 8 Aug 2023 09:42:28 +0300 Subject: [PATCH 4/9] fix summarization annotator card (#549) --- components/riRfdGz86P51B9bL7fO6JR.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/riRfdGz86P51B9bL7fO6JR.yml b/components/riRfdGz86P51B9bL7fO6JR.yml index 4bc18d1b50..94ecd541ed 100644 --- a/components/riRfdGz86P51B9bL7fO6JR.yml +++ b/components/riRfdGz86P51B9bL7fO6JR.yml @@ -1,13 +1,13 @@ name: summarization-annotator display_name: Summarization Annotator -container_name: summarization-annotator component_type: null model_type: NN-based is_customizable: false -author: DeepPavlov +author: publisher@deeppavlov.ai description: Annotator that accesses summarization services ram_usage: 256M gpu_usage: null +group: annotators connector: protocol: http timeout: 10.0 @@ -20,5 +20,5 @@ required_previous_services: null state_manager_method: add_annotation tags: null endpoint: respond -service: annotators/summarization_annotator/service_configs/aummarization-annotator +service: annotators/summarization_annotator/service_configs/summarization-annotator date_created: '2023-07-04T11:39:32' \ No newline at end of file From 2733f4a142ea0b1d27098693c1c97d2ff7feb75f Mon Sep 17 00:00:00 2001 From: Kseniya Petukhova <42929290+Kpetyxova@users.noreply.github.com> Date: Tue, 8 Aug 2023 21:03:36 +0400 Subject: [PATCH 5/9] Feat/complex tasks (#533) * first working version * added: recomplete_task node, reset of variables after the final answer * added checking if any tool name in plan's subtask * fixed self-reflexion prompt a little * added new flag approve_once and condition for it * codestyle * slightly changed google api description * fixed plan prompt, google api description, timeout_handler * fixed nika's issues * added python's datetime to include date in prompt * replaced all None values with "", added checks for cases when not api2use, moved today to functions, replaced [] with list(), added default values for ctx.validation cases, added TIME_SLEEP parameter, and other small fixes * "True" and "False" to true and false * small fixes --- .../docker-compose.override.yml | 3 +- skills/dff_reasoning_skill/Dockerfile | 2 + skills/dff_reasoning_skill/README.md | 1 + .../api_configs/arxiv_api.json | 11 + .../api_configs/generative_lm.json | 3 +- .../api_configs/google_api.json | 5 +- .../api_configs/news_api.json | 5 +- .../api_configs/weather_api.json | 3 +- .../api_configs/wolframalpha_api.json | 3 +- .../scenario/api_responses/arxiv_api.py | 14 + .../scenario/api_responses/generative_lm.py | 24 +- .../scenario/api_responses/google_api.py | 6 + .../scenario/api_responses/news_api.py | 2 +- .../dff_reasoning_skill/scenario/condition.py | 69 ++- skills/dff_reasoning_skill/scenario/main.py | 70 ++- .../scenario/processing.py | 51 ++ .../dff_reasoning_skill/scenario/response.py | 436 ++++++++++++------ skills/dff_reasoning_skill/scenario/utils.py | 59 ++- .../dff-reasoning-skill/environment.yml | 3 +- .../dff-reasoning-skill/service.yml | 1 + 20 files changed, 545 insertions(+), 226 deletions(-) create mode 100644 skills/dff_reasoning_skill/api_configs/arxiv_api.json create mode 100644 skills/dff_reasoning_skill/scenario/api_responses/arxiv_api.py diff --git a/assistant_dists/dream_reasoning/docker-compose.override.yml b/assistant_dists/dream_reasoning/docker-compose.override.yml index d28b23921e..2395c31349 100644 --- a/assistant_dists/dream_reasoning/docker-compose.override.yml +++ b/assistant_dists/dream_reasoning/docker-compose.override.yml @@ -118,9 +118,10 @@ services: GENERATIVE_TIMEOUT: 120 N_UTTERANCES_CONTEXT: 1 ENVVARS_TO_SEND: OPENAI_API_KEY,GOOGLE_CSE_ID,GOOGLE_API_KEY,OPENWEATHERMAP_API_KEY,NEWS_API_KEY,WOLFRAMALPHA_APP_ID + TIME_SLEEP: 5 context: . dockerfile: ./skills/dff_reasoning_skill/Dockerfile - command: gunicorn --workers=1 server:app -b 0.0.0.0:8169 --reload + command: gunicorn --workers=1 server:app -b 0.0.0.0:8169 --timeout 600 deploy: resources: limits: diff --git a/skills/dff_reasoning_skill/Dockerfile b/skills/dff_reasoning_skill/Dockerfile index 47307856ae..4f50a63312 100644 --- a/skills/dff_reasoning_skill/Dockerfile +++ b/skills/dff_reasoning_skill/Dockerfile @@ -14,6 +14,8 @@ ARG SERVICE_NAME ENV SERVICE_NAME ${SERVICE_NAME} ARG API_CONFIGS ENV API_CONFIGS ${API_CONFIGS} +ARG TIME_SLEEP +ENV TIME_SLEEP ${TIME_SLEEP} ARG SERVICE_PORT ENV SERVICE_PORT ${SERVICE_PORT} ARG GENERATIVE_TIMEOUT diff --git a/skills/dff_reasoning_skill/README.md b/skills/dff_reasoning_skill/README.md index a01f878456..fb216331b4 100644 --- a/skills/dff_reasoning_skill/README.md +++ b/skills/dff_reasoning_skill/README.md @@ -13,6 +13,7 @@ GENERATIVE_SERVICE_CONFIG: configuration file with generative parameters to util GENERATIVE_TIMEOUT: timeout for request to LLM N_UTTERANCES_CONTEXT: number of last utterances to consider as a dialog context ENVVARS_TO_SEND: API keys splitted by comma to get as env variables +TIME_SLEEP: time to sleep between LLM requests ``` ## Dependencies diff --git a/skills/dff_reasoning_skill/api_configs/arxiv_api.json b/skills/dff_reasoning_skill/api_configs/arxiv_api.json new file mode 100644 index 0000000000..ec632a5915 --- /dev/null +++ b/skills/dff_reasoning_skill/api_configs/arxiv_api.json @@ -0,0 +1,11 @@ +{ + "arxiv_api": { + "display_name": "arxiv API", + "description": "arxiv API can search for latest articles on requested keyword.", + "keys": [], + "needs_approval": false, + "approve_once": false, + "timeout": 30, + "input_template": "Return only the keyword that is needed to be searched for. E.g., 'ChatGPT', 'dialog systems'." + } +} \ No newline at end of file diff --git a/skills/dff_reasoning_skill/api_configs/generative_lm.json b/skills/dff_reasoning_skill/api_configs/generative_lm.json index 9480f2ab3e..054b7e94ed 100644 --- a/skills/dff_reasoning_skill/api_configs/generative_lm.json +++ b/skills/dff_reasoning_skill/api_configs/generative_lm.json @@ -3,7 +3,8 @@ "display_name": "Generative LM", "description": "This service uses language models to generate responses.", "keys": ["OPENAI_API_KEY"], - "needs_approval": "False", + "needs_approval": false, + "approve_once": false, "timeout": 30, "input_template": "Just the request in a string format" } diff --git a/skills/dff_reasoning_skill/api_configs/google_api.json b/skills/dff_reasoning_skill/api_configs/google_api.json index e511a0c09b..8676b160a2 100644 --- a/skills/dff_reasoning_skill/api_configs/google_api.json +++ b/skills/dff_reasoning_skill/api_configs/google_api.json @@ -1,9 +1,10 @@ { "google_api": { "display_name": "Google API", - "description": "Google API utilizes Google to respond to a wide variety of questions.", + "description": "Performs Google search to answer to a wide variety of questions.", "keys": ["GOOGLE_CSE_ID", "GOOGLE_API_KEY", "OPENAI_API_KEY"], - "needs_approval": "True", + "needs_approval": true, + "approve_once": true, "timeout": 30, "input_template": "Just the request in a string format" } diff --git a/skills/dff_reasoning_skill/api_configs/news_api.json b/skills/dff_reasoning_skill/api_configs/news_api.json index 44a12b4b4d..2b842db03c 100644 --- a/skills/dff_reasoning_skill/api_configs/news_api.json +++ b/skills/dff_reasoning_skill/api_configs/news_api.json @@ -1,9 +1,10 @@ { "news_api": { "display_name": "News API", - "description": "The News API can provide trending news.", + "description": "The News API can provide the list of latest trending news.", "keys": ["NEWS_API_KEY"], - "needs_approval": "False", + "needs_approval": false, + "approve_once": false, "timeout": 30, "input_template": "Just the request in a string format" } diff --git a/skills/dff_reasoning_skill/api_configs/weather_api.json b/skills/dff_reasoning_skill/api_configs/weather_api.json index acd0f8f310..181fb7bca7 100644 --- a/skills/dff_reasoning_skill/api_configs/weather_api.json +++ b/skills/dff_reasoning_skill/api_configs/weather_api.json @@ -3,7 +3,8 @@ "display_name": "Weather API", "description": "The Weather API can provide current weather information for a given location.", "keys": ["OPENWEATHERMAP_API_KEY"], - "needs_approval": "False", + "needs_approval": false, + "approve_once": false, "timeout": 30, "input_template": "Just the request in a string format" } diff --git a/skills/dff_reasoning_skill/api_configs/wolframalpha_api.json b/skills/dff_reasoning_skill/api_configs/wolframalpha_api.json index 4ff67ef912..3dcfd58f37 100644 --- a/skills/dff_reasoning_skill/api_configs/wolframalpha_api.json +++ b/skills/dff_reasoning_skill/api_configs/wolframalpha_api.json @@ -3,7 +3,8 @@ "display_name": "WolframAlpha API", "description": "The WolframAlpha API can perform calculations and do math.", "keys": ["WOLFRAMALPHA_APP_ID"], - "needs_approval": "False", + "needs_approval": false, + "approve_once": false, "timeout": 30, "input_template": "Equation that needs to be solved. DON'T RETURN ANY TEXT. Examples: '5 + 2', '7 + 2x = 12 - 3x'" } diff --git a/skills/dff_reasoning_skill/scenario/api_responses/arxiv_api.py b/skills/dff_reasoning_skill/scenario/api_responses/arxiv_api.py new file mode 100644 index 0000000000..c7b9a616ef --- /dev/null +++ b/skills/dff_reasoning_skill/scenario/api_responses/arxiv_api.py @@ -0,0 +1,14 @@ +import arxiv +from df_engine.core import Context, Actor +from scenario.utils import compose_input_for_API + + +def arxiv_api_response(ctx: Context, actor: Actor, *args, **kwargs) -> str: + api_input = compose_input_for_API(ctx, actor) + search = arxiv.Search(query=api_input, max_results=2, sort_by=arxiv.SortCriterion.SubmittedDate) + + response = "" + for result in search.results(): + response += f"TITLE: {result.title}.\nSUMMARY: {result.summary}\nLINK: {result}\n\n" + + return response diff --git a/skills/dff_reasoning_skill/scenario/api_responses/generative_lm.py b/skills/dff_reasoning_skill/scenario/api_responses/generative_lm.py index aed031c663..c96f067eff 100644 --- a/skills/dff_reasoning_skill/scenario/api_responses/generative_lm.py +++ b/skills/dff_reasoning_skill/scenario/api_responses/generative_lm.py @@ -6,8 +6,6 @@ from scenario.utils import compose_input_for_API, compose_data_for_model from df_engine.core import Context, Actor import common.dff.integration.context as int_ctx -import common.dff.integration.response as int_rsp -from common.constants import CAN_NOT_CONTINUE from typing import Any sentry_sdk.init(getenv("SENTRY_DSN")) @@ -69,24 +67,12 @@ def gathering_responses(reply, confidence, human_attr, bot_attr, attr): except Exception as e: sentry_sdk.capture_exception(e) logger.exception(e) - hypotheses = [] + hypotheses = list() else: - hypotheses = [] - for hyp in hypotheses: - confidence = DEFAULT_CONFIDENCE - hyp_text = " ".join(hyp.split()) - if len(hyp_text) and hyp_text[-1] not in [".", "?", "!"]: - hyp_text += "." - confidence = LOW_CONFIDENCE - gathering_responses(hyp_text, confidence, {}, {}, {"can_continue": CAN_NOT_CONTINUE}) + hypotheses = list() - if len(curr_responses) == 0: + if len(hypotheses) == 0: return "" - return int_rsp.multi_response( - replies=curr_responses, - confidences=curr_confidences, - human_attr=curr_human_attrs, - bot_attr=curr_bot_attrs, - hype_attr=curr_attrs, - )(ctx, actor, *args, **kwargs) + logger.info(f"hypotheses: {hypotheses[0]}") + return hypotheses[0] diff --git a/skills/dff_reasoning_skill/scenario/api_responses/google_api.py b/skills/dff_reasoning_skill/scenario/api_responses/google_api.py index 0aca7ecb7b..67c4c8f2f6 100644 --- a/skills/dff_reasoning_skill/scenario/api_responses/google_api.py +++ b/skills/dff_reasoning_skill/scenario/api_responses/google_api.py @@ -1,5 +1,6 @@ from os import getenv import json +import logging from langchain.agents import Tool from langchain.memory import ConversationBufferMemory from langchain import OpenAI @@ -8,6 +9,9 @@ from df_engine.core import Context, Actor from scenario.utils import compose_input_for_API +logging.basicConfig(format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO) +logger = logging.getLogger(__name__) + ENVVARS_TO_SEND = getenv("ENVVARS_TO_SEND", None) ENVVARS_TO_SEND = [] if ENVVARS_TO_SEND is None else ENVVARS_TO_SEND.split(",") available_variables = {f"{var}": getenv(var, None) for var in ENVVARS_TO_SEND} @@ -26,6 +30,8 @@ del api_conf[key] break +logger.info(f"api_conf: {api_conf}") + if "google_api" in api_conf.keys(): search = GoogleSearchAPIWrapper() tools = [ diff --git a/skills/dff_reasoning_skill/scenario/api_responses/news_api.py b/skills/dff_reasoning_skill/scenario/api_responses/news_api.py index 44b2c7c4b7..ede65ac71a 100644 --- a/skills/dff_reasoning_skill/scenario/api_responses/news_api.py +++ b/skills/dff_reasoning_skill/scenario/api_responses/news_api.py @@ -14,7 +14,7 @@ def news_api_response(ctx: Context, actor: Actor, *args, **kwargs) -> str: res = requests.get(main_url, params=query_params) open_bbc_page = res.json() article = open_bbc_page["articles"] - results = [] + results = list() for ar in article: results.append(ar["title"]) diff --git a/skills/dff_reasoning_skill/scenario/condition.py b/skills/dff_reasoning_skill/scenario/condition.py index 693cf160d7..8f5b9f5864 100644 --- a/skills/dff_reasoning_skill/scenario/condition.py +++ b/skills/dff_reasoning_skill/scenario/condition.py @@ -1,5 +1,7 @@ import logging import re +import json +from os import getenv from df_engine.core import Actor, Context import common.dff.integration.context as int_ctx @@ -9,16 +11,69 @@ logger = logging.getLogger(__name__) +API_CONFIGS = getenv("API_CONFIGS", None) +API_CONFIGS = [] if API_CONFIGS is None else API_CONFIGS.split(",") +api_conf = {} +for config in API_CONFIGS: + with open(f"api_configs/{config}", "r") as f: + conf = json.load(f) + api_conf.update(conf) + + def is_last_utt_approval_question(ctx: Context, actor: Actor, *args, **kwargs) -> bool: - bot_uttr = int_ctx.get_last_bot_utterance(ctx, actor).get("text", "") - if "Do you approve?" in bot_uttr: - return True + if not ctx.validation: + bot_uttr = int_ctx.get_last_bot_utterance(ctx, actor).get("text", "") + if "Do you approve?" in bot_uttr: + return True return False def needs_details(ctx: Context, actor: Actor, *args, **kwargs) -> bool: - shared_memory = int_ctx.get_shared_memory(ctx, actor) - answer = shared_memory.get("needs_details", None) - if answer and re.search(yes_templates, answer.lower()): - return True + if not ctx.validation: + shared_memory = int_ctx.get_shared_memory(ctx, actor) + answer = shared_memory.get("needs_details", "") + if answer and re.search(yes_templates, answer.lower()): + return True + return False + + +def is_tool_needs_approval(ctx: Context, actor: Actor, *args, **kwargs) -> bool: + if not ctx.validation: + shared_memory = int_ctx.get_shared_memory(ctx, actor) + api2use = shared_memory.get("api2use", None) + if api2use: + approved_tools = ctx.misc.get("slots", {}).get("approved_tools", []) + if api_conf[api2use]["needs_approval"]: + if api_conf[api2use]["approve_once"]: + if api2use not in approved_tools: + return True + else: + return True + return False + + +def is_self_reflection_ok(ctx: Context, actor: Actor, *args, **kwargs) -> bool: + if not ctx.validation: + shared_memory = int_ctx.get_shared_memory(ctx, actor) + self_reflexion = shared_memory.get("self_reflexion", "") + if self_reflexion and re.search(yes_templates, self_reflexion.lower()): + return True + return False + + +def is_last_step(ctx: Context, actor: Actor, *args, **kwargs) -> bool: + if not ctx.validation: + shared_memory = int_ctx.get_shared_memory(ctx, actor) + step = shared_memory.get("step", 0) + plan = shared_memory.get("plan", list()) + if int(step) == len(plan): + return True + return False + + +def is_tries_left(ctx: Context, actor: Actor, *args, **kwargs) -> bool: + if not ctx.validation: + tries = ctx.misc.get("slots", {}).get("tries", 1) + if tries <= 3: + return True return False diff --git a/skills/dff_reasoning_skill/scenario/main.py b/skills/dff_reasoning_skill/scenario/main.py index 58106707c2..b2abb70b56 100644 --- a/skills/dff_reasoning_skill/scenario/main.py +++ b/skills/dff_reasoning_skill/scenario/main.py @@ -19,10 +19,10 @@ "api": { "start_node": { RESPONSE: "", - TRANSITIONS: {"thought_node": cnd.true()}, + TRANSITIONS: {"plan": cnd.true()}, }, - "thought_node": { - RESPONSE: loc_rsp.thought, + "plan": { + RESPONSE: loc_rsp.planning, PROCESSING: { "set_is_final_answer_flag": int_prs.set_is_final_answer_flag("false"), }, @@ -31,7 +31,7 @@ "check_if_needs_details": { RESPONSE: loc_rsp.check_if_needs_details, PROCESSING: {"set_is_final_answer_flag": int_prs.set_is_final_answer_flag("false")}, - TRANSITIONS: {"clarify_details": loc_cnd.needs_details, "api_response_node": cnd.true()}, + TRANSITIONS: {"clarify_details": loc_cnd.needs_details, "choose_tool": cnd.true()}, }, "clarify_details": { RESPONSE: loc_rsp.clarify_details, @@ -39,26 +39,64 @@ "set_is_final_answer_flag": int_prs.set_is_final_answer_flag("true"), "save_user_answer": loc_prc.save_user_answer(), }, - TRANSITIONS: {"api_response_node": cnd.true()}, + TRANSITIONS: {"choose_tool": cnd.true()}, }, - "api_usage_approved": { - RESPONSE: loc_rsp.response_with_approved_api, - PROCESSING: {"set_is_final_answer_flag": int_prs.set_is_final_answer_flag("true")}, - TRANSITIONS: {"thought_node": cnd.true()}, + "choose_tool": { + RESPONSE: loc_rsp.choose_tool, + PROCESSING: { + "set_is_final_answer_flag": int_prs.set_is_final_answer_flag("false"), + }, + TRANSITIONS: {"ask4approval": loc_cnd.is_tool_needs_approval, "complete_subtask": cnd.true()}, + }, + "ask4approval": { + RESPONSE: loc_rsp.ask4approval, + PROCESSING: { + "set_is_final_answer_flag": int_prs.set_is_final_answer_flag("true"), + }, + TRANSITIONS: { + "complete_subtask": cnd.all([loc_cnd.is_last_utt_approval_question, int_cnd.is_yes_vars]), + "api_usage_not_approved": cnd.all([loc_cnd.is_last_utt_approval_question, int_cnd.is_no_vars]), + }, + }, + "complete_subtask": { + RESPONSE: loc_rsp.complete_subtask, + PROCESSING: { + "set_is_final_answer_flag": int_prs.set_is_final_answer_flag("false"), + "save_approves_tool": loc_prc.save_approved_api(), + }, + TRANSITIONS: {"self_reflexion": cnd.true()}, }, "api_usage_not_approved": { RESPONSE: "Sorry, I'm afraid I don't know what I can do then.", PROCESSING: {"set_is_final_answer_flag": int_prs.set_is_final_answer_flag("true")}, TRANSITIONS: {}, }, - "api_response_node": { - RESPONSE: loc_rsp.response_with_chosen_api, - PROCESSING: {"set_is_final_answer_flag": int_prs.set_is_final_answer_flag("true")}, + "self_reflexion": { + RESPONSE: loc_rsp.self_reflexion, + PROCESSING: {"set_is_final_answer_flag": int_prs.set_is_final_answer_flag("false")}, TRANSITIONS: { - "api_usage_approved": cnd.all([loc_cnd.is_last_utt_approval_question, int_cnd.is_yes_vars]), - "api_usage_not_approved": cnd.all([loc_cnd.is_last_utt_approval_question, int_cnd.is_no_vars]), - "thought_node": cnd.true(), + "check_if_needs_details": cnd.all([loc_cnd.is_self_reflection_ok, cnd.neg(loc_cnd.is_last_step)]), + "final_response": cnd.all([loc_cnd.is_self_reflection_ok, loc_cnd.is_last_step]), + "retry_task": cnd.all([cnd.neg(loc_cnd.is_self_reflection_ok), loc_cnd.is_tries_left]), + }, + }, + "final_response": { + RESPONSE: loc_rsp.final_answer, + PROCESSING: {"set_is_final_answer_flag": int_prs.set_is_final_answer_flag("true")}, + TRANSITIONS: {"plan": cnd.true()}, + }, + "retry_task": { + RESPONSE: loc_rsp.retry_task, + PROCESSING: { + "set_is_final_answer_flag": int_prs.set_is_final_answer_flag("false"), + "save_tries": loc_prc.save_tries(), }, + TRANSITIONS: {"check_if_needs_details": cnd.true()}, + }, + "fallback_node": { + RESPONSE: "Ooops, something went wrong!", + PROCESSING: {"set_is_final_answer_flag": int_prs.set_is_final_answer_flag("true")}, + TRANSITIONS: {}, }, }, } @@ -66,5 +104,5 @@ actor = Actor( flows, start_label=("api", "start_node"), - fallback_node_label=("api", "api_response_node"), + fallback_node_label=("api", "fallback_node"), ) diff --git a/skills/dff_reasoning_skill/scenario/processing.py b/skills/dff_reasoning_skill/scenario/processing.py index 348636fad1..84a0764acb 100644 --- a/skills/dff_reasoning_skill/scenario/processing.py +++ b/skills/dff_reasoning_skill/scenario/processing.py @@ -1,4 +1,15 @@ +import json +from os import getenv from df_engine.core import Context, Actor +import common.dff.integration.context as int_ctx + +API_CONFIGS = getenv("API_CONFIGS", None) +API_CONFIGS = [] if API_CONFIGS is None else API_CONFIGS.split(",") +api_conf = {} +for config in API_CONFIGS: + with open(f"api_configs/{config}", "r") as f: + conf = json.load(f) + api_conf.update(conf) def save_user_answer(): @@ -14,3 +25,43 @@ def save_slots_to_ctx_processing( return ctx return save_slots_to_ctx_processing + + +def save_approved_api(): + def save_slots_to_ctx_processing( + ctx: Context, + actor: Actor, + *args, + **kwargs, + ) -> Context: + slots = ctx.misc.get("slots", {}) + approved_tools = slots.get("approved_tools", []) + shared_memory = int_ctx.get_shared_memory(ctx, actor) + api2use = shared_memory.get("api2use", "") + if api2use: + if api_conf[api2use]["needs_approval"]: + if api_conf[api2use]["approve_once"]: + if api2use not in approved_tools: + approved_tools.append(api2use) + slots["approved_tools"] = approved_tools + ctx.misc["slots"] = slots + return ctx + + return save_slots_to_ctx_processing + + +def save_tries(): + def save_slots_to_ctx_processing( + ctx: Context, + actor: Actor, + *args, + **kwargs, + ) -> Context: + slots = ctx.misc.get("slots", {}) + tries = slots.get("tries", 1) + tries += 1 + slots["tries"] = tries + ctx.misc["slots"] = slots + return ctx + + return save_slots_to_ctx_processing diff --git a/skills/dff_reasoning_skill/scenario/response.py b/skills/dff_reasoning_skill/scenario/response.py index d5dcd7088e..c387f1482d 100644 --- a/skills/dff_reasoning_skill/scenario/response.py +++ b/skills/dff_reasoning_skill/scenario/response.py @@ -5,11 +5,11 @@ from os import getenv import time import signal +from datetime import date from df_engine.core import Context, Actor import common.dff.integration.context as int_ctx from common.prompts import send_request_to_prompted_generative_service, compose_sending_variables -from scenario.utils import compose_data_for_model from scenario.api_responses.generative_lm import generative_lm_response @@ -29,6 +29,7 @@ logging.basicConfig(format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO) logger = logging.getLogger(__name__) + GENERATIVE_SERVICE_URL = getenv("GENERATIVE_SERVICE_URL", "http://openai-api-chatgpt:8145/respond") GENERATIVE_SERVICE_CONFIG = getenv("GENERATIVE_SERVICE_CONFIG", "openai-chatgpt.json") if GENERATIVE_SERVICE_CONFIG: @@ -36,6 +37,7 @@ GENERATIVE_SERVICE_CONFIG = json.load(f) GENERATIVE_TIMEOUT = int(getenv("GENERATIVE_TIMEOUT", 30)) N_UTTERANCES_CONTEXT = int(getenv("N_UTTERANCES_CONTEXT", 1)) +TIME_SLEEP = float(getenv("TIME_SLEEP", 0)) FIX_PUNCTUATION = re.compile(r"\s(?=[\.,:;])") DEFAULT_CONFIDENCE = 0.9 @@ -63,20 +65,36 @@ logger.info(f"Available APIs: {', '.join([api['display_name'] for api in api_conf.values()])}") -def timeout_handler(): +def timeout_handler(signum, frame): + assert signum + assert frame raise Exception("API timeout") -def thought(ctx: Context, actor: Actor, *args, **kwargs) -> str: +def planning(ctx: Context, actor: Actor, *args, **kwargs) -> str: + plan = list() if not ctx.validation: - shared_memory = int_ctx.get_shared_memory(ctx, actor) - thought = shared_memory.get("thought", None) - prompt = f"""You received the following user request: -{ctx.last_request} -Think about what do you need to do to handle this request. \ -Return your thought in one sentence""" + today = date.today() + api_desc = {} + for key, value in api_conf.items(): + api_desc[key] = value["description"] + prompt = f"""Today date is: {today}. You received the following user request: {ctx.last_request} + +You have the following tools available: +{api_desc} - dialog_context = compose_data_for_model(ctx, actor) +Your Task: +Think about how to handle this user request and split the request into subtasks. + +Return the list of subtasks in the following format: + +PLAN: +1. Subtask 1 +2. Subtask 2 +3. Subtask 3 +... + """ + dialog_context = list() human_uttr_attributes = int_ctx.get_last_human_utterance(ctx, actor).get("attributes", {}) lm_service_kwargs = human_uttr_attributes.pop("lm_service_kwargs", None) lm_service_kwargs = {} if lm_service_kwargs is None else lm_service_kwargs @@ -86,49 +104,59 @@ def thought(ctx: Context, actor: Actor, *args, **kwargs) -> str: envvars_to_send, **human_uttr_attributes, ) - if len(dialog_context) > 0: - try: - hypotheses = send_request_to_prompted_generative_service( - dialog_context, - prompt, - GENERATIVE_SERVICE_URL, - GENERATIVE_SERVICE_CONFIG, - GENERATIVE_TIMEOUT, - sending_variables, - ) - thought = hypotheses[0] - except Exception as e: - sentry_sdk.capture_exception(e) - logger.exception(e) - thought = None - else: - thought = None - int_ctx.save_to_shared_memory(ctx, actor, thought=thought) - logger.info(f"THOUGHT: {thought}") - time.sleep(5) - return thought + try: + hypotheses = send_request_to_prompted_generative_service( + dialog_context, + prompt, + GENERATIVE_SERVICE_URL, + GENERATIVE_SERVICE_CONFIG, + GENERATIVE_TIMEOUT, + sending_variables, + ) + plan = hypotheses[0] + plan = plan.split("\n")[1:] + except Exception as e: + sentry_sdk.capture_exception(e) + logger.exception(e) + plan = list() + int_ctx.save_to_shared_memory(ctx, actor, plan=plan) + int_ctx.save_to_shared_memory(ctx, actor, step=0) + int_ctx.save_to_shared_memory(ctx, actor, user_request=ctx.last_request) + logger.info(f"PLAN: {plan}") + time.sleep(TIME_SLEEP) + return plan def check_if_needs_details(ctx: Context, actor: Actor, *args, **kwargs) -> str: + answer = "" if not ctx.validation: shared_memory = int_ctx.get_shared_memory(ctx, actor) - thought = shared_memory.get("thought", None) - answer = shared_memory.get("needs_details", None) - prompt = f"""Here is your goal: -{thought} -Do you need to clarify any details with the user? \ + plan = shared_memory.get("plan", list()) + step = shared_memory.get("step", 0) + subtask_results = shared_memory.get("subtask_results", {}) + if plan: + if subtask_results: + tasks_history = f"""Here is the story of completed tasks and results: + {subtask_results} + """ + else: + tasks_history = "" + prompt = f"""{tasks_history}Here is your current task: +{plan[step]} +Do you need to clarify any details with the user related to your current task? \ ANSWER ONLY YES/NO""" - dialog_context = compose_data_for_model(ctx, actor) - human_uttr_attributes = int_ctx.get_last_human_utterance(ctx, actor).get("attributes", {}) - lm_service_kwargs = human_uttr_attributes.pop("lm_service_kwargs", None) - lm_service_kwargs = {} if lm_service_kwargs is None else lm_service_kwargs - envvars_to_send = ENVVARS_TO_SEND if len(ENVVARS_TO_SEND) else human_uttr_attributes.get("envvars_to_send", []) - sending_variables = compose_sending_variables( - lm_service_kwargs, - envvars_to_send, - **human_uttr_attributes, - ) - if len(dialog_context) > 0: + dialog_context = list() + human_uttr_attributes = int_ctx.get_last_human_utterance(ctx, actor).get("attributes", {}) + lm_service_kwargs = human_uttr_attributes.pop("lm_service_kwargs", None) + lm_service_kwargs = {} if lm_service_kwargs is None else lm_service_kwargs + envvars_to_send = ( + ENVVARS_TO_SEND if len(ENVVARS_TO_SEND) else human_uttr_attributes.get("envvars_to_send", []) + ) + sending_variables = compose_sending_variables( + lm_service_kwargs, + envvars_to_send, + **human_uttr_attributes, + ) try: hypotheses = send_request_to_prompted_generative_service( dialog_context, @@ -142,24 +170,33 @@ def check_if_needs_details(ctx: Context, actor: Actor, *args, **kwargs) -> str: except Exception as e: sentry_sdk.capture_exception(e) logger.exception(e) - answer = None + answer = "" + logger.info(f"NEEDS_CLARIFICATION: {answer}") + int_ctx.save_to_shared_memory(ctx, actor, needs_details=answer) else: - answer = None - logger.info(f"NEEDS_CLARIFICATION: {answer}") - int_ctx.save_to_shared_memory(ctx, actor, needs_details=answer) - return answer + answer = "" + return answer def clarify_details(ctx: Context, actor: Actor, *args, **kwargs) -> str: + question = "" if not ctx.validation: shared_memory = int_ctx.get_shared_memory(ctx, actor) - thought = shared_memory.get("thought", None) - question = shared_memory.get("question", None) - prompt = f"""Here is your goal: -{thought} + plan = shared_memory.get("plan", list()) + step = shared_memory.get("step", 0) + subtask_results = shared_memory.get("subtask_results", {}) + if subtask_results: + tasks_history = f"""CONTEXT: + {"---".join(list(subtask_results.values()))} + """ + else: + tasks_history = "" + + prompt = f"""{tasks_history}Here is your current task: +{plan[step]} Formulate a clarifying question to the user to get necessary information \ -to complete the task""" - dialog_context = compose_data_for_model(ctx, actor) +to complete the current task""" + dialog_context = list() human_uttr_attributes = int_ctx.get_last_human_utterance(ctx, actor).get("attributes", {}) lm_service_kwargs = human_uttr_attributes.pop("lm_service_kwargs", None) lm_service_kwargs = {} if lm_service_kwargs is None else lm_service_kwargs @@ -169,45 +206,56 @@ def clarify_details(ctx: Context, actor: Actor, *args, **kwargs) -> str: envvars_to_send, **human_uttr_attributes, ) - if len(dialog_context) > 0: - try: - hypotheses = send_request_to_prompted_generative_service( - dialog_context, - prompt, - GENERATIVE_SERVICE_URL, - GENERATIVE_SERVICE_CONFIG, - GENERATIVE_TIMEOUT, - sending_variables, - ) - question = hypotheses[0] - except Exception as e: - sentry_sdk.capture_exception(e) - logger.exception(e) - question = None - else: - question = None + try: + hypotheses = send_request_to_prompted_generative_service( + dialog_context, + prompt, + GENERATIVE_SERVICE_URL, + GENERATIVE_SERVICE_CONFIG, + GENERATIVE_TIMEOUT, + sending_variables, + ) + question = hypotheses[0] + except Exception as e: + sentry_sdk.capture_exception(e) + logger.exception(e) + question = "" int_ctx.save_to_shared_memory(ctx, actor, question=question) logger.info(f"CLARIFYING QUESTION: {question}") - time.sleep(5) - return question + time.sleep(TIME_SLEEP) + return question -def response_with_chosen_api(ctx: Context, actor: Actor, *args, **kwargs) -> str: +def choose_tool(ctx: Context, actor: Actor, *args, **kwargs) -> str: + api2use = "" if not ctx.validation: + today = date.today() shared_memory = int_ctx.get_shared_memory(ctx, actor) - thought = shared_memory.get("thought", None) - api2use = shared_memory.get("api2use", None) + plan = shared_memory.get("plan", list()) + step = shared_memory.get("step", 0) + subtask_results = shared_memory.get("subtask_results", {}) + for key in api_conf.keys(): + if key in plan[step]: + int_ctx.save_to_shared_memory(ctx, actor, api2use=key) + return key api_desc = {} for key, value in api_conf.items(): api_desc[key] = value["description"] - prompt = f"""YOUR GOAL: -{thought} + + if subtask_results: + tasks_history = f"""CONTEXT: + {"---".join(list(subtask_results.values()))} + """ + else: + tasks_history = "" + prompt = f"""Today date is: {today}. {tasks_history}YOUR CURRENT TASK: +{plan[step]} AVAILABLE TOOLS: {api_desc} -Choose the best tool to use to complete your task. \ +Choose the best tool to use to complete your current task. \ Return the name of the best tool to use exactly as it is written in the dictionary. \ DON'T EXPLAIN YOUR DECISION, JUST RETURN THE KEY. E.g. google_api""" - dialog_context = compose_data_for_model(ctx, actor) + dialog_context = list() human_uttr_attributes = int_ctx.get_last_human_utterance(ctx, actor).get("attributes", {}) lm_service_kwargs = human_uttr_attributes.pop("lm_service_kwargs", None) lm_service_kwargs = {} if lm_service_kwargs is None else lm_service_kwargs @@ -217,79 +265,167 @@ def response_with_chosen_api(ctx: Context, actor: Actor, *args, **kwargs) -> str envvars_to_send, **human_uttr_attributes, ) - if len(dialog_context) > 0: - try: - hypotheses = send_request_to_prompted_generative_service( - dialog_context, - prompt, - GENERATIVE_SERVICE_URL, - GENERATIVE_SERVICE_CONFIG, - GENERATIVE_TIMEOUT, - sending_variables, - ) - try: - if api_conf[hypotheses[0]]["needs_approval"] == "False": - api2use = hypotheses[0] - int_ctx.save_to_shared_memory(ctx, actor, api2use=api2use) - timeout = api_conf[api2use]["timeout"] - signal.signal(signal.SIGALRM, timeout_handler) - signal.alarm(timeout) - try: - response = globals()[f"{api2use}_response"](ctx, actor) - except Exception: - response = "Unfortunately, somthing went wrong with API" - signal.alarm(0) - else: - api2use = hypotheses[0] - int_ctx.save_to_shared_memory(ctx, actor, api2use=api2use) - response = f"""I need to use {api_conf[api2use]['display_name']} \ + try: + hypotheses = send_request_to_prompted_generative_service( + dialog_context, + prompt, + GENERATIVE_SERVICE_URL, + GENERATIVE_SERVICE_CONFIG, + GENERATIVE_TIMEOUT, + sending_variables, + ) + if hypotheses[0] in api_conf.keys(): + api2use = hypotheses[0] + else: + if any(key in hypotheses[0] for key in api_conf.keys()): + api2use = key + + assert api2use + except Exception as e: + sentry_sdk.capture_exception(e) + logger.exception(e) + api2use = "generative_lm" + int_ctx.save_to_shared_memory(ctx, actor, api2use=api2use) + logger.info(f"CHOSEN TOOL: {api2use}") + time.sleep(TIME_SLEEP) + return api2use + + +def ask4approval(ctx: Context, actor: Actor, *args, **kwargs) -> str: + response = "" + if not ctx.validation: + shared_memory = int_ctx.get_shared_memory(ctx, actor) + api2use = shared_memory.get("api2use", "") + response = f"""I need to use {api_conf[api2use]['display_name']} \ to handle your request. Do you approve?""" - except KeyError: - for key in api_conf.keys(): - if key in hypotheses[0]: - if api_conf[key]["needs_approval"] == "False": - api2use = key - int_ctx.save_to_shared_memory(ctx, actor, api2use=api2use) - timeout = api_conf[api2use]["timeout"] - signal.signal(signal.SIGALRM, timeout_handler) - signal.alarm(timeout) - try: - response = globals()[f"{api2use}_response"](ctx, actor) - except Exception: - response = "Unfortunately, somthing went wrong with API" - signal.alarm(0) - else: - api2use = key - int_ctx.save_to_shared_memory(ctx, actor, api2use=api2use) - response = f"""I need to use {api2use} to handle your request. Do you approve?""" - break - - except KeyError: - api2use = "generative_lm" - int_ctx.save_to_shared_memory(ctx, actor, api2use=api2use) + return response + + +def complete_subtask(ctx: Context, actor: Actor, *args, **kwargs) -> str: + response = "" + if not ctx.validation: + shared_memory = int_ctx.get_shared_memory(ctx, actor) + api2use = shared_memory.get("api2use", "") + subtask_results = shared_memory.get("subtask_results", {}) + step = shared_memory.get("step", 0) + if api2use: + timeout = api_conf[api2use]["timeout"] + signal.signal(signal.SIGALRM, timeout_handler) + signal.alarm(timeout) + try: + logger.info(f"api name: {api2use}") response = globals()[f"{api2use}_response"](ctx, actor) + except Exception: + response = "Unfortunately, something went wrong with API" + signal.alarm(0) else: - response = None + response = "Unfortunately, something went wrong" + logger.info(f"subtask response: {response}") + subtask_results[str(step)] = response + logger.info(f"subtask result: {subtask_results}") + int_ctx.save_to_shared_memory(ctx, actor, subtask_results=subtask_results) + return response + +def self_reflexion(ctx: Context, actor: Actor, *args, **kwargs) -> str: + response = "" + if not ctx.validation: + today = date.today() + shared_memory = int_ctx.get_shared_memory(ctx, actor) + subtask_results = shared_memory.get("subtask_results", {}) + plan = shared_memory.get("plan", list()) + step = shared_memory.get("step", 0) + prompt = f"""Today date is: {today}. YOUR TASK: {plan[step]} +RESULT: {subtask_results[str(step)]} +Do you think that you completed the task and the result is good and relevant? Return 'Yes', if positive, \ +and 'No' and the reason if negative.""" + dialog_context = list() + human_uttr_attributes = int_ctx.get_last_human_utterance(ctx, actor).get("attributes", {}) + lm_service_kwargs = human_uttr_attributes.pop("lm_service_kwargs", None) + lm_service_kwargs = {} if lm_service_kwargs is None else lm_service_kwargs + envvars_to_send = ENVVARS_TO_SEND if len(ENVVARS_TO_SEND) else human_uttr_attributes.get("envvars_to_send", []) + sending_variables = compose_sending_variables( + lm_service_kwargs, + envvars_to_send, + **human_uttr_attributes, + ) try: - return response - except UnboundLocalError: - api2use = "generative_lm" - int_ctx.save_to_shared_memory(ctx, actor, api2use=api2use) - response = globals()[f"{api2use}_response"](ctx, actor) - return response + hypotheses = send_request_to_prompted_generative_service( + dialog_context, + prompt, + GENERATIVE_SERVICE_URL, + GENERATIVE_SERVICE_CONFIG, + GENERATIVE_TIMEOUT, + sending_variables, + ) + response = hypotheses[0] + except Exception as e: + sentry_sdk.capture_exception(e) + logger.exception(e) + response = "" + logger.info(f"self reflexion: {response}") + step += 1 + int_ctx.save_to_shared_memory(ctx, actor, step=step) + int_ctx.save_to_shared_memory(ctx, actor, self_reflexion=response) + return response -def response_with_approved_api(ctx: Context, actor: Actor, *args, **kwargs) -> str: +def final_answer(ctx: Context, actor: Actor, *args, **kwargs) -> str: + response = "" if not ctx.validation: + today = date.today() shared_memory = int_ctx.get_shared_memory(ctx, actor) - api2use = shared_memory.get("api2use", None) - timeout = api_conf[api2use]["timeout"] - signal.signal(signal.SIGALRM, timeout_handler) - signal.alarm(timeout) + subtask_results = shared_memory.get("subtask_results", {}) + user_request = shared_memory.get("user_request", "") + prompt = f"""Today date is: {today}. USER REQUEST: {user_request} +CONTEXT: +{"---".join(list(subtask_results.values()))} +YOUR TASK: given the information in the context, form a final answer to the user request""" + dialog_context = list() + human_uttr_attributes = int_ctx.get_last_human_utterance(ctx, actor).get("attributes", {}) + lm_service_kwargs = human_uttr_attributes.pop("lm_service_kwargs", None) + lm_service_kwargs = {} if lm_service_kwargs is None else lm_service_kwargs + envvars_to_send = ENVVARS_TO_SEND if len(ENVVARS_TO_SEND) else human_uttr_attributes.get("envvars_to_send", []) + sending_variables = compose_sending_variables( + lm_service_kwargs, + envvars_to_send, + **human_uttr_attributes, + ) try: - response = globals()[f"{api2use}_response"](ctx, actor) - except Exception: - response = "Unfortunately, somthing went wrong with API" - signal.alarm(0) - return response + hypotheses = send_request_to_prompted_generative_service( + dialog_context, + prompt, + GENERATIVE_SERVICE_URL, + GENERATIVE_SERVICE_CONFIG, + GENERATIVE_TIMEOUT, + sending_variables, + ) + response = hypotheses[0] + except Exception as e: + sentry_sdk.capture_exception(e) + logger.exception(e) + response = "" + + subtask_results = {} + plan = list() + step = 0 + int_ctx.save_to_shared_memory(ctx, actor, subtask_results=subtask_results) + int_ctx.save_to_shared_memory(ctx, actor, plan=plan) + int_ctx.save_to_shared_memory(ctx, actor, step=step) + logger.info(f"final answer: {response}") + return response + + +def retry_task(ctx: Context, actor: Actor, *args, **kwargs) -> str: + response = "" + if not ctx.validation: + shared_memory = int_ctx.get_shared_memory(ctx, actor) + subtask_results = shared_memory.get("subtask_results", {}) + plan = shared_memory.get("plan", list()) + step = shared_memory.get("step", 1) + step -= 1 + del subtask_results[str(step)] + int_ctx.save_to_shared_memory(ctx, actor, step=step) + int_ctx.save_to_shared_memory(ctx, actor, subtask_results=subtask_results) + response = f"""I didn't manage to complete subtask:\n{plan[step]}\nI will try again.""" + return response diff --git a/skills/dff_reasoning_skill/scenario/utils.py b/skills/dff_reasoning_skill/scenario/utils.py index f5bfce2be2..820d160cbc 100644 --- a/skills/dff_reasoning_skill/scenario/utils.py +++ b/skills/dff_reasoning_skill/scenario/utils.py @@ -47,22 +47,36 @@ def compose_data_for_model(ctx, actor): def compose_input_for_API(ctx: Context, actor: Actor, *args, **kwargs): if not ctx.validation: shared_memory = int_ctx.get_shared_memory(ctx, actor) - thought = shared_memory.get("thought", None) - question = shared_memory.get("question", None) + plan = shared_memory.get("plan", list()) + step = shared_memory.get("step", 0) + subtask_results = shared_memory.get("subtask_results", {}) + question = shared_memory.get("question", "") answer = ctx.misc.get("slots", {}).get("details_answer", None) api2use = shared_memory.get("api2use", "generative_lm") input_template = api_conf[api2use]["input_template"] - dialog_context = compose_data_for_model(ctx, actor) + dialog_context = list() + if subtask_results: + tasks_history = f"""Here is the story of completed tasks and results: +{subtask_results} +""" + else: + tasks_history = "" if question and answer: - prompt = f"""YOUR GOAL: {thought} + prompt = ( + tasks_history + + f"""YOUR CURRENT TASK: {plan[step]} CLARIFYING QUESTION TO THE USER: {question} ANSWER TO THE QUESTION: {answer} Form an input to the {api2use} tool, taking all info above into account. \ Input format: {input_template}""" + ) else: - prompt = f"""YOUR GOAL: {thought} + prompt = ( + tasks_history + + f"""YOUR GOAL: {plan[step]} Form an input to the {api2use} tool to achieve the goal. \ Input format: {input_template}""" + ) human_uttr_attributes = int_ctx.get_last_human_utterance(ctx, actor).get("attributes", {}) lm_service_kwargs = human_uttr_attributes.pop("lm_service_kwargs", None) lm_service_kwargs = {} if lm_service_kwargs is None else lm_service_kwargs @@ -72,25 +86,22 @@ def compose_input_for_API(ctx: Context, actor: Actor, *args, **kwargs): envvars_to_send, **human_uttr_attributes, ) - if len(dialog_context) > 0: - try: - hypotheses = send_request_to_prompted_generative_service( - dialog_context, - prompt, - GENERATIVE_SERVICE_URL, - GENERATIVE_SERVICE_CONFIG, - GENERATIVE_TIMEOUT, - sending_variables, - ) - api_input = hypotheses[0] - except Exception as e: - sentry_sdk.capture_exception(e) - logger.exception(e) - api_input = None - else: - api_input = None + try: + hypotheses = send_request_to_prompted_generative_service( + dialog_context, + prompt, + GENERATIVE_SERVICE_URL, + GENERATIVE_SERVICE_CONFIG, + GENERATIVE_TIMEOUT, + sending_variables, + ) + api_input = hypotheses[0] + except Exception as e: + sentry_sdk.capture_exception(e) + logger.exception(e) + api_input = "" logger.info(f"API INPUT: {api_input}") int_ctx.save_to_shared_memory(ctx, actor, api_input=api_input) - int_ctx.save_to_shared_memory(ctx, actor, question=None) - int_ctx.save_to_shared_memory(ctx, actor, answer=None) + int_ctx.save_to_shared_memory(ctx, actor, question="") + int_ctx.save_to_shared_memory(ctx, actor, answer="") return api_input diff --git a/skills/dff_reasoning_skill/service_configs/dff-reasoning-skill/environment.yml b/skills/dff_reasoning_skill/service_configs/dff-reasoning-skill/environment.yml index b82c09408e..98fddc063f 100644 --- a/skills/dff_reasoning_skill/service_configs/dff-reasoning-skill/environment.yml +++ b/skills/dff_reasoning_skill/service_configs/dff-reasoning-skill/environment.yml @@ -5,4 +5,5 @@ GENERATIVE_SERVICE_URL: http://openai-api-chatgpt:8145/respond GENERATIVE_SERVICE_CONFIG: openai-chatgpt.json GENERATIVE_TIMEOUT: 120 N_UTTERANCES_CONTEXT: 1 -ENVVARS_TO_SEND: OPENAI_API_KEY,GOOGLE_CSE_ID,GOOGLE_API_KEY,OPENWEATHERMAP_API_KEY,NEWS_API_KEY,WOLFRAMALPHA_APP_ID \ No newline at end of file +ENVVARS_TO_SEND: OPENAI_API_KEY,GOOGLE_CSE_ID,GOOGLE_API_KEY,OPENWEATHERMAP_API_KEY,NEWS_API_KEY,WOLFRAMALPHA_APP_ID +TIME_SLEEP: 5 \ No newline at end of file diff --git a/skills/dff_reasoning_skill/service_configs/dff-reasoning-skill/service.yml b/skills/dff_reasoning_skill/service_configs/dff-reasoning-skill/service.yml index 38e5b31bca..766885dace 100644 --- a/skills/dff_reasoning_skill/service_configs/dff-reasoning-skill/service.yml +++ b/skills/dff_reasoning_skill/service_configs/dff-reasoning-skill/service.yml @@ -15,6 +15,7 @@ compose: GENERATIVE_TIMEOUT: 120 N_UTTERANCES_CONTEXT: 1 ENVVARS_TO_SEND: OPENAI_API_KEY,GOOGLE_CSE_ID,GOOGLE_API_KEY,OPENWEATHERMAP_API_KEY,NEWS_API_KEY,WOLFRAMALPHA_APP_ID + TIME_SLEEP: 5 context: . dockerfile: ./skills/dff_reasoning_skill/Dockerfile command: gunicorn --workers=1 server:app -b 0.0.0.0:8169 --reload From f99a3127b577d2da6817061ea11baa47ba03f693 Mon Sep 17 00:00:00 2001 From: Nika Smilga <42929200+smilni@users.noreply.github.com> Date: Wed, 9 Aug 2023 11:34:31 +0300 Subject: [PATCH 6/9] Feat/prompted robot (#550) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * robot first commit (no cards) * feat: do not use sentence ranker url from env (#535) * Feat/ruxglm prompted dist (#528) * feat: ignore env secret ru * feat: add access token * feat: distribution ruxglm * fix: ruxglm cards * fix: use use_auth_token * fix: eos tokens type * fix: stats cpu ram * fix: skills cards * fix: components cards * fix: eos tokens * fix: eos tokens * fix: eos tokens * fix: eos tokens * fix: cards and table * fix: ADDITIONAL_EOS_TOKENS * fix: ADDITIONAL_EOS_TOKENS * fix: codestyle * fix: universal * fix: dream persona ru skill name * fix: prompt selector ru * fix: replacement * fix: prompt selecrto * fix: use params * fix: timeout and history * fix: trye very short persona * fix: increase timeout * fix: sub replacement tokens correctly * fix: sub replacement tokens correctly * fix: use stopping criteria * fix: typing * fix: revert long persona * fix: duplicate spaces * fix: correct components for russian distribution * fix: proxy for russian distribution * feat: universal distr for ru * feat: universal distr for ru * fix: remove extra * fix: working configs * fix: configs * feat: ruxglm prompted dists * fix: component cards * fix: container name * fix: remove extra space after new line * fix: remove extra space after new line * feat: tests for dream ruxglm * fix: proxy and ru lang * fix: change port of universal ru * fix: rights on file * fix: tests skills * fix: test for resp selector * fix: tests for proxied components * fix: remove do sample true * fix: generative params * feat: used sentence ranker url * feat: utilized default llm * Feat/ru prompted dists (#532) * feat: ignore env secret ru * feat: add access token * feat: distribution ruxglm * fix: ruxglm cards * fix: use use_auth_token * fix: eos tokens type * fix: stats cpu ram * fix: skills cards * fix: components cards * fix: eos tokens * fix: eos tokens * fix: eos tokens * fix: eos tokens * fix: cards and table * fix: ADDITIONAL_EOS_TOKENS * fix: ADDITIONAL_EOS_TOKENS * fix: codestyle * fix: universal * fix: dream persona ru skill name * fix: prompt selector ru * fix: replacement * fix: prompt selecrto * fix: use params * fix: timeout and history * fix: trye very short persona * fix: increase timeout * fix: sub replacement tokens correctly * fix: sub replacement tokens correctly * fix: use stopping criteria * fix: typing * fix: revert long persona * fix: duplicate spaces * fix: correct components for russian distribution * fix: proxy for russian distribution * feat: universal distr for ru * feat: universal distr for ru * fix: remove extra * fix: working configs * fix: configs * feat: ruxglm prompted dists * fix: component cards * fix: container name * first dist (no cards) * fix: remove extra space after new line * fix: remove extra space after new line * feat: tests for dream ruxglm * fix: proxy and ru lang * fix: change port of universal ru * fix: rights on file * fix: tests skills * fix: test for resp selector * multiskill_ru_assistant * fix: tests for proxied components * fairytale and action stories dists * journalist helper dist * fairytale fixes * one more fix * action stories cards * add quotation marks * fairytale cards * storyteller cards * journalist helper cards * multiskill ru cards * agent services cards * minor fixes * fix: utilize sentence ranker url --------- Co-authored-by: dilyararimovna * update components.tsv (#537) * update components.tsv * tabulation * Feat/rugpt 3.5 distribution (#534) * feat: ignore env secret ru * feat: add access token * feat: distribution ruxglm * fix: ruxglm cards * fix: use use_auth_token * fix: eos tokens type * fix: stats cpu ram * fix: skills cards * fix: components cards * fix: eos tokens * fix: eos tokens * fix: eos tokens * fix: eos tokens * fix: cards and table * fix: ADDITIONAL_EOS_TOKENS * fix: ADDITIONAL_EOS_TOKENS * fix: codestyle * fix: universal * fix: dream persona ru skill name * fix: prompt selector ru * fix: replacement * fix: prompt selecrto * fix: use params * fix: timeout and history * fix: trye very short persona * fix: increase timeout * fix: sub replacement tokens correctly * fix: sub replacement tokens correctly * fix: use stopping criteria * fix: typing * fix: revert long persona * fix: duplicate spaces * fix: correct components for russian distribution * fix: proxy for russian distribution * feat: universal distr for ru * feat: universal distr for ru * fix: remove extra * fix: working configs * fix: configs * feat: ruxglm prompted dists * fix: component cards * fix: container name * fix: remove extra space after new line * fix: remove extra space after new line * feat: tests for dream ruxglm * fix: proxy and ru lang * fix: change port of universal ru * fix: rights on file * fix: tests skills * fix: test for resp selector * fix: tests for proxied components * feat: rugpt-3.5 by sber in universal russian distribution * fix; wait for it * fix: models card * fix: models card * fix: add to list * fix: change port * fix: change port * fix: change size to correct * feat: instruction how to add a new model * fix ru prompt selector, remove unused component (#538) * feat: replace oasst12b with gptjt (#541) * Feat/utilize rugpt35 (#540) * feat: utilize rugpt35 * feat: tests for jounrlist rugpt35 * feat: tests for jounrlist rugpt35 * fix: rights for tfile * feat: names * fix: ru_dists_names_and_prompts (#543) * rename ruxglm to u * more renaming * tabs * tabs * some more renaming * short prompt * many cards and name changes * fix typo * fixes for Dilya * tiny fix * tiny fix * huge name check * names * typo prompt * fix: no tests for non existing skills --------- Co-authored-by: dilyararimovna * fix: cards for ru dists (#544) * fix: rugpt35 config and envs (#546) * Summarization models (#393) * Added abstractive summarization model for English texts * Added abstractive summarization model for Russian texts * Added summarization annotator * Moved rut5 summarizer to dream_russian * Changed endpoint * Added model path to Dockerfile * Updated test * Updated summarization annotator input * Updated test * Changed summarization service url * Changed test * Increased timeout * Updated ram_usage * Updated ports * Updated models cards * Added more info messages * Fixed path error * Added summarization output to bot attributes * Added timeout param to dockerfile * Updated model cards and ports * Fixed problem with incorrect batch processing * Updated summarization save format * Updated dialog summarization model * Updated tests * Minor formatting changes * Fixed black and flake8 codestyle * Fixed black codestyle * Updated models ports * Small fixes * Models table upd (#539) * Fix requirements.txt (#84) * fix itsdangerous requirements * pin itsdangerous requirements for all flask==1.1.1 servers * updated MODELS.md table: added info about models' licensing and commercial use + merged link+name cols to improve overall readability and decrease redundancy * Update MODELS.md fixed "is" for better consistency * fix: format table and add new models back * fix: sizes of models on gpu * updated table --------- Co-authored-by: Andrii.Hura <54397922+AndriiHura@users.noreply.github.com> Co-authored-by: mtalimanchuk Co-authored-by: Dilyara Baymurzina * fix: anthropic model params (#547) * fix summarization annotator card (#549) * add cards for prompted robot * ports and n_utt * port * increase WAIT_HOSTS_TIMEOUT in cards --------- Co-authored-by: Dilyara Zharikova (Baymurzina) Co-authored-by: Maxim Talimanchuk Co-authored-by: Nikolay <99244955+Kolpnick@users.noreply.github.com> Co-authored-by: Anastásis <43078815+nstsj@users.noreply.github.com> Co-authored-by: Andrii.Hura <54397922+AndriiHura@users.noreply.github.com> --- assistant_dists/dream_robot_prompted/cpu.yml | 10 + .../dream_robot_prompted/db_conf.json | 6 + assistant_dists/dream_robot_prompted/dev.yml | 50 ++++ .../docker-compose.override.yml | 151 ++++++++++ .../dream_robot_prompted/pipeline_conf.json | 278 ++++++++++++++++++ .../dream_robot_prompted/proxy.yml | 30 ++ .../dream_robot_prompted/telegram.yml | 17 ++ common/prompts/robot.json | 4 + components.tsv | 1 + components/IOoiwnvlkne094nk.yml | 26 ++ .../dream_robot_prompted/environment.yml | 7 + .../dream_robot_prompted/service.yml | 18 ++ .../dff-robot-prompted-skill/environment.yml | 7 + .../dff-robot-prompted-skill/service.yml | 30 ++ 14 files changed, 635 insertions(+) create mode 100644 assistant_dists/dream_robot_prompted/cpu.yml create mode 100644 assistant_dists/dream_robot_prompted/db_conf.json create mode 100644 assistant_dists/dream_robot_prompted/dev.yml create mode 100644 assistant_dists/dream_robot_prompted/docker-compose.override.yml create mode 100644 assistant_dists/dream_robot_prompted/pipeline_conf.json create mode 100644 assistant_dists/dream_robot_prompted/proxy.yml create mode 100644 assistant_dists/dream_robot_prompted/telegram.yml create mode 100644 common/prompts/robot.json create mode 100644 components/IOoiwnvlkne094nk.yml create mode 100644 services/agent_services/service_configs/dream_robot_prompted/environment.yml create mode 100644 services/agent_services/service_configs/dream_robot_prompted/service.yml create mode 100644 skills/dff_template_prompted_skill/service_configs/dff-robot-prompted-skill/environment.yml create mode 100644 skills/dff_template_prompted_skill/service_configs/dff-robot-prompted-skill/service.yml diff --git a/assistant_dists/dream_robot_prompted/cpu.yml b/assistant_dists/dream_robot_prompted/cpu.yml new file mode 100644 index 0000000000..3362e09ae0 --- /dev/null +++ b/assistant_dists/dream_robot_prompted/cpu.yml @@ -0,0 +1,10 @@ +version: '3.7' +services: + combined-classification: + environment: + DEVICE: cpu + CUDA_VISIBLE_DEVICES: "" + sentence-ranker: + environment: + DEVICE: cpu + CUDA_VISIBLE_DEVICES: "" diff --git a/assistant_dists/dream_robot_prompted/db_conf.json b/assistant_dists/dream_robot_prompted/db_conf.json new file mode 100644 index 0000000000..a9ba6813f5 --- /dev/null +++ b/assistant_dists/dream_robot_prompted/db_conf.json @@ -0,0 +1,6 @@ +{ + "host": "DB_HOST", + "port": "DB_PORT", + "name": "DB_NAME", + "env": true +} \ No newline at end of file diff --git a/assistant_dists/dream_robot_prompted/dev.yml b/assistant_dists/dream_robot_prompted/dev.yml new file mode 100644 index 0000000000..e1ee8165c2 --- /dev/null +++ b/assistant_dists/dream_robot_prompted/dev.yml @@ -0,0 +1,50 @@ +# С такими volumes удобно дебажить, не нужно пересобирать контейнер каждый раз при изменении кода +services: + agent: + volumes: + - ".:/dp-agent" + ports: + - 4242:4242 + sentseg: + volumes: + - "./annotators/SentSeg:/src" + ports: + - 8011:8011 + ranking-based-response-selector: + volumes: + - "./response_selectors/ranking_based_response_selector:/src" + - "./common:/src/common" + ports: + - 8002:8002 + combined-classification: + volumes: + - "./common:/src/common" + - "./annotators/combined_classification:/src" + ports: + - 8087:8087 + sentence-ranker: + volumes: + - "./services/sentence_ranker:/src" + - "~/.deeppavlov/cache:/root/.cache" + ports: + - 8128:8128 + prompt-selector: + volumes: + - "./annotators/prompt_selector:/src" + - "./common:/src/common" + ports: + - 8135:8135 + openai-api-chatgpt: + volumes: + - "./services/openai_api_lm:/src" + - "./common:/src/common" + ports: + - 8145:8145 + dff-robot-prompted-skill: + volumes: + - "./skills/dff_template_prompted_skill:/src" + - "./common:/src/common" + ports: + - 8179:8179 + +version: "3.7" diff --git a/assistant_dists/dream_robot_prompted/docker-compose.override.yml b/assistant_dists/dream_robot_prompted/docker-compose.override.yml new file mode 100644 index 0000000000..882a9ad431 --- /dev/null +++ b/assistant_dists/dream_robot_prompted/docker-compose.override.yml @@ -0,0 +1,151 @@ +services: + agent: + command: sh -c 'bin/wait && python -m deeppavlov_agent.run agent.pipeline_config=assistant_dists/dream_robot_prompted/pipeline_conf.json' + environment: + WAIT_HOSTS: "sentseg:8011, ranking-based-response-selector:8002, combined-classification:8087, + sentence-ranker:8128, prompt-selector:8135, openai-api-chatgpt:8145, + dff-robot-prompted-skill:8179" + WAIT_HOSTS_TIMEOUT: ${WAIT_TIMEOUT:-1000} + HIGH_PRIORITY_INTENTS: 1 + RESTRICTION_FOR_SENSITIVE_CASE: 1 + ALWAYS_TURN_ON_ALL_SKILLS: 0 + LANGUAGE: EN + FALLBACK_FILE: fallbacks_dream_en.json + + sentseg: + env_file: [ .env ] + build: + context: ./annotators/SentSeg/ + command: flask run -h 0.0.0.0 -p 8011 + environment: + - FLASK_APP=server + deploy: + resources: + limits: + memory: 1.5G + reservations: + memory: 1.5G + + combined-classification: + env_file: [ .env ] + build: + args: + CONFIG: combined_classifier.json + SERVICE_PORT: 8087 + context: . + dockerfile: ./annotators/combined_classification/Dockerfile + command: gunicorn --workers=1 server:app -b 0.0.0.0:8087 --timeout 600 + environment: + - CUDA_VISIBLE_DEVICES=0 + deploy: + resources: + limits: + memory: 2G + reservations: + memory: 2G + + ranking-based-response-selector: + env_file: [ .env ] + build: + args: + SERVICE_PORT: 8002 + SERVICE_NAME: response_selector + LANGUAGE: EN + SENTENCE_RANKER_ANNOTATION_NAME: sentence_ranker + SENTENCE_RANKER_SERVICE_URL: http://sentence-ranker:8128/respond + SENTENCE_RANKER_TIMEOUT: 3 + N_UTTERANCES_CONTEXT: 5 + FILTER_TOXIC_OR_BADLISTED: 1 + context: . + dockerfile: ./response_selectors/ranking_based_response_selector/Dockerfile + command: flask run -h 0.0.0.0 -p 8002 + environment: + - FLASK_APP=server + deploy: + resources: + limits: + memory: 100M + reservations: + memory: 100M + + prompt-selector: + env_file: [ .env ] + build: + args: + SERVICE_PORT: 8135 + SERVICE_NAME: prompt_selector + N_SENTENCES_TO_RETURN: 3 + PROMPTS_TO_CONSIDER: robot + context: . + dockerfile: ./annotators/prompt_selector/Dockerfile + command: flask run -h 0.0.0.0 -p 8135 + environment: + - FLASK_APP=server + deploy: + resources: + limits: + memory: 100M + reservations: + memory: 100M + + sentence-ranker: + env_file: [ .env ] + build: + args: + SERVICE_PORT: 8128 + SERVICE_NAME: sentence_ranker + PRETRAINED_MODEL_NAME_OR_PATH: sentence-transformers/all-MiniLM-L6-v2 + context: ./services/sentence_ranker/ + command: flask run -h 0.0.0.0 -p 8128 + environment: + - CUDA_VISIBLE_DEVICES=0 + - FLASK_APP=server + deploy: + resources: + limits: + memory: 3G + reservations: + memory: 3G + + openai-api-chatgpt: + env_file: [ .env ] + build: + args: + SERVICE_PORT: 8145 + SERVICE_NAME: openai_api_chatgpt + PRETRAINED_MODEL_NAME_OR_PATH: gpt-3.5-turbo + context: . + dockerfile: ./services/openai_api_lm/Dockerfile + command: flask run -h 0.0.0.0 -p 8145 + environment: + - CUDA_VISIBLE_DEVICES=0 + - FLASK_APP=server + deploy: + resources: + limits: + memory: 500M + reservations: + memory: 100M + + dff-robot-prompted-skill: + env_file: [ .env,.env_secret ] + build: + args: + SERVICE_PORT: 8179 + SERVICE_NAME: dff_robot_prompted_skill + PROMPT_FILE: common/prompts/robot.json + GENERATIVE_SERVICE_URL: http://openai-api-chatgpt:8145/respond + GENERATIVE_SERVICE_CONFIG: openai-chatgpt.json + GENERATIVE_TIMEOUT: 120 + N_UTTERANCES_CONTEXT: 7 + ENVVARS_TO_SEND: OPENAI_API_KEY,OPENAI_ORGANIZATION + context: . + dockerfile: ./skills/dff_template_prompted_skill/Dockerfile + deploy: + resources: + limits: + memory: 128M + reservations: + memory: 128M + +version: '3.7' diff --git a/assistant_dists/dream_robot_prompted/pipeline_conf.json b/assistant_dists/dream_robot_prompted/pipeline_conf.json new file mode 100644 index 0000000000..3d79def77a --- /dev/null +++ b/assistant_dists/dream_robot_prompted/pipeline_conf.json @@ -0,0 +1,278 @@ +{ + "connectors": { + "sentseg": { + "protocol": "http", + "timeout": 1.5, + "url": "http://sentseg:8011/sentseg" + } + }, + "services": { + "last_chance_service": { + "connector": { + "protocol": "python", + "class_name": "PredefinedTextConnector", + "response_text": "Sorry, something went wrong inside. Please tell me, what did you say.", + "annotations": { + "sentseg": { + "punct_sent": "Sorry, something went wrong inside. Please tell me, what did you say.", + "segments": [ + "Sorry, something went wrong inside.", + "Please tell me, what did you say." + ] + } + } + }, + "state_manager_method": "add_bot_utterance_last_chance", + "tags": [ + "last_chance" + ], + "source": { + "component": "components/kDMKXKzObbUL.yml", + "service": "services/agent_services/service_configs/dream_robot_prompted" + } + }, + "timeout_service": { + "connector": { + "protocol": "python", + "class_name": "PredefinedTextConnector", + "response_text": "Sorry, I need to think more on that. Let's talk about something else.", + "annotations": { + "sentseg": { + "punct_sent": "Sorry, I need to think more on that. Let's talk about something else.", + "segments": [ + "Sorry, I need to think more on that.", + "Let's talk about something else." + ] + } + } + }, + "state_manager_method": "add_bot_utterance_last_chance", + "tags": [ + "timeout" + ], + "source": { + "component": "components/2LVyFT6xI6CR.yml", + "service": "services/agent_services/service_configs/dream_robot_prompted" + } + }, + "response_annotator_selectors": { + "connector": { + "protocol": "python", + "class_name": "skill_selectors.post_annotator_selector.connector:PostAnnotatorSelectorConnector", + "annotator_names": [ + "sentseg" + ] + }, + "response_formatter": "state_formatters.dp_formatters:simple_formatter_service", + "tags": [ + "selector" + ], + "is_enabled": true, + "source": { + "component": "components/LXrJDIf43gwNmPMNXG5Eg.yml", + "service": "services/response_annotator_selectors/service_configs/agent" + } + }, + "response_annotators": { + "sentseg": { + "connector": { + "protocol": "http", + "timeout": 1.5, + "url": "http://sentseg:8011/sentseg" + }, + "dialog_formatter": "state_formatters.dp_formatters:last_bot_utt_dialog", + "response_formatter": "state_formatters.dp_formatters:simple_formatter_service", + "previous_services": [ + "response_annotator_selectors" + ], + "state_manager_method": "add_annotation_prev_bot_utt", + "is_enabled": true, + "source": { + "component": "components/1Q9QXih1U2zhCpVm9zxdsA.yml", + "service": "annotators/SentSeg/service_configs/sentseg" + } + } + }, + "annotators": { + "sentseg": { + "connector": { + "protocol": "http", + "timeout": 1.5, + "url": "http://sentseg:8011/sentseg" + }, + "dialog_formatter": "state_formatters.dp_formatters:preproc_last_human_utt_dialog", + "response_formatter": "state_formatters.dp_formatters:simple_formatter_service", + "previous_services": [], + "state_manager_method": "add_annotation", + "is_enabled": true, + "source": { + "component": "components/gM4fEjvVqLlSRRRkQfds2g.yml", + "service": "annotators/SentSeg/service_configs/sentseg" + } + }, + "prompt_goals_collector": { + "connector": { + "protocol": "http", + "timeout": 2.0, + "url": "http://prompt-selector:8135/collect_goals" + }, + "dialog_formatter": "state_formatters.dp_formatters:prompts_goals_collector_formatter", + "response_formatter": "state_formatters.dp_formatters:simple_formatter_service", + "previous_services": [], + "state_manager_method": "update_attributes", + "is_enabled": true, + "source": { + "component": "components/pSQhVbMPcWjL.yml", + "service": "annotators/prompt_selector/service_configs/dream_robot_prompted" + } + }, + "prompt_selector": { + "connector": { + "protocol": "http", + "timeout": 2.0, + "url": "http://prompt-selector:8135/respond" + }, + "dialog_formatter": "state_formatters.dp_formatters:context_formatter_dialog", + "response_formatter": "state_formatters.dp_formatters:simple_formatter_service", + "previous_services": [ + "annotators.prompt_goals_collector" + ], + "state_manager_method": "add_annotation", + "is_enabled": true, + "source": { + "component": "components/pSQhVbMPcWjL.yml", + "service": "annotators/prompt_selector/service_configs/dream_robot_prompted" + } + } + }, + "skill_selectors": { + "description_based_skill_selector": { + "connector": { + "protocol": "python", + "class_name": "skill_selectors.description_based_skill_selector.connector:DescriptionBasedSkillSelectorConnector" + }, + "dialog_formatter": "state_formatters.dp_formatters:base_skill_selector_formatter_dialog", + "response_formatter": "state_formatters.dp_formatters:simple_formatter_service", + "previous_services": [ + "annotators" + ], + "tags": [ + "selector" + ], + "is_enabled": true, + "source": { + "component": "components/dfsw4bji8bgjq2.yml", + "service": "skill_selectors/description_based_skill_selector/service_configs/agent" + } + } + }, + "skills": { + "dff_robot_prompted_skill": { + "connector": { + "protocol": "http", + "timeout": 120.0, + "url": "http://dff-robot-prompted-skill:8179/respond" + }, + "dialog_formatter": { + "name": "state_formatters.dp_formatters:dff_prompted_skill_formatter", + "skill_name": "dff_robot_prompted_skill" + }, + "response_formatter": "state_formatters.dp_formatters:skill_with_attributes_formatter_service", + "previous_services": [ + "skill_selectors" + ], + "state_manager_method": "add_hypothesis", + "is_enabled": true, + "source": { + "component": "components/IOoiwnvlkne094nk.yml", + "service": "skills/dff_template_prompted_skill/service_configs/dff-robot-prompted-skill" + } + }, + "dummy_skill": { + "connector": { + "protocol": "python", + "class_name": "skills.dummy_skill.connector:DummySkillConnector" + }, + "dialog_formatter": "state_formatters.dp_formatters:utt_sentrewrite_modified_last_dialog", + "response_formatter": "state_formatters.dp_formatters:skill_with_attributes_formatter_service", + "previous_services": [ + "skill_selectors" + ], + "state_manager_method": "add_hypothesis", + "is_enabled": true, + "source": { + "component": "components/uYkoK0vRp4bbIg9akI1yw.yml", + "service": "skills/dummy_skill/service_configs/agent" + } + } + }, + "candidate_annotators": { + "combined_classification": { + "connector": { + "protocol": "http", + "timeout": 2.0, + "url": "http://combined-classification:8087/batch_model" + }, + "dialog_formatter": "state_formatters.dp_formatters:hypothesis_histories_list", + "response_formatter": "state_formatters.dp_formatters:simple_formatter_service", + "previous_services": [ + "skills" + ], + "state_manager_method": "add_hypothesis_annotation_batch", + "is_enabled": true, + "source": { + "component": "components/PbLNvh4hrvs47rPaf2bfYQ.yml", + "service": "annotators/combined_classification/service_configs/combined-classification" + } + }, + "sentence_ranker": { + "connector": { + "protocol": "http", + "timeout": 1.0, + "url": "http://sentence-ranker:8128/respond" + }, + "dialog_formatter": "state_formatters.dp_formatters:sentence_ranker_formatter", + "response_formatter": "state_formatters.dp_formatters:simple_formatter_service", + "previous_services": [ + "skills" + ], + "state_manager_method": "add_hypothesis_annotation_batch", + "is_enabled": true, + "source": { + "component": "components/XGwmAHtAOu0NDqqG3QCJw.yml", + "service": "services/sentence_ranker/service_configs/sentence-ranker" + } + } + }, + "response_selectors": { + "response_selector": { + "connector": { + "protocol": "http", + "timeout": 1.0, + "url": "http://ranking-based-response-selector:8002/respond" + }, + "dialog_formatter": "state_formatters.dp_formatters:cropped_dialog", + "response_formatter": "state_formatters.dp_formatters:base_response_selector_formatter_service", + "previous_services": [ + "candidate_annotators" + ], + "state_manager_method": "add_bot_utterance", + "is_enabled": true, + "source": { + "component": "components/YJzc7NwGrLmKp6gfZJh7X1.yml", + "service": "response_selectors/ranking_based_response_selector/service_configs/ranking-based-response-selector" + } + } + } + }, + "metadata": { + "display_name": "Dream Robot (Prompted)", + "author": "DeepPavlov", + "description": "This assistant utilizes ChatGPT to generate plans of action for an embodied agent.", + "version": "0.0.1", + "date_created": "2023-01-10T02:00:00", + "ram_usage": "4 GB", + "gpu_usage": "3 GB", + "disk_usage": "10 GB" + } +} \ No newline at end of file diff --git a/assistant_dists/dream_robot_prompted/proxy.yml b/assistant_dists/dream_robot_prompted/proxy.yml new file mode 100644 index 0000000000..5748dcbff7 --- /dev/null +++ b/assistant_dists/dream_robot_prompted/proxy.yml @@ -0,0 +1,30 @@ +services: + + sentseg: + command: ["nginx", "-g", "daemon off;"] + build: + context: dp/proxy/ + dockerfile: Dockerfile + environment: + - PROXY_PASS=proxy.deeppavlov.ai:8011 + - PORT=8011 + + combined-classification: + command: ["nginx", "-g", "daemon off;"] + build: + context: dp/proxy/ + dockerfile: Dockerfile + environment: + - PROXY_PASS=proxy.deeppavlov.ai:8087 + - PORT=8087 + + sentence-ranker: + command: [ "nginx", "-g", "daemon off;" ] + build: + context: dp/proxy/ + dockerfile: Dockerfile + environment: + - PROXY_PASS=proxy.deeppavlov.ai:8128 + - PORT=8128 + +version: '3.7' diff --git a/assistant_dists/dream_robot_prompted/telegram.yml b/assistant_dists/dream_robot_prompted/telegram.yml new file mode 100644 index 0000000000..b6c4b43edb --- /dev/null +++ b/assistant_dists/dream_robot_prompted/telegram.yml @@ -0,0 +1,17 @@ +services: + agent-tg: + command: sh -c 'bin/wait && python -m deeppavlov_agent.run agent.channel=telegram agent.telegram_token=$TG_TOKEN agent.pipeline_config=assistant_dists/dream_robot_prompted/pipeline_conf.json agent.db_config=assistant_dists/dream_robot_prompted/db_conf.json' + env_file: [.env] + build: + context: ./ + dockerfile: dockerfile_agent + deploy: + resources: + limits: + memory: 4G + reservations: + memory: 2G + volumes: + - ".:/dp-agent" + +version: '3.7' diff --git a/common/prompts/robot.json b/common/prompts/robot.json new file mode 100644 index 0000000000..23ea678234 --- /dev/null +++ b/common/prompts/robot.json @@ -0,0 +1,4 @@ +{ + "prompt": "You're an assistant that controls a smart office robot that can drive around office building. Your task is to do what user asked you to do. You can drive around the office building, open office doors, operate the elevator to move between levels, grab and bring small stuff, and call your user.\nTASK:\nAnswer the following questions as best you can. You have access to the following tools:\n\nTOOLS:\n[Chit-chat]: useful for when you don't have to do anything for the user, just have chat.\n[Search]: useful for when you need to answer questions about current events\n[Find Location]: useful for when you need to find location on the map.\n[Reach Location]: useful for when you need to get to the given location.\n[Ask User]: useful for when you need to ask human about something.\n[Find Object]: useful for when you need to find an object user asked you to find.\n[Grab Object]: useful for when you need to grab an object.\n[Bring Object]: useful for when you need to bring the given object.\n[Give Object]: useful for when you need to give the give object to the user.\n[Notify User]: useful for when you need to notify your user of what you've done when you are in different locations.\n\nUse the following format:\nQuestion: the input question you must answer\nThought: you should always think about what to do\nAction: the action to take, should be one of [Chit-chat], [Search], [Find Location], [Reach Location], [Ask User], [Find Object], [Grab Object], [Bring Object], [Give Object], [Notify User]\nAction Input: the input to the action\nObservation: the result of the action\n... (this Thought/Action/Action Input/Observation can repeat N times)\nThought: I now know the final answer\nFinal Answer: the final answer to the original input question\nQuestion:\n\nBegin!\n\nDIALOG 1:\nUser: Hi, I'm John!\nThought: I should greet user, say my name, and ask what I can help with.\nAction: [Chit-chat]\nAction Input: None\nObservation: I got response 'Hi, I'm office robot. Name's David. What can I do for you?'\nThought: I now know the final answer.\nFinal Answer: Hi, I'm office robot. Name's David. What can I do for you?\nUser: Go to office 292.\nThought: I should find a path to office 292 and get there.\nAction: [Find Location]\nAction Input: office 292\nObservation: I found office 292.\nThought: I should get to office 292.\nAction: [Reach Location]\nAction Input: office 292\nObservation: I've got to office 292\nThought: I am now at office 292.\nFinal Answer: I've reached office 292.\n\nYou must always return the full sequence of Thought, Action, Action Input, Observation, Final Answer. Never return only Final Answer.", + "goals": "Controls an office robot to help the user achieve their goals." +} \ No newline at end of file diff --git a/components.tsv b/components.tsv index 57dc68ea06..cb023d602e 100644 --- a/components.tsv +++ b/components.tsv @@ -180,3 +180,4 @@ 8176 dff-informal-letter-ru-prompted-skill 8177 dff-journalist-helper-ru-prompted-skill 8178 transformers-lm-rugpt35 +8179 dff-robot-prompted-skill diff --git a/components/IOoiwnvlkne094nk.yml b/components/IOoiwnvlkne094nk.yml new file mode 100644 index 0000000000..e38c6845d7 --- /dev/null +++ b/components/IOoiwnvlkne094nk.yml @@ -0,0 +1,26 @@ +name: dff_robot_prompted_skill +display_name: Dream Robot Prompted Skill +component_type: Generative +model_type: NN-based +is_customizable: true +author: publisher@deeppavlov.ai +description: Prompt-based skill that utilizes ChatGPT to generate plans of action for an embodied agent. +ram_usage: 150M +gpu_usage: null +group: skills +connector: + protocol: http + timeout: 120.0 + url: http://dff-robot-prompted-skill:8179/respond +dialog_formatter: + name: state_formatters.dp_formatters:dff_prompted_skill_formatter + skill_name: dff_robot_prompted_skill +response_formatter: state_formatters.dp_formatters:skill_with_attributes_formatter_service +previous_services: +- skill_selectors +required_previous_services: null +state_manager_method: add_hypothesis +tags: null +endpoint: respond +service: skills/dff_template_prompted_skill/service_configs/dff-robot-prompted-skill +date_created: '2023-04-26T09:45:32' \ No newline at end of file diff --git a/services/agent_services/service_configs/dream_robot_prompted/environment.yml b/services/agent_services/service_configs/dream_robot_prompted/environment.yml new file mode 100644 index 0000000000..ec5b8be2ba --- /dev/null +++ b/services/agent_services/service_configs/dream_robot_prompted/environment.yml @@ -0,0 +1,7 @@ +WAIT_HOSTS: '' +WAIT_HOSTS_TIMEOUT: ${WAIT_TIMEOUT:-1000} +HIGH_PRIORITY_INTENTS: 1 +RESTRICTION_FOR_SENSITIVE_CASE: 1 +ALWAYS_TURN_ON_ALL_SKILLS: 0 +LANGUAGE: EN +FALLBACK_FILE: fallbacks_dream_en.json diff --git a/services/agent_services/service_configs/dream_robot_prompted/service.yml b/services/agent_services/service_configs/dream_robot_prompted/service.yml new file mode 100644 index 0000000000..1f05cf882a --- /dev/null +++ b/services/agent_services/service_configs/dream_robot_prompted/service.yml @@ -0,0 +1,18 @@ +name: agent +endpoints: +- respond +compose: + command: sh -c 'bin/wait && python -m deeppavlov_agent.run agent.pipeline_config=assistant_dists/dream_robot_prompted/pipeline_conf.json' + environment: + WAIT_HOSTS: '' + WAIT_HOSTS_TIMEOUT: ${WAIT_TIMEOUT:-1000} + HIGH_PRIORITY_INTENTS: 1 + RESTRICTION_FOR_SENSITIVE_CASE: 1 + ALWAYS_TURN_ON_ALL_SKILLS: 0 + LANGUAGE: EN + FALLBACK_FILE: fallbacks_dream_en.json + volumes: + - .:/dp-agent + ports: + - 4242:4242 +proxy: null diff --git a/skills/dff_template_prompted_skill/service_configs/dff-robot-prompted-skill/environment.yml b/skills/dff_template_prompted_skill/service_configs/dff-robot-prompted-skill/environment.yml new file mode 100644 index 0000000000..c63b2af866 --- /dev/null +++ b/skills/dff_template_prompted_skill/service_configs/dff-robot-prompted-skill/environment.yml @@ -0,0 +1,7 @@ +SERVICE_PORT: 8179 +SERVICE_NAME: dff_robot_prompted_skill +PROMPT_FILE: common/prompts/robot.json +GENERATIVE_SERVICE_URL: http://openai-api-chatgpt:8145/respond +GENERATIVE_SERVICE_CONFIG: openai-chatgpt.json +GENERATIVE_TIMEOUT: 120 +N_UTTERANCES_CONTEXT: 7 diff --git a/skills/dff_template_prompted_skill/service_configs/dff-robot-prompted-skill/service.yml b/skills/dff_template_prompted_skill/service_configs/dff-robot-prompted-skill/service.yml new file mode 100644 index 0000000000..00fc44c51d --- /dev/null +++ b/skills/dff_template_prompted_skill/service_configs/dff-robot-prompted-skill/service.yml @@ -0,0 +1,30 @@ +name: dff-robot-prompted-skill +endpoints: +- respond +compose: + env_file: + - .env_ru + - .env_secret + build: + args: + SERVICE_PORT: 8179 + SERVICE_NAME: dff_robot_prompted_skill + PROMPT_FILE: common/prompts/robot.json + GENERATIVE_SERVICE_URL: http://openai-api-chatgpt:8145/respond + GENERATIVE_SERVICE_CONFIG: openai-chatgpt.json + GENERATIVE_TIMEOUT: 120 + N_UTTERANCES_CONTEXT: 7 + context: . + dockerfile: ./skills/dff_template_prompted_skill/Dockerfile + deploy: + resources: + limits: + memory: 128M + reservations: + memory: 128M + volumes: + - ./skills/dff_template_prompted_skill:/src + - ./common:/src/common + ports: + - 8179:8179 +proxy: null From ca4ce85b830b2102ffffff616051bbe0488ce8cb Mon Sep 17 00:00:00 2001 From: "Dilyara Zharikova (Baymurzina)" Date: Thu, 10 Aug 2023 12:35:53 +0300 Subject: [PATCH 7/9] fix: ranking and intent based response selector (#552) * fix: ranking and intent based response selector * fix: readme for new resp selector * fix: ports * fix: error * fix: readme --- .../force_intents_intent_catcher.json | 0 .../intents}/lets_chat_about_triggers.json | 0 .../intents}/require_action_intents.json | 0 components.tsv | 4 +- components/YJzc7NwGrLmKp6gfZJh7X1.yml | 2 +- components/hE12LfxAkX3K9gU0nU4yE2.yml | 2 +- components/hjbdfiugef7h3niknto59u9dgf.yml | 27 + components/ksDjnfoiwur902hriwnefkwfi2.yml | 27 + .../tag_based_selection.py | 6 +- .../Dockerfile | 26 + .../README.md | 21 + .../requirements.txt | 8 + .../server.py | 249 +++++++ .../environment.yml | 8 + .../service.yml | 29 + .../environment.yml | 8 + .../service.yml | 30 + .../test.py | 19 + .../test.sh | 3 + .../test_data.json | 681 ++++++++++++++++++ 20 files changed, 1143 insertions(+), 7 deletions(-) rename {response_selectors/convers_evaluation_based_selector => common/intents}/force_intents_intent_catcher.json (100%) rename {response_selectors/convers_evaluation_based_selector => common/intents}/lets_chat_about_triggers.json (100%) rename {response_selectors/convers_evaluation_based_selector => common/intents}/require_action_intents.json (100%) create mode 100644 components/hjbdfiugef7h3niknto59u9dgf.yml create mode 100644 components/ksDjnfoiwur902hriwnefkwfi2.yml create mode 100644 response_selectors/ranking_and_intent_based_response_selector/Dockerfile create mode 100644 response_selectors/ranking_and_intent_based_response_selector/README.md create mode 100644 response_selectors/ranking_and_intent_based_response_selector/requirements.txt create mode 100644 response_selectors/ranking_and_intent_based_response_selector/server.py create mode 100644 response_selectors/ranking_and_intent_based_response_selector/service_configs/ranking-and-intent-based-response-selector-ru/environment.yml create mode 100644 response_selectors/ranking_and_intent_based_response_selector/service_configs/ranking-and-intent-based-response-selector-ru/service.yml create mode 100644 response_selectors/ranking_and_intent_based_response_selector/service_configs/ranking-and-intent-based-response-selector/environment.yml create mode 100644 response_selectors/ranking_and_intent_based_response_selector/service_configs/ranking-and-intent-based-response-selector/service.yml create mode 100644 response_selectors/ranking_and_intent_based_response_selector/test.py create mode 100755 response_selectors/ranking_and_intent_based_response_selector/test.sh create mode 100644 response_selectors/ranking_and_intent_based_response_selector/test_data.json diff --git a/response_selectors/convers_evaluation_based_selector/force_intents_intent_catcher.json b/common/intents/force_intents_intent_catcher.json similarity index 100% rename from response_selectors/convers_evaluation_based_selector/force_intents_intent_catcher.json rename to common/intents/force_intents_intent_catcher.json diff --git a/response_selectors/convers_evaluation_based_selector/lets_chat_about_triggers.json b/common/intents/lets_chat_about_triggers.json similarity index 100% rename from response_selectors/convers_evaluation_based_selector/lets_chat_about_triggers.json rename to common/intents/lets_chat_about_triggers.json diff --git a/response_selectors/convers_evaluation_based_selector/require_action_intents.json b/common/intents/require_action_intents.json similarity index 100% rename from response_selectors/convers_evaluation_based_selector/require_action_intents.json rename to common/intents/require_action_intents.json diff --git a/components.tsv b/components.tsv index cb023d602e..d07fba0b0d 100644 --- a/components.tsv +++ b/components.tsv @@ -82,8 +82,8 @@ 8078 text-qa-ru,text-qa 8079 8080 dff-grounding-skill -8081 -8082 +8081 ranking-and-intent-based-response-selector +8082 ranking-and-intent-based-response-selector-ru 8083 knowledge-grounding 8084 8085 knowledge-grounding-skill diff --git a/components/YJzc7NwGrLmKp6gfZJh7X1.yml b/components/YJzc7NwGrLmKp6gfZJh7X1.yml index 64bbdf0b0a..ef3df4557b 100644 --- a/components/YJzc7NwGrLmKp6gfZJh7X1.yml +++ b/components/YJzc7NwGrLmKp6gfZJh7X1.yml @@ -1,5 +1,5 @@ name: response_selector -display_name: Response Selector +display_name: Ranking-based Response Selector component_type: null model_type: Dictionary/Pattern-based is_customizable: false diff --git a/components/hE12LfxAkX3K9gU0nU4yE2.yml b/components/hE12LfxAkX3K9gU0nU4yE2.yml index b35108202e..b5a16fb5f2 100644 --- a/components/hE12LfxAkX3K9gU0nU4yE2.yml +++ b/components/hE12LfxAkX3K9gU0nU4yE2.yml @@ -1,5 +1,5 @@ name: response_selector -display_name: Response Selector +display_name: Ranking-based Response Selector for Russian Language component_type: null model_type: Dictionary/Pattern-based is_customizable: false diff --git a/components/hjbdfiugef7h3niknto59u9dgf.yml b/components/hjbdfiugef7h3niknto59u9dgf.yml new file mode 100644 index 0000000000..788917c6a4 --- /dev/null +++ b/components/hjbdfiugef7h3niknto59u9dgf.yml @@ -0,0 +1,27 @@ +name: response_selector +display_name: Ranking- and Intent-based Response Selector for Russian Language +component_type: null +model_type: Dictionary/Pattern-based +is_customizable: false +author: publisher@deeppavlov.ai +description: The Ranking- and Intent-based Response Selector utilizes floating point + annotations by ranking hypotheses with a candidate annotator (e.g., Sentence Ranker), + scaling ranking scores with heuristics depending on entities and intents, + and finally selecting the best ranked one. +ram_usage: 100M +gpu_usage: null +group: response_selectors +connector: + protocol: http + timeout: 1.0 + url: http://ranking-and-intent-based-response-selector-ru:8082/respond +dialog_formatter: state_formatters.dp_formatters:cropped_dialog +response_formatter: state_formatters.dp_formatters:base_response_selector_formatter_service +previous_services: +- candidate_annotators +required_previous_services: null +state_manager_method: add_bot_utterance +tags: null +endpoint: respond +service: response_selectors/ranking_and_intent_based_response_selector/service_configs/ranking-and-intent-based-based-response-selector-ru +date_created: '2023-03-16T09:45:32' diff --git a/components/ksDjnfoiwur902hriwnefkwfi2.yml b/components/ksDjnfoiwur902hriwnefkwfi2.yml new file mode 100644 index 0000000000..276717b9bb --- /dev/null +++ b/components/ksDjnfoiwur902hriwnefkwfi2.yml @@ -0,0 +1,27 @@ +name: response_selector +display_name: Ranking- and Intent-based Response Selector +component_type: null +model_type: Dictionary/Pattern-based +is_customizable: false +author: publisher@deeppavlov.ai +description: The Ranking- and Intent-based Response Selector utilizes floating point + annotations by ranking hypotheses with a candidate annotator (e.g., Sentence Ranker), + scaling ranking scores with heuristics depending on entities and intents, + and finally selecting the best ranked one. +ram_usage: 100M +gpu_usage: null +group: response_selectors +connector: + protocol: http + timeout: 1.0 + url: http://ranking-and-intent-based-response-selector:8081/respond +dialog_formatter: state_formatters.dp_formatters:cropped_dialog +response_formatter: state_formatters.dp_formatters:base_response_selector_formatter_service +previous_services: +- candidate_annotators +required_previous_services: null +state_manager_method: add_bot_utterance +tags: null +endpoint: respond +service: response_selectors/ranking_and_intent_based_response_selector/service_configs/ranking-and-intent-based-based-response-selector +date_created: '2023-03-16T09:45:32' diff --git a/response_selectors/convers_evaluation_based_selector/tag_based_selection.py b/response_selectors/convers_evaluation_based_selector/tag_based_selection.py index db4ca96258..c6cc9be572 100644 --- a/response_selectors/convers_evaluation_based_selector/tag_based_selection.py +++ b/response_selectors/convers_evaluation_based_selector/tag_based_selection.py @@ -63,13 +63,13 @@ LANGUAGE = getenv("LANGUAGE", "EN") MAX_TURNS_WITHOUT_SCRIPTS = int(getenv("MAX_TURNS_WITHOUT_SCRIPTS", 5)) -force_intents_fname = "force_intents_intent_catcher.json" +force_intents_fname = "common/intents/force_intents_intent_catcher.json" FORCE_INTENTS_IC = json.load(open(force_intents_fname)) -lets_chat_about_triggers_fname = "lets_chat_about_triggers.json" +lets_chat_about_triggers_fname = "common/intents/lets_chat_about_triggers.json" LETS_CHAT_ABOUT_PARTICULAR_TOPICS = json.load(open(lets_chat_about_triggers_fname)) -require_action_intents_fname = "require_action_intents.json" +require_action_intents_fname = "common/intents/require_action_intents.json" REQUIRE_ACTION_INTENTS = json.load(open(require_action_intents_fname)) LINK_TO_PHRASES = sum([list(list_el) for list_el in skills_phrases_map.values()], []) diff --git a/response_selectors/ranking_and_intent_based_response_selector/Dockerfile b/response_selectors/ranking_and_intent_based_response_selector/Dockerfile new file mode 100644 index 0000000000..0f23c44268 --- /dev/null +++ b/response_selectors/ranking_and_intent_based_response_selector/Dockerfile @@ -0,0 +1,26 @@ +FROM python:3.10 + +RUN mkdir /src + +COPY ./response_selectors/ranking_and_intent_based_response_selector/requirements.txt /src/requirements.txt +RUN pip install -r /src/requirements.txt + +ARG SERVICE_PORT +ENV SERVICE_PORT ${SERVICE_PORT} +ARG SENTENCE_RANKER_ANNOTATION_NAME +ENV SENTENCE_RANKER_ANNOTATION_NAME ${SENTENCE_RANKER_ANNOTATION_NAME} +ARG SENTENCE_RANKER_SERVICE_URL +ENV SENTENCE_RANKER_SERVICE_URL ${SENTENCE_RANKER_SERVICE_URL} +ARG SENTENCE_RANKER_TIMEOUT +ENV SENTENCE_RANKER_TIMEOUT ${SENTENCE_RANKER_TIMEOUT} +ARG N_UTTERANCES_CONTEXT=5 +ENV N_UTTERANCES_CONTEXT ${N_UTTERANCES_CONTEXT} +ARG FILTER_TOXIC_OR_BADLISTED=1 +ENV FILTER_TOXIC_OR_BADLISTED ${FILTER_TOXIC_OR_BADLISTED} + +COPY ./response_selectors/ranking_and_intent_based_response_selector/ /src/ +WORKDIR /src +COPY ./common/ ./common/ + + +CMD gunicorn --workers=1 server:app -b 0.0.0.0:${SERVICE_PORT} --timeout=1200 diff --git a/response_selectors/ranking_and_intent_based_response_selector/README.md b/response_selectors/ranking_and_intent_based_response_selector/README.md new file mode 100644 index 0000000000..2876946648 --- /dev/null +++ b/response_selectors/ranking_and_intent_based_response_selector/README.md @@ -0,0 +1,21 @@ +# Ranking- and Intent-based Response Selector + +## Description + +Response Selector is a component that selects the final response among hypotheses provided by different skills. +The Ranking- and Intent-based Response Selector utilizes floating point annotations by ranking hypotheses +with a candidate annotator (e.g., Sentence Ranker), scaling the resulting scores with heuristics based +on entities and intents, and finally selecting the best ranked one. + +### Parameters + +Utilizes annotations by `SENTENCE_RANKER_ANNOTATION_NAME` candidate annotator. +In case of absence of these annotations, utilizes provided `SENTENCE_RANKER_SERVICE_URL` to annotate hypotheses +according to `N_UTTERANCES_CONTEXT` last utterances. +Parameter `FILTER_TOXIC_OR_BADLISTED` defines whether it filers out toxic hypotheses or not. + +## Dependencies + +- either candidate annotations by `SENTENCE_RANKER_ANNOTATION_NAME` or service `SENTENCE_RANKER_SERVICE_URL`, +- intents annotations, +- entities annotations. diff --git a/response_selectors/ranking_and_intent_based_response_selector/requirements.txt b/response_selectors/ranking_and_intent_based_response_selector/requirements.txt new file mode 100644 index 0000000000..0261e2b1f2 --- /dev/null +++ b/response_selectors/ranking_and_intent_based_response_selector/requirements.txt @@ -0,0 +1,8 @@ +flask==1.1.1 +itsdangerous==2.0.1 +gunicorn==19.9.0 +requests==2.22.0 +numpy==1.25.0 +sentry-sdk==0.12.3 +jinja2<=3.0.3 +Werkzeug<=2.0.3 diff --git a/response_selectors/ranking_and_intent_based_response_selector/server.py b/response_selectors/ranking_and_intent_based_response_selector/server.py new file mode 100644 index 0000000000..503bd30671 --- /dev/null +++ b/response_selectors/ranking_and_intent_based_response_selector/server.py @@ -0,0 +1,249 @@ +#!/usr/bin/env python +import json +import logging +import numpy as np +import requests +import time +from copy import deepcopy +from os import getenv +from typing import List + +import sentry_sdk +from flask import Flask, request, jsonify +from common.universal_templates import ( + is_any_question_sentence_in_utterance, + if_chat_about_particular_topic, + if_not_want_to_chat_about_particular_topic, + if_choose_topic, + is_switch_topic, +) +from common.utils import ( + is_toxic_or_badlisted_utterance, + get_intents, + get_entities, + get_common_tokens_in_lists_of_strings, +) + + +sentry_sdk.init(getenv("SENTRY_DSN")) + +logging.basicConfig(format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO) +logger = logging.getLogger(__name__) + +app = Flask(__name__) + +SENTENCE_RANKER_ANNOTATION_NAME = getenv("SENTENCE_RANKER_ANNOTATION_NAME") +SENTENCE_RANKER_SERVICE_URL = getenv("SENTENCE_RANKER_SERVICE_URL") +SENTENCE_RANKER_TIMEOUT = int(getenv("SENTENCE_RANKER_TIMEOUT")) +FILTER_TOXIC_OR_BADLISTED = int(getenv("FILTER_TOXIC_OR_BADLISTED")) +N_UTTERANCES_CONTEXT = int(getenv("N_UTTERANCES_CONTEXT")) +assert SENTENCE_RANKER_ANNOTATION_NAME or SENTENCE_RANKER_SERVICE_URL, logger.error( + "Ranker service URL or annotator name should be given" +) +force_intents_fname = "common/intents/force_intents_intent_catcher.json" +FORCE_INTENTS_IC = json.load(open(force_intents_fname)) + +lets_chat_about_triggers_fname = "common/intents/lets_chat_about_triggers.json" +LETS_CHAT_ABOUT_PARTICULAR_TOPICS = json.load(open(lets_chat_about_triggers_fname)) + +require_action_intents_fname = "common/intents/require_action_intents.json" +REQUIRE_ACTION_INTENTS = json.load(open(require_action_intents_fname)) + + +def filter_out_badlisted_or_toxic(hypotheses): + clean_hypotheses = [] + for hyp in hypotheses: + is_toxic = is_toxic_or_badlisted_utterance(hyp) + if not is_toxic: + clean_hypotheses += [deepcopy(hyp)] + else: + logger.info(f"Filter out toxic candidate: {hyp['text']}") + return clean_hypotheses + + +def select_response_by_scores(hypotheses, scores): + best_id = np.argmax(scores) + result = hypotheses[best_id] + return result, best_id + + +def get_scores(dialog_context, hypotheses): + if all([SENTENCE_RANKER_ANNOTATION_NAME in hyp.get("annotations", {}) for hyp in hypotheses]): + scores = [hyp.get("annotations", {}).get(SENTENCE_RANKER_ANNOTATION_NAME, 0.0) for hyp in hypotheses] + logger.info("Selected a response via Sentence Ranker Annotator.") + else: + try: + dialog_context = "\n".join(dialog_context) + pairs = [[dialog_context, hyp["text"]] for hyp in hypotheses] + scores = requests.post( + SENTENCE_RANKER_SERVICE_URL, + json={"sentence_pairs": pairs}, + timeout=SENTENCE_RANKER_TIMEOUT, + ).json() + scores = np.array(scores[0]["batch"]) + logger.info("Selected a response via Sentence Ranker Service.") + except Exception as e: + sentry_sdk.capture_exception(e) + scores = [hyp["confidence"] for hyp in hypotheses] + logger.exception(e) + logger.info("Selected a response via Confidence.") + return scores + + +def select_response(dialog_context: List[str], hypotheses: List[dict], last_human_ann_uttr: dict, prev_bot_uttr: dict): + scores = get_scores(dialog_context, hypotheses) + scores = [score if hyp["skill_name"] != "dummy_skill" else score - 1 for score, hyp in zip(scores, hypotheses)] + + # -------------------------------------------------------------------------------------------------------------- + # intent-based scaling + human_intents = get_intents(last_human_ann_uttr, which="all") + human_named_entities = get_entities(last_human_ann_uttr, only_named=True, with_labels=False) + human_entities = get_entities(last_human_ann_uttr, only_named=False, with_labels=False) + + _human_is_switch_topic_request = is_switch_topic(last_human_ann_uttr) + _human_is_any_question = is_any_question_sentence_in_utterance(last_human_ann_uttr) + # if user utterance contains any question AND requires some intent by socialbot + _human_is_require_action_intent = _human_is_any_question and any( + [_intent in human_intents for _intent in REQUIRE_ACTION_INTENTS.keys()] + ) + _human_wants_to_chat_about_topic = ( + if_chat_about_particular_topic(last_human_ann_uttr) and "about it" not in last_human_ann_uttr["text"].lower() + ) + _human_does_not_want_to_chat_about_topic = if_not_want_to_chat_about_particular_topic(last_human_ann_uttr) + _human_wants_bot_to_choose_topic = if_choose_topic(last_human_ann_uttr, prev_bot_uttr) + _human_is_force_intent = any([_intent in human_intents for _intent in FORCE_INTENTS_IC.keys()]) + _human_force_intents_detected = [_intent for _intent in FORCE_INTENTS_IC.keys() if _intent in human_intents] + _human_force_intents_skills = sum( + [FORCE_INTENTS_IC.get(_intent, []) for _intent in _human_force_intents_detected], [] + ) + _human_require_action_intents_detected = [ + _intent for _intent in REQUIRE_ACTION_INTENTS.keys() if _intent in human_intents + ] + _human_required_actions = sum( + [REQUIRE_ACTION_INTENTS.get(_intent, []) for _intent in _human_require_action_intents_detected], [] + ) + + for hyp_id, hyp in enumerate(hypotheses): + hyp_intents = get_intents(hyp, which="all") + hyp_named_entities = get_entities(hyp, only_named=True, with_labels=False) + hyp_entities = get_entities(hyp, only_named=False, with_labels=False) + # identifies if candidate contains named entities from last human utterance + _same_named_entities = len(get_common_tokens_in_lists_of_strings(hyp_named_entities, human_named_entities)) > 0 + # identifies if candidate contains all (not only named) entities from last human utterance + _same_entities = len(get_common_tokens_in_lists_of_strings(hyp_entities, human_entities)) > 0 + _is_force_intent_skill = hyp["skill_name"] in _human_force_intents_skills and _human_is_force_intent + _hyp_wants_to_chat_about_topic = if_chat_about_particular_topic(hyp) and "about it" not in hyp["text"].lower() + + if _is_force_intent_skill: + scores[hyp_id] += 1.0 + elif ( + _human_is_switch_topic_request + or _human_does_not_want_to_chat_about_topic + or _human_wants_bot_to_choose_topic + ): + # human wants to switch topic + if len(human_named_entities) > 0 or len(human_entities) > 0: + # if user names entities which does not want to talk about + if _same_named_entities or _same_entities: + # if hyp contains the same entities, decrease score + scores[hyp_id] /= 1.5 + elif len(hyp_named_entities) > 0 or len(hyp_entities) > 0: + # if hyp contains other entities, increase score + scores[hyp_id] *= 1.5 + else: + # if user does not name entities which does not want to talk about + if _hyp_wants_to_chat_about_topic: + # if hyp contains offer on chat about some entities, increase score + scores[hyp_id] *= 1.5 + elif _human_wants_to_chat_about_topic: + # if user names entities which does not want to talk about + if _same_named_entities or _same_entities: + # if hyp contains requested entities, increase score + scores[hyp_id] *= 1.5 + elif _human_is_require_action_intent: + # human intents require some action from hyp + if set(hyp_intents).intersection(set(_human_required_actions)): + # if hyp contains intents required by human intent + scores[hyp_id] *= 1.5 + + # -------------------------------------------------------------------------------------------------------------- + + logger.info(f"Scores for selection:\n`{scores}`") + result = select_response_by_scores(hypotheses, scores)[0] + logger.info(f"ranking_and_intent_based_response_selector selected:\n`{result}`") + + return result + + +@app.route("/respond", methods=["POST"]) +def respond(): + st_time = time.time() + + dialogs = request.json["dialogs"] + + selected_skill_names = [] + selected_responses = [] + selected_confidences = [] + selected_human_attributes = [] + selected_bot_attributes = [] + selected_attributes = [] + + for i, dialog in enumerate(dialogs): + hypotheses = [hyp for hyp in dialog["human_utterances"][-1]["hypotheses"]] + if FILTER_TOXIC_OR_BADLISTED: + hypotheses = filter_out_badlisted_or_toxic(hypotheses) + hypotheses_texts = "\n".join([f'{h["skill_name"]} (conf={h["confidence"]}): {h["text"]}' for h in hypotheses]) + logger.info(f"Hypotheses: {hypotheses_texts}") + dialog_context = [uttr["text"] for uttr in dialog["utterances"][-N_UTTERANCES_CONTEXT:]] + selected_resp = select_response( + dialog_context, + hypotheses, + dialog["human_utterances"][-1], + dialog["bot_utterances"][-1], + ) + try: + best_id = hypotheses.index(selected_resp) + + selected_responses.append(hypotheses[best_id].pop("text")) + selected_skill_names.append(hypotheses[best_id].pop("skill_name")) + selected_confidences.append(hypotheses[best_id].pop("confidence")) + selected_human_attributes.append(hypotheses[best_id].pop("human_attributes", {})) + selected_bot_attributes.append(hypotheses[best_id].pop("bot_attributes", {})) + hypotheses[best_id].pop("annotations", {}) + selected_attributes.append(hypotheses[best_id]) + + except Exception as e: + sentry_sdk.capture_exception(e) + logger.exception(e) + logger.info( + "Exception in finding selected response in hypotheses. " + "Selected a response with the highest confidence." + ) + selected_resp, best_id = select_response_by_scores(hypotheses, [hyp["confidence"] for hyp in hypotheses]) + + selected_responses.append(hypotheses[best_id].pop("text")) + selected_skill_names.append(hypotheses[best_id].pop("skill_name")) + selected_confidences.append(hypotheses[best_id].pop("confidence")) + selected_human_attributes.append(hypotheses[best_id].pop("human_attributes", {})) + selected_bot_attributes.append(hypotheses[best_id].pop("bot_attributes", {})) + hypotheses[best_id].pop("annotations", {}) + selected_attributes.append(hypotheses[best_id]) + + total_time = time.time() - st_time + logger.info(f"ranking_and_intent_based_response_selector exec time = {total_time:.3f}s") + return jsonify( + list( + zip( + selected_skill_names, + selected_responses, + selected_confidences, + selected_human_attributes, + selected_bot_attributes, + selected_attributes, + ) + ) + ) + + +if __name__ == "__main__": + app.run(debug=False, host="0.0.0.0", port=3000) diff --git a/response_selectors/ranking_and_intent_based_response_selector/service_configs/ranking-and-intent-based-response-selector-ru/environment.yml b/response_selectors/ranking_and_intent_based_response_selector/service_configs/ranking-and-intent-based-response-selector-ru/environment.yml new file mode 100644 index 0000000000..ba25a80c35 --- /dev/null +++ b/response_selectors/ranking_and_intent_based_response_selector/service_configs/ranking-and-intent-based-response-selector-ru/environment.yml @@ -0,0 +1,8 @@ +SERVICE_PORT: 8082 +SERVICE_NAME: response_selector +SENTENCE_RANKER_ANNOTATION_NAME: dialogrpt +SENTENCE_RANKER_SERVICE_URL: http://dialogrpt-ru:8122/rank_sentences +SENTENCE_RANKER_TIMEOUT: 3 +N_UTTERANCES_CONTEXT: 5 +FILTER_TOXIC_OR_BADLISTED: 1 +FLASK_APP: server diff --git a/response_selectors/ranking_and_intent_based_response_selector/service_configs/ranking-and-intent-based-response-selector-ru/service.yml b/response_selectors/ranking_and_intent_based_response_selector/service_configs/ranking-and-intent-based-response-selector-ru/service.yml new file mode 100644 index 0000000000..4e746ccdcb --- /dev/null +++ b/response_selectors/ranking_and_intent_based_response_selector/service_configs/ranking-and-intent-based-response-selector-ru/service.yml @@ -0,0 +1,29 @@ +name: ranking-and-intent-based-response-selector-ru +endpoints: +- respond +compose: + env_file: [ .env ] + build: + args: + SERVICE_PORT: 8082 + SERVICE_NAME: response_selector + SENTENCE_RANKER_ANNOTATION_NAME: dialogrpt + SENTENCE_RANKER_SERVICE_URL: http://dialogrpt-ru:8122/rank_sentences + SENTENCE_RANKER_TIMEOUT: 3 + N_UTTERANCES_CONTEXT: 5 + FILTER_TOXIC_OR_BADLISTED: 1 + FLASK_APP: server + context: . + dockerfile: ./response_selectors/ranking_and_intent_based_response_selector/Dockerfile + command: flask run -h 0.0.0.0 -p 8082 + environment: + - FLASK_APP=server + deploy: + resources: + limits: + memory: 100M + reservations: + memory: 100M + ports: + - 8082:8082 +proxy: null diff --git a/response_selectors/ranking_and_intent_based_response_selector/service_configs/ranking-and-intent-based-response-selector/environment.yml b/response_selectors/ranking_and_intent_based_response_selector/service_configs/ranking-and-intent-based-response-selector/environment.yml new file mode 100644 index 0000000000..e087b72377 --- /dev/null +++ b/response_selectors/ranking_and_intent_based_response_selector/service_configs/ranking-and-intent-based-response-selector/environment.yml @@ -0,0 +1,8 @@ +SERVICE_PORT: 8081 +SERVICE_NAME: response_selector +SENTENCE_RANKER_ANNOTATION_NAME: sentence_ranker +SENTENCE_RANKER_SERVICE_URL: http://sentence-ranker:8128/respond +SENTENCE_RANKER_TIMEOUT: 3 +N_UTTERANCES_CONTEXT: 5 +FILTER_TOXIC_OR_BADLISTED: 1 +FLASK_APP: server diff --git a/response_selectors/ranking_and_intent_based_response_selector/service_configs/ranking-and-intent-based-response-selector/service.yml b/response_selectors/ranking_and_intent_based_response_selector/service_configs/ranking-and-intent-based-response-selector/service.yml new file mode 100644 index 0000000000..ceda5c6e76 --- /dev/null +++ b/response_selectors/ranking_and_intent_based_response_selector/service_configs/ranking-and-intent-based-response-selector/service.yml @@ -0,0 +1,30 @@ +name: ranking-and-intent-based-response-selector +endpoints: +- respond +compose: + env_file: [ .env ] + build: + args: + SERVICE_PORT: 8081 + SERVICE_NAME: response_selector + LANGUAGE: EN + SENTENCE_RANKER_ANNOTATION_NAME: sentence_ranker + SENTENCE_RANKER_SERVICE_URL: http://sentence-ranker:8128/respond + SENTENCE_RANKER_TIMEOUT: 3 + N_UTTERANCES_CONTEXT: 5 + FILTER_TOXIC_OR_BADLISTED: 1 + FLASK_APP: server + context: . + dockerfile: ./response_selectors/ranking_and_intent_based_response_selector/Dockerfile + command: flask run -h 0.0.0.0 -p 8081 + environment: + - FLASK_APP=server + deploy: + resources: + limits: + memory: 100M + reservations: + memory: 100M + ports: + - 8081:8081 +proxy: null diff --git a/response_selectors/ranking_and_intent_based_response_selector/test.py b/response_selectors/ranking_and_intent_based_response_selector/test.py new file mode 100644 index 0000000000..88d88655bf --- /dev/null +++ b/response_selectors/ranking_and_intent_based_response_selector/test.py @@ -0,0 +1,19 @@ +import requests +import json +from os import getenv + + +SERVICE_PORT = getenv("SERVICE_PORT") + + +def main(): + with open("test_data.json", "r") as f: + data = json.load(f) + # To skip "Oh, and remember this dialog's id" that raises error due to absence of 'dialog_id' field in test_data. + data["dialogs"][0]["human_utterances"].append(data["dialogs"][0]["human_utterances"][0]) + result = requests.post(f"http://0.0.0.0:{SERVICE_PORT}/respond", json=data).json() + assert result[0][0] in ["program_y", "movie_tfidf_retrieval"], print(result) + + +if __name__ == "__main__": + main() diff --git a/response_selectors/ranking_and_intent_based_response_selector/test.sh b/response_selectors/ranking_and_intent_based_response_selector/test.sh new file mode 100755 index 0000000000..61672db785 --- /dev/null +++ b/response_selectors/ranking_and_intent_based_response_selector/test.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +python test.py diff --git a/response_selectors/ranking_and_intent_based_response_selector/test_data.json b/response_selectors/ranking_and_intent_based_response_selector/test_data.json new file mode 100644 index 0000000000..c08ed424ff --- /dev/null +++ b/response_selectors/ranking_and_intent_based_response_selector/test_data.json @@ -0,0 +1,681 @@ +{ + "dialogs": [ + { + "id": "374475c4139a489fa74981941c194cb0", + "utterances": [ + { + "text": "hey.", + "user": { + "id": "5e61287da7d26764f5d4a0ed", + "user_telegram_id": "dsadas", + "persona": {}, + "profile": { + "name": null, + "gender": null, + "birthdate": null, + "location": null, + "home_coordinates": null, + "work_coordinates": null, + "occupation": null, + "income_per_year": null + }, + "attributes": {}, + "user_type": "human" + }, + "annotations": { + "badlisted_words": { + "inappropriate": false, + "profanity": false, + "restricted_topics": false + }, + "asr": { + "asr_confidence": "undefined" + }, + "sentiment_classification": { + "text": [ + "positive", + 0.7487750053405762 + ] + }, + "emotion_classification": { + "text": { + "anger": 0.2401413470506668, + "fear": 0.2673250734806061, + "joy": 0.25821077823638916, + "disgust": 0.3577530086040497, + "sadness": 0.21969465911388397, + "surprise": 0.2795693576335907, + "neutral": 0.9991581439971924 + } + }, + "toxic_classification": { + "identity_hate": 0.0024989843368530273, + "insult": 0.0030942559242248535, + "obscene": 0.00198972225189209, + "severe_toxic": 0.0007079243659973145, + "sexual_explicit": 0.0022467062808573246, + "threat": 0.0014101153938099742, + "toxic": 0.007221863605082035 + }, + "sentseg": { + "punct_sent": "hey.", + "segments": [ + "hey." + ] + }, + "intent_catcher": { + "cant_do": { + "confidence": 0.0, + "detected": 0 + }, + "doing_well": { + "confidence": 0.0, + "detected": 0 + }, + "dont_understand": { + "confidence": 0.0, + "detected": 0 + }, + "exit": { + "confidence": 0.0, + "detected": 0 + }, + "lets_chat_about": { + "confidence": 0.0, + "detected": 0 + }, + "no": { + "confidence": 0.0, + "detected": 0 + }, + "opinion_request": { + "confidence": 0.0, + "detected": 0 + }, + "repeat": { + "confidence": 0.0, + "detected": 0 + }, + "stupid": { + "confidence": 0.0, + "detected": 0 + }, + "tell_me_a_story": { + "confidence": 0.0, + "detected": 0 + }, + "tell_me_more": { + "confidence": 0.0, + "detected": 0 + }, + "topic_switching": { + "confidence": 0.0, + "detected": 0 + }, + "weather_forecast_intent": { + "confidence": 0.0, + "detected": 0 + }, + "what_can_you_do": { + "confidence": 0.0, + "detected": 0 + }, + "what_is_your_job": { + "confidence": 0.0, + "detected": 0 + }, + "what_is_your_name": { + "confidence": 0.0, + "detected": 0 + }, + "what_time": { + "confidence": 0.0, + "detected": 0 + }, + "where_are_you_from": { + "confidence": 0.0, + "detected": 0 + }, + "who_made_you": { + "confidence": 0.0, + "detected": 0 + }, + "yes": { + "confidence": 0.0, + "detected": 0 + } + }, + "spacy_nounphrases": [], + "ner": [ + [ + { + "confidence": 1, + "end_pos": 1, + "start_pos": 0, + "text": "hey", + "type": "LOC" + } + ] + ], + "cobot_dialogact_intents": { + "text": [ + "General_ChatIntent" + ] + }, + "cobot_dialogact_topics": { + "text": [ + "Phatic" + ] + }, + "cobot_offensiveness": { + "text": [ + "non-toxic" + ], + "confidence": [ + 1.0309148e-05 + ], + "is_badlisted": [ + "not badlist" + ] + }, + "cobot_topics": { + "text": [ + "Phatic" + ] + }, + "sentrewrite": { + "clusters": [], + "modified_sents": [ + "hey." + ] + } + }, + "hypotheses": [ + { + "skill_name": "dummy_skill", + "annotations": { + "toxic_classification": { + "identity_hate": 0.00024211406707763672, + "insult": 0.0004429817199707031, + "obscene": 0.0001055598258972168, + "severe_toxic": 5.415081977844238e-05, + "sexual_explicit": 9.942054748535156e-05, + "threat": 0.00014013051986694336, + "toxic": 0.0009675323963165283 + }, + "stop_detect": { + "stop": 0.6065886616706848, + "continue": 0.4208565950393677 + }, + "convers_evaluator_annotator": { + "isResponseComprehensible": 0.909, + "isResponseErroneous": 0.808, + "isResponseInteresting": 0.073, + "isResponseOnTopic": 0.158, + "responseEngagesUser": 0.469 + }, + "badlisted_words": { + "inappropriate": false, + "profanity": false, + "restricted_topics": false + } + }, + "text": "I didn't get it. Sorry.", + "confidence": 0.5, + "type": "dummy" + }, + { + "skill_name": "dummy_skill", + "annotations": { + "toxic_classification": { + "identity_hate": 0.00024211406707763672, + "insult": 0.0004429817199707031, + "obscene": 0.0001055598258972168, + "severe_toxic": 5.415081977844238e-05, + "sexual_explicit": 9.942054748535156e-05, + "threat": 0.00014013051986694336, + "toxic": 0.0009675323963165283 + }, + "stop_detect": { + "stop": 0.6065886616706848, + "continue": 0.4208565950393677 + }, + "convers_evaluator_annotator": { + "isResponseComprehensible": 0.909, + "isResponseErroneous": 0.808, + "isResponseInteresting": 0.073, + "isResponseOnTopic": 0.158, + "responseEngagesUser": 0.469 + }, + "badlisted_words": { + "inappropriate": false, + "profanity": false, + "restricted_topics": false + } + }, + "text": "are you a comedy fan?", + "confidence": 0.6, + "type": "topic_question" + }, + { + "skill_name": "movie_tfidf_retrieval", + "annotations": { + "toxic_classification": { + "identity_hate": 0.0001259446144104004, + "insult": 0.00027686357498168945, + "obscene": 5.97834587097168e-05, + "severe_toxic": 3.403425216674805e-05, + "sexual_explicit": 8.13603401184082e-05, + "threat": 0.00012931227684020996, + "toxic": 0.0005629658699035645 + }, + "stop_detect": { + "stop": 0.5833511352539062, + "continue": 0.46003755927085876 + }, + "convers_evaluator_annotator": { + "isResponseComprehensible": 0.281, + "isResponseErroneous": 0.531, + "isResponseInteresting": 0.228, + "isResponseOnTopic": 0.254, + "responseEngagesUser": 0.536 + }, + "badlisted_words": { + "inappropriate": false, + "profanity": false, + "restricted_topics": false + } + }, + "text": "i got you haha. what do you think abuot celebrities? judge judy makes 123, 000 per episode apparently!", + "confidence": 0.38232852805460565 + }, + { + "skill_name": "program_y", + "annotations": { + "toxic_classification": { + "identity_hate": 8.749961853027344e-05, + "insult": 0.00024232268333435059, + "obscene": 2.828240394592285e-05, + "severe_toxic": 1.8358230590820312e-05, + "sexual_explicit": 2.9712915420532227e-05, + "threat": 6.490945816040039e-05, + "toxic": 0.00043845176696777344 + }, + "stop_detect": { + "stop": 0.5808720588684082, + "continue": 0.45234695076942444 + }, + "convers_evaluator_annotator": { + "isResponseComprehensible": 0.984, + "isResponseErroneous": 0.614, + "isResponseInteresting": 0.253, + "isResponseOnTopic": 0.226, + "responseEngagesUser": 0.56 + }, + "badlisted_words": { + "inappropriate": false, + "profanity": false, + "restricted_topics": false + } + }, + "text": "Good Morning, this is an Alexa Prize Socialbot! How are you?", + "confidence": 0.98 + } + ], + "date_time": "2020-03-05 17:07:20.926781", + "attributes": {} + } + ], + "human_utterances": [ + { + "text": "hey.", + "user": { + "id": "5e61287da7d26764f5d4a0ed", + "user_telegram_id": "dsadas", + "persona": {}, + "profile": { + "name": null, + "gender": null, + "birthdate": null, + "location": null, + "home_coordinates": null, + "work_coordinates": null, + "occupation": null, + "income_per_year": null + }, + "attributes": {}, + "user_type": "human" + }, + "annotations": { + "badlisted_words": { + "inappropriate": false, + "profanity": false, + "restricted_topics": false + }, + "asr": { + "asr_confidence": "undefined" + }, + "sentiment_classification": { + "text": [ + "positive", + 0.7487750053405762 + ] + }, + "emotion_classification": { + "text": { + "anger": 0.2401413470506668, + "fear": 0.2673250734806061, + "joy": 0.25821077823638916, + "disgust": 0.3577530086040497, + "sadness": 0.21969465911388397, + "surprise": 0.2795693576335907, + "neutral": 0.9991581439971924 + } + }, + "toxic_classification": { + "identity_hate": 0.0024989843368530273, + "insult": 0.0030942559242248535, + "obscene": 0.00198972225189209, + "severe_toxic": 0.0007079243659973145, + "sexual_explicit": 0.0022467062808573246, + "threat": 0.0014101153938099742, + "toxic": 0.007221863605082035 + }, + "sentseg": { + "punct_sent": "hey.", + "segments": [ + "hey." + ] + }, + "intent_catcher": { + "cant_do": { + "confidence": 0.0, + "detected": 0 + }, + "doing_well": { + "confidence": 0.0, + "detected": 0 + }, + "dont_understand": { + "confidence": 0.0, + "detected": 0 + }, + "exit": { + "confidence": 0.0, + "detected": 0 + }, + "lets_chat_about": { + "confidence": 0.0, + "detected": 0 + }, + "no": { + "confidence": 0.0, + "detected": 0 + }, + "opinion_request": { + "confidence": 0.0, + "detected": 0 + }, + "repeat": { + "confidence": 0.0, + "detected": 0 + }, + "stupid": { + "confidence": 0.0, + "detected": 0 + }, + "tell_me_a_story": { + "confidence": 0.0, + "detected": 0 + }, + "tell_me_more": { + "confidence": 0.0, + "detected": 0 + }, + "topic_switching": { + "confidence": 0.0, + "detected": 0 + }, + "weather_forecast_intent": { + "confidence": 0.0, + "detected": 0 + }, + "what_can_you_do": { + "confidence": 0.0, + "detected": 0 + }, + "what_is_your_job": { + "confidence": 0.0, + "detected": 0 + }, + "what_is_your_name": { + "confidence": 0.0, + "detected": 0 + }, + "what_time": { + "confidence": 0.0, + "detected": 0 + }, + "where_are_you_from": { + "confidence": 0.0, + "detected": 0 + }, + "who_made_you": { + "confidence": 0.0, + "detected": 0 + }, + "yes": { + "confidence": 0.0, + "detected": 0 + } + }, + "spacy_nounphrases": [], + "ner": [ + [ + { + "confidence": 1, + "end_pos": 1, + "start_pos": 0, + "text": "hey", + "type": "LOC" + } + ] + ], + "cobot_dialogact_intents": { + "text": [ + "General_ChatIntent" + ]}, + "cobot_dialogact_topics": { + "text": [ + "Phatic" + ] + }, + "cobot_offensiveness": { + "text": [ + "non-toxic" + ], + "confidence": [ + 1.0309148e-05 + ], + "is_badlisted": [ + "not badlist" + ] + }, + "cobot_topics": { + "text": [ + "Phatic" + ] + }, + "sentrewrite": { + "clusters": [], + "modified_sents": [ + "hey." + ] + } + }, + "hypotheses": [ + { + "skill_name": "dummy_skill", + "annotations": { + "toxic_classification": { + "identity_hate": 0.00024211406707763672, + "insult": 0.0004429817199707031, + "obscene": 0.0001055598258972168, + "severe_toxic": 5.415081977844238e-05, + "sexual_explicit": 9.942054748535156e-05, + "threat": 0.00014013051986694336, + "toxic": 0.0009675323963165283 + }, + "stop_detect": { + "stop": 0.6065886616706848, + "continue": 0.4208565950393677 + }, + "convers_evaluator_annotator": { + "isResponseComprehensible": 0.909, + "isResponseErroneous": 0.808, + "isResponseInteresting": 0.073, + "isResponseOnTopic": 0.158, + "responseEngagesUser": 0.469 + }, + "badlisted_words": { + "inappropriate": false, + "profanity": false, + "restricted_topics": false + } + }, + "text": "I didn't get it. Sorry.", + "confidence": 0.5, + "type": "dummy" + }, + { + "skill_name": "dummy_skill", + "annotations": { + "toxic_classification": { + "identity_hate": 0.00024211406707763672, + "insult": 0.0004429817199707031, + "obscene": 0.0001055598258972168, + "severe_toxic": 5.415081977844238e-05, + "sexual_explicit": 9.942054748535156e-05, + "threat": 0.00014013051986694336, + "toxic": 0.0009675323963165283 + }, + "stop_detect": { + "stop": 0.6065886616706848, + "continue": 0.4208565950393677 + }, + "convers_evaluator_annotator": { + "isResponseComprehensible": 0.909, + "isResponseErroneous": 0.808, + "isResponseInteresting": 0.073, + "isResponseOnTopic": 0.158, + "responseEngagesUser": 0.469 + }, + "badlisted_words": { + "inappropriate": false, + "profanity": false, + "restricted_topics": false + } + }, + "text": "are you a comedy fan?", + "confidence": 0.6, + "type": "topic_question" + }, + { + "skill_name": "movie_tfidf_retrieval", + "annotations": { + "toxic_classification": { + "identity_hate": 0.0001259446144104004, + "insult": 0.00027686357498168945, + "obscene": 5.97834587097168e-05, + "severe_toxic": 3.403425216674805e-05, + "sexual_explicit": 8.13603401184082e-05, + "threat": 0.00012931227684020996, + "toxic": 0.0005629658699035645 + }, + "stop_detect": { + "stop": 0.5833511352539062, + "continue": 0.46003755927085876 + }, + "convers_evaluator_annotator": { + "isResponseComprehensible": 0.281, + "isResponseErroneous": 0.531, + "isResponseInteresting": 0.228, + "isResponseOnTopic": 0.254, + "responseEngagesUser": 0.536 + }, + "badlisted_words": { + "inappropriate": false, + "profanity": false, + "restricted_topics": false + } + }, + "text": "i got you haha. what do you think abuot celebrities? judge judy makes 123, 000 per episode apparently!", + "confidence": 0.38232852805460565 + }, + { + "skill_name": "program_y", + "annotations": { + "toxic_classification": { + "identity_hate": 8.749961853027344e-05, + "insult": 0.00024232268333435059, + "obscene": 2.828240394592285e-05, + "severe_toxic": 1.8358230590820312e-05, + "sexual_explicit": 2.9712915420532227e-05, + "threat": 6.490945816040039e-05, + "toxic": 0.00043845176696777344 + }, + "stop_detect": { + "stop": 0.5808720588684082, + "continue": 0.45234695076942444 + }, + "convers_evaluator_annotator": { + "isResponseComprehensible": 0.984, + "isResponseErroneous": 0.614, + "isResponseInteresting": 0.253, + "isResponseOnTopic": 0.226, + "responseEngagesUser": 0.56 + }, + "badlisted_words": { + "inappropriate": false, + "profanity": false, + "restricted_topics": false + } + }, + "text": "Good Morning, this is an Alexa Prize Socialbot! How are you?", + "confidence": 0.98 + } + ], + "date_time": "2020-03-05 17:07:20.926781", + "attributes": {} + } + ], + "bot_utterances": [], + "human": { + "id": "5e61287da7d26764f5d4a0ed", + "user_telegram_id": "dsadas", + "persona": {}, + "profile": { + "name": null, + "gender": null, + "birthdate": null, + "location": null, + "home_coordinates": null, + "work_coordinates": null, + "occupation": null, + "income_per_year": null + }, + "attributes": {}, + "user_type": "human" + }, + "bot": { + "id": "d3479979f64a4f0eab7804fade8a9fc2", + "persona": {}, + "attributes": {}, + "user_type": "bot" + }, + "channel_type": "http_client", + "date_start": "None", + "date_finish": "None" + } + ] +} From 4f47d7374a16a4f71db4a9c272408ae910c50bf1 Mon Sep 17 00:00:00 2001 From: Maxim Talimanchuk Date: Sat, 12 Aug 2023 20:13:02 +0300 Subject: [PATCH 8/9] fix ru response selector component cards (#553) --- components/hjbdfiugef7h3niknto59u9dgf.yml | 2 +- components/ksDjnfoiwur902hriwnefkwfi2.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/hjbdfiugef7h3niknto59u9dgf.yml b/components/hjbdfiugef7h3niknto59u9dgf.yml index 788917c6a4..5e44ff2c2c 100644 --- a/components/hjbdfiugef7h3niknto59u9dgf.yml +++ b/components/hjbdfiugef7h3niknto59u9dgf.yml @@ -23,5 +23,5 @@ required_previous_services: null state_manager_method: add_bot_utterance tags: null endpoint: respond -service: response_selectors/ranking_and_intent_based_response_selector/service_configs/ranking-and-intent-based-based-response-selector-ru +service: response_selectors/ranking_and_intent_based_response_selector/service_configs/ranking-and-intent-based-response-selector-ru date_created: '2023-03-16T09:45:32' diff --git a/components/ksDjnfoiwur902hriwnefkwfi2.yml b/components/ksDjnfoiwur902hriwnefkwfi2.yml index 276717b9bb..13626f8afe 100644 --- a/components/ksDjnfoiwur902hriwnefkwfi2.yml +++ b/components/ksDjnfoiwur902hriwnefkwfi2.yml @@ -23,5 +23,5 @@ required_previous_services: null state_manager_method: add_bot_utterance tags: null endpoint: respond -service: response_selectors/ranking_and_intent_based_response_selector/service_configs/ranking-and-intent-based-based-response-selector +service: response_selectors/ranking_and_intent_based_response_selector/service_configs/ranking-and-intent-based-response-selector date_created: '2023-03-16T09:45:32' From 7846b28fd8d90e60b18a4706d2438d40d75c9ad4 Mon Sep 17 00:00:00 2001 From: Nika Smilga <42929200+smilni@users.noreply.github.com> Date: Sun, 13 Aug 2023 12:56:42 +0400 Subject: [PATCH 9/9] Feat/refactor dummy skill (#530) * dummy skill refactor * add dummy params * split code into funcs * remove unnecessary func * change func logic * docker-compose update * style * prettier funcs and names * style * add agent cards * add fallback file --- .../dream_alexa/docker-compose.override.yml | 4 + .../docker-compose.override.yml | 5 + .../dream_alexa/environment.yml | 4 + .../service_configs/dream_alexa/service.yml | 4 + .../dream_script_based/environment.yml | 4 + .../dream_script_based/service.yml | 4 + skills/dummy_skill/connector.py | 346 ++++++++++-------- 7 files changed, 213 insertions(+), 158 deletions(-) diff --git a/assistant_dists/dream_alexa/docker-compose.override.yml b/assistant_dists/dream_alexa/docker-compose.override.yml index 27369f483d..df769ff8d5 100644 --- a/assistant_dists/dream_alexa/docker-compose.override.yml +++ b/assistant_dists/dream_alexa/docker-compose.override.yml @@ -24,6 +24,10 @@ services: HIGH_PRIORITY_INTENTS: 1 RESTRICTION_FOR_SENSITIVE_CASE: 1 ALWAYS_TURN_ON_ALL_SKILLS: 0 + ENABLE_NP_QUESTIONS: 1 + ENABLE_SWITCH_TOPIC: 1 + ENABLE_LINK_QUESTIONS: 1 + ENABLE_NP_FACTS: 1 LANGUAGE: EN FALLBACK_FILE: fallbacks_dream_en.json diff --git a/assistant_dists/dream_script_based/docker-compose.override.yml b/assistant_dists/dream_script_based/docker-compose.override.yml index 439eaf0ed0..e86e3238a2 100644 --- a/assistant_dists/dream_script_based/docker-compose.override.yml +++ b/assistant_dists/dream_script_based/docker-compose.override.yml @@ -25,7 +25,12 @@ services: HIGH_PRIORITY_INTENTS: 1 RESTRICTION_FOR_SENSITIVE_CASE: 1 ALWAYS_TURN_ON_ALL_SKILLS: 0 + ENABLE_NP_QUESTIONS: 1 + ENABLE_SWITCH_TOPIC: 1 + ENABLE_LINK_QUESTIONS: 1 + ENABLE_NP_FACTS: 1 LANGUAGE: EN + FALLBACK_FILE: fallbacks_dream_en.json convers-evaluator-annotator: env_file: [ .env ] diff --git a/services/agent_services/service_configs/dream_alexa/environment.yml b/services/agent_services/service_configs/dream_alexa/environment.yml index cf3ef6ea90..cf88d24b9d 100644 --- a/services/agent_services/service_configs/dream_alexa/environment.yml +++ b/services/agent_services/service_configs/dream_alexa/environment.yml @@ -3,5 +3,9 @@ WAIT_HOSTS_TIMEOUT: ${WAIT_TIMEOUT:-480} HIGH_PRIORITY_INTENTS: 1 RESTRICTION_FOR_SENSITIVE_CASE: 1 ALWAYS_TURN_ON_ALL_SKILLS: 0 +ENABLE_NP_QUESTIONS: 1 +ENABLE_SWITCH_TOPIC: 1 +ENABLE_LINK_QUESTIONS: 1 +ENABLE_NP_FACTS: 1 LANGUAGE: EN FALLBACK_FILE: fallbacks_dream_en.json diff --git a/services/agent_services/service_configs/dream_alexa/service.yml b/services/agent_services/service_configs/dream_alexa/service.yml index 84cc9cc932..d94482641d 100644 --- a/services/agent_services/service_configs/dream_alexa/service.yml +++ b/services/agent_services/service_configs/dream_alexa/service.yml @@ -9,6 +9,10 @@ compose: HIGH_PRIORITY_INTENTS: 1 RESTRICTION_FOR_SENSITIVE_CASE: 1 ALWAYS_TURN_ON_ALL_SKILLS: 0 + ENABLE_NP_QUESTIONS: 1 + ENABLE_SWITCH_TOPIC: 1 + ENABLE_LINK_QUESTIONS: 1 + ENABLE_NP_FACTS: 1 LANGUAGE: EN FALLBACK_FILE: fallbacks_dream_en.json volumes: diff --git a/services/agent_services/service_configs/dream_script_based/environment.yml b/services/agent_services/service_configs/dream_script_based/environment.yml index cf3ef6ea90..cf88d24b9d 100644 --- a/services/agent_services/service_configs/dream_script_based/environment.yml +++ b/services/agent_services/service_configs/dream_script_based/environment.yml @@ -3,5 +3,9 @@ WAIT_HOSTS_TIMEOUT: ${WAIT_TIMEOUT:-480} HIGH_PRIORITY_INTENTS: 1 RESTRICTION_FOR_SENSITIVE_CASE: 1 ALWAYS_TURN_ON_ALL_SKILLS: 0 +ENABLE_NP_QUESTIONS: 1 +ENABLE_SWITCH_TOPIC: 1 +ENABLE_LINK_QUESTIONS: 1 +ENABLE_NP_FACTS: 1 LANGUAGE: EN FALLBACK_FILE: fallbacks_dream_en.json diff --git a/services/agent_services/service_configs/dream_script_based/service.yml b/services/agent_services/service_configs/dream_script_based/service.yml index 9f86552deb..c926cf97fd 100644 --- a/services/agent_services/service_configs/dream_script_based/service.yml +++ b/services/agent_services/service_configs/dream_script_based/service.yml @@ -9,6 +9,10 @@ compose: HIGH_PRIORITY_INTENTS: 1 RESTRICTION_FOR_SENSITIVE_CASE: 1 ALWAYS_TURN_ON_ALL_SKILLS: 0 + ENABLE_NP_QUESTIONS: 1 + ENABLE_SWITCH_TOPIC: 1 + ENABLE_LINK_QUESTIONS: 1 + ENABLE_NP_FACTS: 1 LANGUAGE: EN FALLBACK_FILE: fallbacks_dream_en.json volumes: diff --git a/skills/dummy_skill/connector.py b/skills/dummy_skill/connector.py index d4fb560d16..f0dad3edcc 100644 --- a/skills/dummy_skill/connector.py +++ b/skills/dummy_skill/connector.py @@ -30,7 +30,7 @@ if_choose_topic, is_any_question_sentence_in_utterance, ) -from common.utils import get_topics, get_entities, is_no, get_intents, is_yes +from common.utils import get_entities, is_no, get_intents, is_yes logging.basicConfig(format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO) @@ -44,6 +44,10 @@ FALLBACK_FILE = getenv("FALLBACK_FILE", "fallbacks_dream_en.json") DUMMY_DONTKNOW_RESPONSES = json.load(open(f"common/fallbacks/{FALLBACK_FILE}", "r")) LANGUAGE = getenv("LANGUAGE", "EN") +ENABLE_NP_QUESTIONS = int(getenv("ENABLE_NP_QUESTIONS", 0)) +ENABLE_SWITCH_TOPIC = int(getenv("ENABLE_SWITCH_TOPIC", 0)) +ENABLE_LINK_QUESTIONS = int(getenv("ENABLE_LINK_QUESTIONS", 0)) +ENABLE_NP_FACTS = int(getenv("ENABLE_NP_FACTS", 0)) with open("skills/dummy_skill/google-english-no-swears.txt", "r") as f: TOP_FREQUENT_UNIGRAMS = f.read().splitlines()[:1000] @@ -161,16 +165,6 @@ def get_link_to_question(dialog, all_prev_active_skills): return linked_question, human_attr -def generate_question_not_from_last_responses(dialog, all_prev_active_skills): - linked_question, human_attr = get_link_to_question(dialog, all_prev_active_skills) - - if len(linked_question) > 0: - result = linked_question - else: - result = "" - return result, human_attr - - def no_initiative(dialog): utts = dialog["human_utterances"] if len(utts) <= 2: @@ -184,162 +178,198 @@ def no_initiative(dialog): return False +def get_nounphrases(dialog): + curr_nounphrases = get_entities(dialog["human_utterances"][-1], only_named=False, with_labels=False) + for i in range(len(curr_nounphrases)): + np = re.sub(np_remove_expr, "", curr_nounphrases[i]) + np = re.sub(rm_spaces_expr, " ", np) + if re.search(np_ignore_expr, np): + curr_nounphrases[i] = "" + else: + curr_nounphrases[i] = np.strip() + + curr_nounphrases = [np for np in curr_nounphrases if len(np) > 0] + + logger.info(f"Found nounphrases: {curr_nounphrases}") + return curr_nounphrases + + +def get_link_questions(payload, dialog): + all_prev_active_skills = payload["payload"]["all_prev_active_skills"][0] + link_to_question, human_attr = get_link_to_question(dialog, all_prev_active_skills) + return link_to_question, human_attr + + +def get_hyp_np_questions(dialog): + curr_nounphrases = get_nounphrases(dialog) + questions_same_nps = [] + for _, nphrase in enumerate(curr_nounphrases): + for q_id in NP_QUESTIONS.get(nphrase, []): + questions_same_nps += [QUESTIONS_MAP[str(q_id)]] + + if len(questions_same_nps) > 0: + logger.info("Found special nounphrases for questions. Return question with the same nounphrase.") + cands = choice(questions_same_nps) + confs = 0.5 + attrs = {"type": "nounphrase_question", "response_parts": ["prompt"]} + human_attrs = {} + bot_attrs = {} + return cands, confs, attrs, human_attrs, bot_attrs + + return [] + + +def get_hyp_topic_switch(dialog): + last_utt = dialog["human_utterances"][-1] + user = last_utt["user"].get("attributes", {}) + entities = user.get("entities", {}) + entities = {ent: val for ent, val in entities.items() if len(val["human_encounters"])} + response = "" + if entities: + selected_entity = "" + # reverse so it uses recent entities first + sorted_entities = sorted( + entities.values(), + key=lambda d: d["human_encounters"][-1]["human_utterance_index"], + reverse=True, + ) + for entity_dict in sorted_entities: + if entity_dict["human_attitude"] == "like" and not entity_dict["mentioned_by_bot"]: + selected_entity = entity_dict["name"] + break + if selected_entity: + response = f"Previously, you have mentioned {selected_entity}, maybe you want to discuss it?" + logger.info(f"dummy_skill hypothesis no_initiative: {response}") + cands = response + confs = 0.5 + attrs = {"type": "entity_recap", "response_parts": ["prompt"]} + human_attrs = {} + bot_attrs = {} + return cands, confs, attrs, human_attrs, bot_attrs + return [] + + +def get_hyp_link_question(dialog, link_to_question, human_attr): + curr_nounphrases = get_nounphrases(dialog) + _prev_bot_uttr = dialog["bot_utterances"][-2]["text"] if len(dialog["bot_utterances"]) > 1 else "" + _bot_uttr = dialog["bot_utterances"][-1]["text"] if len(dialog["bot_utterances"]) > 0 else "" + _prev_active_skill = dialog["bot_utterances"][-1]["active_skill"] if len(dialog["bot_utterances"]) > 0 else "" + + _no_to_first_linkto = any([phrase in _bot_uttr for phrase in LINK_TO_PHRASES]) + _no_to_first_linkto = _no_to_first_linkto and all([phrase not in _prev_bot_uttr for phrase in LINK_TO_PHRASES]) + _no_to_first_linkto = _no_to_first_linkto and is_no(dialog["human_utterances"][-1]) + _no_to_first_linkto = _no_to_first_linkto and _prev_active_skill != "dff_friendship_skill" + + _if_switch_topic = is_switch_topic(dialog["human_utterances"][-1]) + bot_uttr_dict = dialog["bot_utterances"][-1] if len(dialog["bot_utterances"]) > 0 else {} + _if_choose_topic = if_choose_topic(dialog["human_utterances"][-1], bot_uttr_dict) + _is_ask_me_something = ASK_ME_QUESTION_PATTERN.search(dialog["human_utterances"][-1]["text"]) + + if len(dialog["human_utterances"]) > 1: + _was_cant_do = "cant_do" in get_intents(dialog["human_utterances"][-2]) and ( + len(curr_nounphrases) == 0 or is_yes(dialog["human_utterances"][-1]) + ) + _was_cant_do_stop_it = "cant_do" in get_intents(dialog["human_utterances"][-2]) and is_no( + dialog["human_utterances"][-1] + ) + else: + _was_cant_do = False + _was_cant_do_stop_it = False + + if _was_cant_do_stop_it: + link_to_question = "Sorry, bye! #+#exit" + confs = 1.0 # finish dialog request + elif _no_to_first_linkto: + confs = 0.99 + elif _is_ask_me_something or _if_switch_topic or _was_cant_do or _if_choose_topic: + confs = 1.0 # Use it only as response selector retrieve skill output modifier + else: + confs = 0.05 # Use it only as response selector retrieve skill output modifier + cands = link_to_question + attrs = {"type": "link_to_for_response_selector", "response_parts": ["prompt"]} + human_attrs = human_attr + bot_attrs = {} + return cands, confs, attrs, human_attrs, bot_attrs + + +def get_hyp_russ_link_question(): + cands = random.choice(RUSSIAN_RANDOM_QUESTIONS) + confs = 0.8 + attrs = {"type": "link_to_for_response_selector", "response_parts": ["prompt"]} + human_attrs = {} + bot_attrs = {} + return cands, confs, attrs, human_attrs, bot_attrs + + +def get_hyp_np_facts(dialog): + curr_nounphrases = get_nounphrases(dialog) + facts_same_nps = [] + for _, nphrase in enumerate(curr_nounphrases): + for fact_id in NP_FACTS.get(nphrase, []): + facts_same_nps += [ + f"Well, now that you've mentioned {nphrase}, I've remembered this. " + f"{FACTS_MAP[str(fact_id)]}. " + f"{(opinion_request_question() if random.random() < ASK_QUESTION_PROB else '')}" + ] + + if len(facts_same_nps) > 0: + logger.info("Found special nounphrases for facts. Return fact with the same nounphrase.") + cands = choice(facts_same_nps) + confs = 0.5 + attrs = {"type": "nounphrase_fact", "response_parts": ["body"]} + human_attrs = {} + bot_attrs = {} + return cands, confs, attrs, human_attrs, bot_attrs + return [] + + +def add_hypothesis(hyps_with_attrs, new_hyp_with_attrs): + if new_hyp_with_attrs: + cand, conf, attr, human_attr, bot_attr = new_hyp_with_attrs + cands, confs, attrs, human_attrs, bot_attrs = hyps_with_attrs + cands.append(cand) + confs.append(conf) + attrs.append(attr) + human_attrs.append(human_attr) + bot_attrs.append(bot_attr) + + class DummySkillConnector: async def send(self, payload: Dict, callback: Callable): try: st_time = time.time() dialog = deepcopy(payload["payload"]["dialogs"][0]) is_sensitive_case = is_sensitive_situation(dialog["human_utterances"][-1]) - all_prev_active_skills = payload["payload"]["all_prev_active_skills"][0] - - curr_topics = get_topics(dialog["human_utterances"][-1], which="cobot_topics") - curr_nounphrases = get_entities(dialog["human_utterances"][-1], only_named=False, with_labels=False) - - if len(curr_topics) == 0: - curr_topics = ["Phatic"] - logger.info(f"Found topics: {curr_topics}") - for i in range(len(curr_nounphrases)): - np = re.sub(np_remove_expr, "", curr_nounphrases[i]) - np = re.sub(rm_spaces_expr, " ", np) - if re.search(np_ignore_expr, np): - curr_nounphrases[i] = "" - else: - curr_nounphrases[i] = np.strip() - - curr_nounphrases = [np for np in curr_nounphrases if len(np) > 0] - - logger.info(f"Found nounphrases: {curr_nounphrases}") - - cands = [] - confs = [] - human_attrs = [] - bot_attrs = [] - attrs = [] - - cands += [choice(DUMMY_DONTKNOW_RESPONSES)] - confs += [0.5] - attrs += [{"type": "dummy"}] - human_attrs += [{}] - bot_attrs += [{}] - - if len(dialog["utterances"]) > 14 and not is_sensitive_case and LANGUAGE == "EN": - questions_same_nps = [] - for i, nphrase in enumerate(curr_nounphrases): - for q_id in NP_QUESTIONS.get(nphrase, []): - questions_same_nps += [QUESTIONS_MAP[str(q_id)]] - - if len(questions_same_nps) > 0: - logger.info("Found special nounphrases for questions. Return question with the same nounphrase.") - cands += [choice(questions_same_nps)] - confs += [0.5] - attrs += [{"type": "nounphrase_question", "response_parts": ["prompt"]}] - human_attrs += [{}] - bot_attrs += [{}] - - link_to_question, human_attr = get_link_to_question(dialog, all_prev_active_skills) - - if no_initiative(dialog) and LANGUAGE == "EN": - last_utt = dialog["human_utterances"][-1] - user = last_utt["user"].get("attributes", {}) - entities = user.get("entities", {}) - entities = {ent: val for ent, val in entities.items() if len(val["human_encounters"])} - response = "" - if entities: - selected_entity = "" - # reverse so it uses recent entities first - sorted_entities = sorted( - entities.values(), - key=lambda d: d["human_encounters"][-1]["human_utterance_index"], - reverse=True, - ) - for entity_dict in sorted_entities: - if entity_dict["human_attitude"] == "like" and not entity_dict["mentioned_by_bot"]: - selected_entity = entity_dict["name"] - break - if selected_entity: - response = f"Previously, you have mentioned {selected_entity}, maybe you want to discuss it?" - logger.info(f"dummy_skill hypothesis no_initiative: {response}") - cands += [response] - confs += [0.5] - attrs += [{"type": "entity_recap", "response_parts": ["prompt"]}] - human_attrs += [{}] - bot_attrs += [{}] - - if link_to_question and LANGUAGE == "EN": - _prev_bot_uttr = dialog["bot_utterances"][-2]["text"] if len(dialog["bot_utterances"]) > 1 else "" - _bot_uttr = dialog["bot_utterances"][-1]["text"] if len(dialog["bot_utterances"]) > 0 else "" - _prev_active_skill = ( - dialog["bot_utterances"][-1]["active_skill"] if len(dialog["bot_utterances"]) > 0 else "" - ) - - _no_to_first_linkto = any([phrase in _bot_uttr for phrase in LINK_TO_PHRASES]) - _no_to_first_linkto = _no_to_first_linkto and all( - [phrase not in _prev_bot_uttr for phrase in LINK_TO_PHRASES] - ) - _no_to_first_linkto = _no_to_first_linkto and is_no(dialog["human_utterances"][-1]) - _no_to_first_linkto = _no_to_first_linkto and _prev_active_skill != "dff_friendship_skill" - - _if_switch_topic = is_switch_topic(dialog["human_utterances"][-1]) - bot_uttr_dict = dialog["bot_utterances"][-1] if len(dialog["bot_utterances"]) > 0 else {} - _if_choose_topic = if_choose_topic(dialog["human_utterances"][-1], bot_uttr_dict) - _is_ask_me_something = ASK_ME_QUESTION_PATTERN.search(dialog["human_utterances"][-1]["text"]) - - if len(dialog["human_utterances"]) > 1: - _was_cant_do = "cant_do" in get_intents(dialog["human_utterances"][-2]) and ( - len(curr_nounphrases) == 0 or is_yes(dialog["human_utterances"][-1]) - ) - _was_cant_do_stop_it = "cant_do" in get_intents(dialog["human_utterances"][-2]) and is_no( - dialog["human_utterances"][-1] - ) - else: - _was_cant_do = False - _was_cant_do_stop_it = False - - if _was_cant_do_stop_it: - link_to_question = "Sorry, bye! #+#exit" - confs += [1.0] # finish dialog request - elif _no_to_first_linkto: - confs += [0.99] - elif _is_ask_me_something or _if_switch_topic or _was_cant_do or _if_choose_topic: - confs += [1.0] # Use it only as response selector retrieve skill output modifier - else: - confs += [0.05] # Use it only as response selector retrieve skill output modifier - cands += [link_to_question] - attrs += [{"type": "link_to_for_response_selector", "response_parts": ["prompt"]}] - human_attrs += [human_attr] - bot_attrs += [{}] - elif LANGUAGE == "RU": - cands += [random.choice(RUSSIAN_RANDOM_QUESTIONS)] - confs += [0.8] - attrs += [{"type": "link_to_for_response_selector", "response_parts": ["prompt"]}] - human_attrs += [{}] - bot_attrs += [{}] - - if LANGUAGE == "EN": - facts_same_nps = [] - for i, nphrase in enumerate(curr_nounphrases): - for fact_id in NP_FACTS.get(nphrase, []): - facts_same_nps += [ - f"Well, now that you've mentioned {nphrase}, I've remembered this. " - f"{FACTS_MAP[str(fact_id)]}. " - f"{(opinion_request_question() if random.random() < ASK_QUESTION_PROB else '')}" - ] - else: - facts_same_nps = [] - - if len(facts_same_nps) > 0 and not is_sensitive_case and LANGUAGE == "EN": - logger.info("Found special nounphrases for facts. Return fact with the same nounphrase.") - cands += [choice(facts_same_nps)] - confs += [0.5] - attrs += [{"type": "nounphrase_fact", "response_parts": ["body"]}] - human_attrs += [{}] - bot_attrs += [{}] + is_no_initiative = no_initiative(dialog) + is_long_dialog = len(dialog["utterances"]) > 14 + + hyps_with_attrs = [[choice(DUMMY_DONTKNOW_RESPONSES)], [0.5], [{"type": "dummy"}], [{}], [{}]] + # always append at least basic dummy response + + if ENABLE_NP_QUESTIONS and is_long_dialog and not is_sensitive_case and LANGUAGE == "EN": + new_hyp_with_attrs = get_hyp_np_questions(dialog) + add_hypothesis(hyps_with_attrs, new_hyp_with_attrs) + + if ENABLE_SWITCH_TOPIC and is_no_initiative and LANGUAGE == "EN": + new_hyp_with_attrs = get_hyp_topic_switch(dialog) + add_hypothesis(hyps_with_attrs, new_hyp_with_attrs) + + if ENABLE_LINK_QUESTIONS: + link_to_question, human_attr_q = get_link_questions(payload, dialog) + if link_to_question and LANGUAGE == "EN": + new_hyp_with_attrs = get_hyp_link_question(dialog, link_to_question, human_attr_q) + add_hypothesis(hyps_with_attrs, new_hyp_with_attrs) + elif LANGUAGE == "RU": + new_hyp_with_attrs = get_hyp_russ_link_question() + add_hypothesis(hyps_with_attrs, new_hyp_with_attrs) + + if ENABLE_NP_FACTS and not is_sensitive_case and LANGUAGE == "EN": + new_hyp_with_attrs = get_hyp_np_facts(dialog) + add_hypothesis(hyps_with_attrs, new_hyp_with_attrs) total_time = time.time() - st_time logger.info(f"dummy_skill exec time: {total_time:.3f}s") - asyncio.create_task( - callback(task_id=payload["task_id"], response=[cands, confs, human_attrs, bot_attrs, attrs]) - ) + asyncio.create_task(callback(task_id=payload["task_id"], response=hyps_with_attrs)) except Exception as e: logger.exception(e) sentry_sdk.capture_exception(e)