diff --git a/config/settings/common.py b/config/settings/common.py index 82bbe70fe..96cbd6a24 100644 --- a/config/settings/common.py +++ b/config/settings/common.py @@ -514,3 +514,11 @@ OPENAI_API_ENGINE_4 = env.str("OPENAI_API_ENGINE_4", "") OPENAI_API_ENGINE_4_MAX_TOKENS = env.int("OPENAI_API_ENGINE_4_MAX_TOKENS", 8000) OPENAI_API_TEMPERATURE = env.float("OPENAI_API_TEMPERATURE", 0.0) +OPENAI_API_ENGINE_EMBEDDINGS = env.str("OPENAI_API_ENGINE_EMBEDDINGS", "") +OPENAI_API_ENGINE_EMBEDDINGS_MAX_TOKENS = env.int( + "OPENAI_API_ENGINE_EMBEDDINGS_MAX_TOKENS", 8000 +) +LLM_VECTOR_DATA_STORE_ENV = env( + "LLM_VECTOR_DATA_STORE", default="llm_vector_data_store" +) +LLM_VECTOR_DATA_STORE = os.path.join(MEDIA_ROOT, LLM_VECTOR_DATA_STORE_ENV) diff --git a/config/settings/local.py b/config/settings/local.py index 8d43e8495..f7c8c02f9 100644 --- a/config/settings/local.py +++ b/config/settings/local.py @@ -68,3 +68,4 @@ MEDIA_ROOT_ENV = env("MEDIA_ROOT_ENV", default="media_dev") MEDIA_ROOT = str(APPS_DIR(MEDIA_ROOT_ENV)) SENDFILE_ROOT = MEDIA_ROOT +LLM_VECTOR_DATA_STORE = os.path.join(MEDIA_ROOT, LLM_VECTOR_DATA_STORE_ENV) diff --git a/docker-compose.yml b/docker-compose.yml index 84e80d423..44134c8f7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -65,11 +65,13 @@ services: EMAILLABS_SECRET_KEY: ${EMAILLABS_SECRET_KEY} OPENAI_API_KEY: ${OPENAI_API_KEY} AZURE_ENDPOINT: ${OPENAI_API_BASE} + OPENAI_API_ENGINE_EMBEDDINGS: "text-embedding-ada-002" OPENAI_API_ENGINE_35: "gpt-35-turbo-16k" OPENAI_API_ENGINE_4: "gpt-4" OPENAI_API_TYPE: "azure" OPENAI_API_ENGINE_35_MAX_TOKENS: 16000 OPENAI_API_ENGINE_4_MAX_TOKENS: 8000 + OPENAI_API_ENGINE_EMBEDDINGS_MAX_TOKENS: 8000 OPENAI_API_TEMPERATURE: 0.0 OPENAI_API_VERSION: "2023-07-01-preview" ports: @@ -112,7 +114,7 @@ services: # SENTRY_ENVIRONMENT: "feder-dev" # COMPRESS_EML: "True" # env_file: - # - secrets_imap-to-webhook-url.env:optional + # - secrets_imap-to-webhook-url.env volumes: mysql-data: diff --git a/feder/llm_evaluation/llm_tools.py b/feder/llm_evaluation/llm_tools.py index afe2c4759..b3a4d4ddb 100644 --- a/feder/llm_evaluation/llm_tools.py +++ b/feder/llm_evaluation/llm_tools.py @@ -1,12 +1,14 @@ import json import logging +import os import time import tiktoken from django.conf import settings from langchain.schema.output_parser import StrOutputParser from langchain_community.callbacks import get_openai_callback -from langchain_openai import AzureChatOpenAI +from langchain_community.vectorstores.chroma import Chroma +from langchain_openai import AzureChatOpenAI, AzureOpenAIEmbeddings logger = logging.getLogger(__name__) @@ -24,7 +26,7 @@ def get_llm_response(prompt, prompt_kwargs_dict): openai_api_key=settings.OPENAI_API_KEY, openai_api_version=settings.OPENAI_API_VERSION, azure_endpoint=settings.AZURE_ENDPOINT, - deployment_name=settings.OPENAI_API_DEPLOYMENT_NAME, + deployment_name=settings.OPENAI_API_ENGINE_35, temperature=settings.OPENAI_API_TEMPERATURE, ) chain = prompt | model | StrOutputParser() @@ -46,3 +48,30 @@ def serializable_dict(obj): def get_serializable_dict(obj): return {k: v for k, v in vars(obj).items() if serializable_dict(v)} + + +def create_vectordb_data_for_monitoring_chat(monitoring): + if not os.path.exists(settings.LLM_VECTOR_DATA_STORE): + os.makedirs(settings.LLM_VECTOR_DATA_STORE) + texts = monitoring.responses_chat_context["chat_context_texts"] + embeddings = AzureOpenAIEmbeddings( + azure_endpoint=settings.AZURE_ENDPOINT, + deployment=settings.OPENAI_API_ENGINE_EMBEDDINGS, + openai_api_type=settings.OPENAI_API_TYPE, + openai_api_key=settings.OPENAI_API_KEY, + openai_api_version=settings.OPENAI_API_VERSION, + ) + vectordb = Chroma( + persist_directory=settings.LLM_VECTOR_DATA_STORE, + collection_name=monitoring.slug, + ) + embedding_ids = vectordb.get().get("ids") + if embedding_ids: + vectordb.delete(embedding_ids) + vectordb.from_texts( + texts, + embedding=embeddings, + collection_name=monitoring.slug, + persist_directory=settings.LLM_VECTOR_DATA_STORE, + ) + vectordb.persist() diff --git a/feder/llm_evaluation/models.py b/feder/llm_evaluation/models.py index 770229ede..efa9d6fc9 100644 --- a/feder/llm_evaluation/models.py +++ b/feder/llm_evaluation/models.py @@ -8,9 +8,11 @@ from django.utils import timezone from django.utils.translation import gettext_lazy as _ from jsonfield import JSONField +from langchain.chains import load_summarize_chain from langchain.schema.output_parser import StrOutputParser from langchain.text_splitter import TokenTextSplitter from langchain_community.callbacks import get_openai_callback +from langchain_core.documents import Document from langchain_openai import AzureChatOpenAI from model_utils import Choices from model_utils.models import TimeStampedModel @@ -23,6 +25,8 @@ letter_categorization, letter_evaluation_intro, letter_response_normalization, + monitoring_chat_prompt_template, + monitoring_chat_refine_template, monitoring_response_normalized_template, ) diff --git a/feder/llm_evaluation/prompts.py b/feder/llm_evaluation/prompts.py index 944f3afc5..0e11ac95a 100644 --- a/feder/llm_evaluation/prompts.py +++ b/feder/llm_evaluation/prompts.py @@ -132,24 +132,65 @@ template_format="f-string", ) -answer_categorization = PromptTemplate.from_template( +monitoring_chat_prompt_template = PromptTemplate.from_template( """ - Oceń odpowiedź z {institution} udzieloną do pytanie, przypisując ją do jednej z - kategorii z listy poniżej wybierając odpowiednią literę wraz z opisem, nie dodając - żadnych innych komentarzy. Przy ocenie weź pod uwagę tylko zrozumiały tekst w - języku Polskim. - Pytanie zadane {institution}: + Skorzystaj z poniższych fragmentów kontekstu, aby odpowiedzieć na pytanie na końcu. + Jeśli nie znasz odpowiedzi, po prostu powiedz, że nie wiesz, nie próbuj wymyślać + odpowiedzi. Kontekst zawiera zebrane odpowiedzi w formacie JSON na ankietę + wysłaną do instytucji. Struktura JSONa z odpowiedziami jest następująca: ``` - {question} - ```. - Odpowiedź udzielona na powyższe pytanie przez {institution}: + {{ + {{ Nazwa_instytucji_1: {{ + "1": {{"Pytanie":"treść pytania 1", "Odpowiedź":"treść odpowiedzi 1"}}, + "2": {{"Pytanie":"treść pytania 2", "Odpowiedź":"treść odpowiedzi 2"}} + }}, + {{ Nazwa_instytucji_2: {{ + "1": {{"Pytanie":"treść pytania 1", "Odpowiedź":"treść odpowiedzi 1"}}, + "2": {{"Pytanie":"treść pytania 2", "Odpowiedź":"treść odpowiedzi 2"}} + }} + }} ``` - {answer} - ```. - Lista kategorii odpowiedzi: + Jeśli instytucja nie udzieliła odpowiedzi to JSON dla tej instytucji będzie + zawierał pusty obiekt: {{}}. Udziel odpowiedzi po polsku. + + Kontekst: + --- + {text} + --- + Pytanie: {question} + """, + template_format="f-string", +) + +monitoring_chat_refine_template = PromptTemplate.from_template( + """ + Uzupełnij odpowiedź na pytanie z poprzedniego zadania, korzystając z poniższych + fragmentów kontekstu, aby otrzymać wyczerpującą odpowiedzieć na pytanie na końcu. + Jeśli nie znasz odpowiedzi, po prostu powiedz, że nie wiesz, nie próbuj wymyślać + odpowiedzi. Kontekst zawiera zebrane odpowiedzi w formacie JSON na ankietę + wysłaną do instytucji. Struktura JSONa z odpowiedziami jest następująca: ``` - {answer_categories} + {{ + {{ Nazwa_instytucji_1: {{ + "1": {{"Pytanie":"treść pytania 1", "Odpowiedź":"treść odpowiedzi 1"}}, + "2": {{"Pytanie":"treść pytania 2", "Odpowiedź":"treść odpowiedzi 2"}} + }}, + {{ Nazwa_instytucji_2: {{ + "1": {{"Pytanie":"treść pytania 1", "Odpowiedź":"treść odpowiedzi 1"}}, + "2": {{"Pytanie":"treść pytania 2", "Odpowiedź":"treść odpowiedzi 2"}} + }} + }} ``` + Jeśli instytucja nie udzieliła odpowiedzi to JSON dla tej instytucji będzie + zawierał pusty obiekt: {{}}. Udziel odpowiedzi po polsku. + + Odpowiedź z poprzedniego zadania: {existing_answer} + Kontekst: + --- + {text} + --- + Pytanie: {question} + """, template_format="f-string", ) diff --git a/feder/monitorings/locale/pl/LC_MESSAGES/django.po b/feder/monitorings/locale/pl/LC_MESSAGES/django.po index 1d3c1858c..4dcddb13a 100644 --- a/feder/monitorings/locale/pl/LC_MESSAGES/django.po +++ b/feder/monitorings/locale/pl/LC_MESSAGES/django.po @@ -459,6 +459,10 @@ msgid "Drafts" msgstr "Projekty" #: feder/monitorings/templates/monitorings/_tabs.html:36 +#: feder/monitorings/templates/monitorings/monitoring_chat.html:19 +msgid "Chat" +msgstr "" + #: feder/monitorings/templates/monitorings/monitoring_answers_categories.html:60 msgid "Answers categories" msgstr "Kategorie odpowiedzi" @@ -601,6 +605,18 @@ msgstr "Kwarantanna? (kol. Kwar.?)" msgid "Filter area and tags" msgstr "Filtruj obszar i tagi" +#: feder/monitorings/templates/monitorings/monitoring_chat.html:12 +#, fuzzy +#| msgid "Monitoring Cases" +msgid "Monitoring Chat" +msgstr "Sprawy w Monitoringu" + +#: feder/monitorings/templates/monitorings/monitoring_chat.html:14 +#, fuzzy +#| msgid "Monitoring Cases" +msgid "Monitoring Chat subject" +msgstr "Sprawy w Monitoringu" + #: feder/monitorings/templates/monitorings/monitoring_confirm_delete.html:9 msgid "Confirm delete" msgstr "Potwierdz usuwanie" diff --git a/feder/monitorings/models.py b/feder/monitorings/models.py index 84fa1e88c..56954976d 100644 --- a/feder/monitorings/models.py +++ b/feder/monitorings/models.py @@ -1,6 +1,8 @@ import json +import logging from itertools import groupby +import pytz import reversion from autoslug.fields import AutoSlugField from django.conf import settings @@ -14,7 +16,10 @@ from model_utils.models import TimeStampedModel from feder.domains.models import Domain -from feder.llm_evaluation.prompts import EMAIL_IS_ANSWER, answer_categorization +from feder.llm_evaluation.llm_tools import ( + create_vectordb_data_for_monitoring_chat, + num_tokens_from_string, +) from feder.main.utils import ( FormattedDatetimeMixin, RenderBooleanFieldMixin, @@ -24,6 +29,8 @@ from .validators import validate_nested_lists, validate_template_syntax +logger = logging.getLogger(__name__) + _("Monitorings index") _("Can add Monitoring") _("Can change Monitoring") diff --git a/feder/monitorings/templates/monitorings/_tabs.html b/feder/monitorings/templates/monitorings/_tabs.html index 471eafde3..ab4abffff 100644 --- a/feder/monitorings/templates/monitorings/_tabs.html +++ b/feder/monitorings/templates/monitorings/_tabs.html @@ -12,11 +12,11 @@ {% endif %} >{% trans 'Letters' %} {% if 'reply' in monitoring_perms or 'add_draft' in monitoring_perms %} -