From bd7ec428d7f7b15a15d70f1747bd3d9420ede5aa Mon Sep 17 00:00:00 2001 From: vishnuszipstack <117254672+vishnuszipstack@users.noreply.github.com> Date: Mon, 23 Dec 2024 09:22:59 +0530 Subject: [PATCH] Feat/prompt key required field (#885) * added required field in prompt key * required fields constant and added required field in promptstudio helper * added frontend required field prompt key functionality * added prompt service required key adding in metadata * added required key in prompt studio registry and removed some unwanted codes * changed required field bool to option with any/all * made changes in frontend to support option required * prompt service changes to support required options field * required field based on enforce type * changed required choices to class * frontend condition fail issue fix in highlight data * required field text changes as per the feedback * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * addressed pr comments * added info for required fields --------- Signed-off-by: vishnuszipstack <117254672+vishnuszipstack@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../prompt_studio_core_v2/constants.py | 1 + .../prompt_studio_helper.py | 1 + .../prompt_studio_registry_v2/constants.py | 1 + .../prompt_studio_registry_helper.py | 1 + .../0003_toolstudioprompt_required.py | 18 ++++++ .../0004_alter_toolstudioprompt_required.py | 24 ++++++++ .../0005_alter_toolstudioprompt_required.py | 24 ++++++++ .../prompt_studio/prompt_studio_v2/models.py | 19 +++++- .../custom-tools/pdf-viewer/PdfViewer.jsx | 4 +- .../custom-tools/prompt-card/Header.jsx | 61 ++++++++++++++++++- .../custom-tools/prompt-card/PromptCard.css | 4 ++ .../prompt-card/PromptCardItems.jsx | 1 + .../src/unstract/prompt_service/constants.py | 2 + .../src/unstract/prompt_service/helper.py | 2 - .../src/unstract/prompt_service/main.py | 6 +- 15 files changed, 162 insertions(+), 7 deletions(-) create mode 100644 backend/prompt_studio/prompt_studio_v2/migrations/0003_toolstudioprompt_required.py create mode 100644 backend/prompt_studio/prompt_studio_v2/migrations/0004_alter_toolstudioprompt_required.py create mode 100644 backend/prompt_studio/prompt_studio_v2/migrations/0005_alter_toolstudioprompt_required.py diff --git a/backend/prompt_studio/prompt_studio_core_v2/constants.py b/backend/prompt_studio/prompt_studio_core_v2/constants.py index 2c6a80ac6..559fe4f7c 100644 --- a/backend/prompt_studio/prompt_studio_core_v2/constants.py +++ b/backend/prompt_studio/prompt_studio_core_v2/constants.py @@ -96,6 +96,7 @@ class ToolStudioPromptKeys: RECORD = "record" FILE_PATH = "file_path" ENABLE_HIGHLIGHT = "enable_highlight" + REQUIRED = "required" EXECUTION_SOURCE = "execution_source" diff --git a/backend/prompt_studio/prompt_studio_core_v2/prompt_studio_helper.py b/backend/prompt_studio/prompt_studio_core_v2/prompt_studio_helper.py index 7985173a1..f3f9e8972 100644 --- a/backend/prompt_studio/prompt_studio_core_v2/prompt_studio_helper.py +++ b/backend/prompt_studio/prompt_studio_core_v2/prompt_studio_helper.py @@ -819,6 +819,7 @@ def _fetch_response( output[TSPKeys.PROMPT] = prompt.prompt output[TSPKeys.ACTIVE] = prompt.active + output[TSPKeys.REQUIRED] = prompt.required output[TSPKeys.CHUNK_SIZE] = profile_manager.chunk_size output[TSPKeys.VECTOR_DB] = vector_db output[TSPKeys.EMBEDDING] = embedding_model diff --git a/backend/prompt_studio/prompt_studio_registry_v2/constants.py b/backend/prompt_studio/prompt_studio_registry_v2/constants.py index ab00a9f2d..49bc1de04 100644 --- a/backend/prompt_studio/prompt_studio_registry_v2/constants.py +++ b/backend/prompt_studio/prompt_studio_registry_v2/constants.py @@ -98,6 +98,7 @@ class JsonSchemaKey: SUMMARIZE_AS_SOURCE = "summarize_as_source" ENABLE_HIGHLIGHT = "enable_highlight" PLATFORM_POSTAMBLE = "platform_postamble" + REQUIRED = "required" class SpecKey: diff --git a/backend/prompt_studio/prompt_studio_registry_v2/prompt_studio_registry_helper.py b/backend/prompt_studio/prompt_studio_registry_v2/prompt_studio_registry_helper.py index 157593cdd..8590c1b03 100644 --- a/backend/prompt_studio/prompt_studio_registry_v2/prompt_studio_registry_helper.py +++ b/backend/prompt_studio/prompt_studio_registry_v2/prompt_studio_registry_helper.py @@ -322,6 +322,7 @@ def frame_export_json( output[JsonSchemaKey.PROMPT] = prompt.prompt output[JsonSchemaKey.ACTIVE] = prompt.active + output[JsonSchemaKey.REQUIRED] = prompt.required output[JsonSchemaKey.CHUNK_SIZE] = prompt.profile_manager.chunk_size output[JsonSchemaKey.VECTOR_DB] = vector_db output[JsonSchemaKey.EMBEDDING] = embedding_model diff --git a/backend/prompt_studio/prompt_studio_v2/migrations/0003_toolstudioprompt_required.py b/backend/prompt_studio/prompt_studio_v2/migrations/0003_toolstudioprompt_required.py new file mode 100644 index 000000000..69a1d0eaf --- /dev/null +++ b/backend/prompt_studio/prompt_studio_v2/migrations/0003_toolstudioprompt_required.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.1 on 2024-12-10 10:07 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("prompt_studio_v2", "0002_alter_toolstudioprompt_enforce_type"), + ] + + operations = [ + migrations.AddField( + model_name="toolstudioprompt", + name="required", + field=models.BooleanField(default=False), + ), + ] diff --git a/backend/prompt_studio/prompt_studio_v2/migrations/0004_alter_toolstudioprompt_required.py b/backend/prompt_studio/prompt_studio_v2/migrations/0004_alter_toolstudioprompt_required.py new file mode 100644 index 000000000..58dba3d26 --- /dev/null +++ b/backend/prompt_studio/prompt_studio_v2/migrations/0004_alter_toolstudioprompt_required.py @@ -0,0 +1,24 @@ +# Generated by Django 4.2.1 on 2024-12-12 08:28 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("prompt_studio_v2", "0003_toolstudioprompt_required"), + ] + + operations = [ + migrations.AlterField( + model_name="toolstudioprompt", + name="required", + field=models.CharField( + blank=True, + choices=[("all", "All values required"), ("any", "Any value required")], + default=None, + max_length=3, + null=True, + ), + ), + ] diff --git a/backend/prompt_studio/prompt_studio_v2/migrations/0005_alter_toolstudioprompt_required.py b/backend/prompt_studio/prompt_studio_v2/migrations/0005_alter_toolstudioprompt_required.py new file mode 100644 index 000000000..bdd843050 --- /dev/null +++ b/backend/prompt_studio/prompt_studio_v2/migrations/0005_alter_toolstudioprompt_required.py @@ -0,0 +1,24 @@ +# Generated by Django 4.2.1 on 2024-12-20 06:35 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("prompt_studio_v2", "0004_alter_toolstudioprompt_required"), + ] + + operations = [ + migrations.AlterField( + model_name="toolstudioprompt", + name="required", + field=models.CharField( + blank=True, + choices=[("all", "All values required"), ("any", "Any value required")], + db_comment="Field to store weather the values all values or any values required. This is used for HQR, based on the value approve or finish review", + default=None, + null=True, + ), + ), + ] diff --git a/backend/prompt_studio/prompt_studio_v2/models.py b/backend/prompt_studio/prompt_studio_v2/models.py index 9cd37c36f..ccef50ac5 100644 --- a/backend/prompt_studio/prompt_studio_v2/models.py +++ b/backend/prompt_studio/prompt_studio_v2/models.py @@ -35,7 +35,15 @@ class PromptType(models.TextChoices): class Mode(models.TextChoices): DEFAULT = "Default", "Default choice for output" - prompt_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + class RequiredType(models.TextChoices): + ALL = "all", "All values required" + ANY = "any", "Any value required" + + prompt_id = models.UUIDField( + primary_key=True, + default=uuid.uuid4, + editable=False, + ) prompt_key = models.TextField( blank=False, db_comment="Field to store the prompt key", @@ -84,6 +92,15 @@ class Mode(models.TextChoices): db_comment="Field to store the prompt key", unique=False, ) + required = models.CharField( + choices=RequiredType.choices, + null=True, # Allows the field to store NULL in the database + blank=True, # Allows the field to be optional in forms + default=None, # Sets the default value to None + db_comment="Field to store weather the values all values or any \ + values required. This is used for HQR, based on the value approve or finish \ + review", + ) is_assert = models.BooleanField(default=False) active = models.BooleanField(default=True, null=False, blank=False) output_metadata = models.JSONField( diff --git a/frontend/src/components/custom-tools/pdf-viewer/PdfViewer.jsx b/frontend/src/components/custom-tools/pdf-viewer/PdfViewer.jsx index 765e468b7..9103184f6 100644 --- a/frontend/src/components/custom-tools/pdf-viewer/PdfViewer.jsx +++ b/frontend/src/components/custom-tools/pdf-viewer/PdfViewer.jsx @@ -21,7 +21,7 @@ function PdfViewer({ fileUrl, highlightData }) { const { jumpToPage } = pageNavigationPluginInstance; const parentRef = useRef(null); function removeZerosAndDeleteIfAllZero(highlightData) { - return highlightData.filter((innerArray) => + return highlightData?.filter((innerArray) => innerArray.some((value) => value !== 0) ); // Keep arrays that contain at least one non-zero value } @@ -36,8 +36,8 @@ function PdfViewer({ fileUrl, highlightData }) { // Jump to page when highlightData changes useEffect(() => { + highlightData = removeZerosAndDeleteIfAllZero(highlightData); // Removing zeros before checking the highlight data condition if (highlightData && highlightData.length > 0) { - highlightData = removeZerosAndDeleteIfAllZero(highlightData); const pageNumber = highlightData[0][0]; // Assume highlightData[0][0] is the page number if (pageNumber !== null && jumpToPage) { setTimeout(() => { diff --git a/frontend/src/components/custom-tools/prompt-card/Header.jsx b/frontend/src/components/custom-tools/prompt-card/Header.jsx index f72a36083..98b0fd2c1 100644 --- a/frontend/src/components/custom-tools/prompt-card/Header.jsx +++ b/frontend/src/components/custom-tools/prompt-card/Header.jsx @@ -6,6 +6,7 @@ import { PlayCircleFilled, PlayCircleOutlined, SyncOutlined, + InfoCircleOutlined, } from "@ant-design/icons"; import { useEffect, useState } from "react"; import { Button, Checkbox, Col, Dropdown, Row, Tag, Tooltip } from "antd"; @@ -45,6 +46,7 @@ function Header({ setExpandCard, spsLoading, handleSpsLoading, + enforceType, }) { const { selectedDoc, @@ -58,6 +60,7 @@ function Header({ const [items, setItems] = useState([]); const [isDisablePrompt, setIsDisablePrompt] = useState(null); + const [required, setRequired] = useState(false); const handleRunBtnClick = (promptRunType, docId = null) => { setExpandCard(true); @@ -73,8 +76,22 @@ function Header({ } ); }; + const handleRequiredChange = (value) => { + const newValue = value === required ? null : value; // Allow deselection + setRequired(newValue); + handleChange( + newValue, + promptDetails?.prompt_id, + "required", + true, + true + ).catch(() => { + setRequired(promptDetails?.required || null); // Rollback state in case of error + }); + }; useEffect(() => { setIsDisablePrompt(promptDetails?.active); + setRequired(promptDetails?.required); }, [promptDetails, details]); useEffect(() => { @@ -87,6 +104,47 @@ function Header({ ), key: "enable", }, + { + label: ( +
+ {["json", "table", "record"].indexOf(enforceType) === -1 && ( + handleRequiredChange("all")} + > + Value Required{" "} + + + + + )} + {enforceType === "json" && ( + <> + handleRequiredChange("all")} + > + All JSON Values Required + + + + + handleRequiredChange("any")} + className="required-checkbox-padding" + > + Atleast 1 JSON Value Required + + + + + + )} +
+ ), + key: "required", + }, { label: ( @@ -270,6 +328,7 @@ Header.propTypes = { setExpandCard: PropTypes.func.isRequired, spsLoading: PropTypes.object, handleSpsLoading: PropTypes.func.isRequired, + enforceType: PropTypes.text, }; export { Header }; diff --git a/frontend/src/components/custom-tools/prompt-card/PromptCard.css b/frontend/src/components/custom-tools/prompt-card/PromptCard.css index f217c417f..cb8752feb 100644 --- a/frontend/src/components/custom-tools/prompt-card/PromptCard.css +++ b/frontend/src/components/custom-tools/prompt-card/PromptCard.css @@ -299,3 +299,7 @@ .info-circle-colored { color: #f0ad4e; } + +.required-checkbox-padding{ + padding-left: 5px; +} diff --git a/frontend/src/components/custom-tools/prompt-card/PromptCardItems.jsx b/frontend/src/components/custom-tools/prompt-card/PromptCardItems.jsx index a404a8089..5fd33d053 100644 --- a/frontend/src/components/custom-tools/prompt-card/PromptCardItems.jsx +++ b/frontend/src/components/custom-tools/prompt-card/PromptCardItems.jsx @@ -180,6 +180,7 @@ function PromptCardItems({ enabledProfiles={enabledProfiles} spsLoading={spsLoading} handleSpsLoading={handleSpsLoading} + enforceType={enforceType} /> diff --git a/prompt-service/src/unstract/prompt_service/constants.py b/prompt-service/src/unstract/prompt_service/constants.py index 4f30dd11d..8bdaa280b 100644 --- a/prompt-service/src/unstract/prompt_service/constants.py +++ b/prompt-service/src/unstract/prompt_service/constants.py @@ -72,6 +72,8 @@ class PromptServiceContants: FILE_PATH = "file_path" HIGHLIGHT_DATA = "highlight_data" CONFIDENCE_DATA = "confidence_data" + REQUIRED_FIELDS = "required_fields" + REQUIRED = "required" EXECUTION_SOURCE = "execution_source" METRICS = "metrics" diff --git a/prompt-service/src/unstract/prompt_service/helper.py b/prompt-service/src/unstract/prompt_service/helper.py index 51a2d93bf..a45443d03 100644 --- a/prompt-service/src/unstract/prompt_service/helper.py +++ b/prompt-service/src/unstract/prompt_service/helper.py @@ -328,7 +328,6 @@ def run_completion( answer: str = completion[PSKeys.RESPONSE].text highlight_data = completion.get(PSKeys.HIGHLIGHT_DATA) confidence_data = completion.get(PSKeys.CONFIDENCE_DATA) - if metadata is not None and prompt_key: if highlight_data: metadata.setdefault(PSKeys.HIGHLIGHT_DATA, {})[ @@ -339,7 +338,6 @@ def run_completion( metadata.setdefault(PSKeys.CONFIDENCE_DATA, {})[ prompt_key ] = confidence_data - return answer # TODO: Catch and handle specific exception here except SdkRateLimitError as e: diff --git a/prompt-service/src/unstract/prompt_service/main.py b/prompt-service/src/unstract/prompt_service/main.py index 7dbe6dca2..7942ff292 100644 --- a/prompt-service/src/unstract/prompt_service/main.py +++ b/prompt-service/src/unstract/prompt_service/main.py @@ -108,6 +108,7 @@ def prompt_processor() -> Any: PSKeys.RUN_ID: run_id, PSKeys.FILE_NAME: doc_name, PSKeys.CONTEXT: {}, + PSKeys.REQUIRED_FIELDS: {}, } metrics: dict = {} variable_names: list[str] = [] @@ -120,10 +121,13 @@ def prompt_processor() -> Any: RunLevel.RUN, f"Preparing to execute {len(prompts)} prompt(s)", ) - # TODO: Rename "output" to "prompt" for output in prompts: # type:ignore variable_names.append(output[PSKeys.NAME]) + metadata[PSKeys.REQUIRED_FIELDS][output[PSKeys.NAME]] = output.get( + PSKeys.REQUIRED, None + ) + for output in prompts: # type:ignore prompt_name = output[PSKeys.NAME] prompt_text = output[PSKeys.PROMPT]