From 4872fd471c220d9f8cac823f182dcb639ccc5a88 Mon Sep 17 00:00:00 2001 From: Tahier Hussain Date: Tue, 27 Feb 2024 03:56:49 +0530 Subject: [PATCH 01/14] Added /extract and /summarize path --- backend/file_management/file_management_helper.py | 4 +++- backend/file_management/views.py | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/file_management/file_management_helper.py b/backend/file_management/file_management_helper.py index a5808f9a5..c4c0160f2 100644 --- a/backend/file_management/file_management_helper.py +++ b/backend/file_management/file_management_helper.py @@ -195,7 +195,7 @@ def fetch_file_contents( elif file_content_type == "text/plain": with fs.open(file_path, "r") as file: FileManagerHelper.logger.info(f"Reading text file: {file_path}") - encoded_string = base64.b64encode(file.read()) + encoded_string = file.read() return encoded_string else: raise InvalidFileType @@ -249,6 +249,8 @@ def handle_sub_directory_for_tenants( if is_create: try: os.makedirs(file_path, exist_ok=True) + os.makedirs(f"{file_path}/extract", exist_ok=True) + os.makedirs(f"{file_path}/summarize", exist_ok=True) except OSError as e: FileManagerHelper.logger.error( f"Error while creating {file_path}: {e}" diff --git a/backend/file_management/views.py b/backend/file_management/views.py index a8756595f..06bb74977 100644 --- a/backend/file_management/views.py +++ b/backend/file_management/views.py @@ -121,7 +121,6 @@ def upload(self, request: HttpRequest) -> Response: @action(detail=True, methods=["post"]) def upload_for_ide(self, request: HttpRequest) -> Response: - print(request.data) serializer = FileUploadIdeSerializer(data=request.data) serializer.is_valid(raise_exception=True) uploaded_files: Any = serializer.validated_data.get("file") From fe3b2059916eb8705675fc1d78cac4f6cba82f04 Mon Sep 17 00:00:00 2001 From: Tahier Hussain Date: Tue, 27 Feb 2024 04:10:30 +0530 Subject: [PATCH 02/14] Handled the logic to toggle between extract and summarize --- .../prompt_studio_core/constants.py | 2 + .../prompt_studio_core/models.py | 14 +++++ .../prompt_studio_helper.py | 51 ++++++++++--------- 3 files changed, 43 insertions(+), 24 deletions(-) diff --git a/backend/prompt_studio/prompt_studio_core/constants.py b/backend/prompt_studio/prompt_studio_core/constants.py index df5be97e9..a848828af 100644 --- a/backend/prompt_studio/prompt_studio_core/constants.py +++ b/backend/prompt_studio/prompt_studio_core/constants.py @@ -73,6 +73,8 @@ class ToolStudioPromptKeys: EVAL_SETTINGS_EVALUATE = "evaluate" EVAL_SETTINGS_MONITOR_LLM = "monitor_llm" EVAL_SETTINGS_EXCLUDE_FAILED = "exclude_failed" + SUMMARIZE = "summarize" + SUMMARIZED_RESULT = "summarized_result" class LogLevels: diff --git a/backend/prompt_studio/prompt_studio_core/models.py b/backend/prompt_studio/prompt_studio_core/models.py index 4b8128461..4a2327e7e 100644 --- a/backend/prompt_studio/prompt_studio_core/models.py +++ b/backend/prompt_studio/prompt_studio_core/models.py @@ -45,6 +45,20 @@ class CustomTool(BaseModel): null=True, blank=True, ) + summarize_llm_profile = models.ForeignKey( + ProfileManager, + on_delete=models.SET_NULL, + related_name="summarize_llm_profile", + null=True, + blank=True, + ) + summarize_context = models.BooleanField(default=True) + summarize_as_source = models.BooleanField(default=True) + summarize_prompt = models.TextField( + blank=True, + db_comment="Field to store the summarize prompt", + unique=False, + ) prompt_grammer = models.JSONField( null=True, blank=True, db_comment="Synonymous words used in prompt" ) diff --git a/backend/prompt_studio/prompt_studio_core/prompt_studio_helper.py b/backend/prompt_studio/prompt_studio_core/prompt_studio_helper.py index a2a4165a1..191618cb5 100644 --- a/backend/prompt_studio/prompt_studio_core/prompt_studio_helper.py +++ b/backend/prompt_studio/prompt_studio_core/prompt_studio_helper.py @@ -2,7 +2,7 @@ import logging import os from pathlib import Path -from typing import Any, Optional +from typing import Any from django.conf import settings from file_management.file_management_helper import FileManagerHelper @@ -16,7 +16,6 @@ AnswerFetchError, DefaultProfileError, IndexingError, - OutputSaveError, PromptNotValid, ToolNotValid, ) @@ -82,7 +81,7 @@ def fetch_prompt_from_tool(tool_id: str) -> list[ToolStudioPrompt]: @staticmethod def index_document( - tool_id: str, file_name: str, org_id: str, user_id: str + tool_id: str, file_name: str, org_id: str, user_id: str, is_summary: bool = False ) -> Any: """Method to index a document. @@ -95,16 +94,19 @@ def index_document( IndexingError """ tool: CustomTool = CustomTool.objects.get(pk=tool_id) - default_profile: Optional[ProfileManager] = tool.default_profile + + if is_summary: + default_profile: ProfileManager = tool.summarize_llm_profile + file_path = file_name + else: + default_profile: ProfileManager = tool.default_profile + file_path = FileManagerHelper.handle_sub_directory_for_tenants( + org_id, is_create=False, user_id=user_id, tool_id=tool_id + ) + file_path = str(Path(file_path) / file_name) + if not default_profile: raise DefaultProfileError() - file_path = FileManagerHelper.handle_sub_directory_for_tenants( - org_id=org_id, - user_id=user_id, - tool_id=tool_id, - is_create=False, - ) - file_path = str(Path(file_path) / file_name) stream_log.publish( tool_id, stream_log.log( @@ -134,6 +136,7 @@ def index_document( tool_id=tool_id, file_name=file_path, org_id=org_id, + is_summary=is_summary, ) logger.info(f"Indexing done sucessfully for {file_name}") stream_log.publish( @@ -182,6 +185,16 @@ def prompt_responder( prompts: list[ToolStudioPrompt] = [] prompts.append(prompt_instance) tool: CustomTool = prompt_instance.tool_id + + + if tool.summarize_as_source: + directory, filename = os.path.split(file_path) + file_path: str = os.path.join( + directory, + TSPKeys.SUMMARIZE, + os.path.splitext(filename)[0] + ".txt", + ) + stream_log.publish( tool.tool_id, stream_log.log( @@ -277,6 +290,7 @@ def _fetch_response( file_name=path, tool_id=str(tool.tool_id), org_id=org_id, + is_summary=tool.summarize_as_source ) output: dict[str, Any] = {} @@ -348,19 +362,6 @@ def _fetch_response( if answer["status"] == "ERROR": raise AnswerFetchError() output_response = json.loads(answer["structure_output"]) - - # persist output - try: - for key in output_response: - if TSPKeys.EVAL_RESULT_DELIM not in key: - persisted_prompt = ToolStudioPrompt.objects.get( - prompt_key=key - ) - persisted_prompt.output = output_response[key] or "" - persisted_prompt.save() - except Exception as e: - logger.error(f"Exception while saving prompt output: {e}") - raise OutputSaveError() return output_response @staticmethod @@ -369,6 +370,7 @@ def dynamic_indexer( tool_id: str, file_name: str, org_id: str, + is_summary: bool = False, ) -> str: try: util = PromptIdeBaseTool(log_level=LogLevel.INFO, org_id=org_id) @@ -391,5 +393,6 @@ def dynamic_indexer( chunk_size=profile_manager.chunk_size, chunk_overlap=profile_manager.chunk_overlap, reindex=profile_manager.reindex, + is_summary=is_summary, ) ) From 498b42ad2ec829bb73fba832dc2e1a851fc08e0f Mon Sep 17 00:00:00 2001 From: Tahier Hussain Date: Tue, 27 Feb 2024 04:11:55 +0530 Subject: [PATCH 03/14] Persist the evaluation data inside the output manager --- .../prompt_studio/prompt_studio_output_manager/models.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/backend/prompt_studio/prompt_studio_output_manager/models.py b/backend/prompt_studio/prompt_studio_output_manager/models.py index 3392bd5a2..1e12d7cd2 100644 --- a/backend/prompt_studio/prompt_studio_output_manager/models.py +++ b/backend/prompt_studio/prompt_studio_output_manager/models.py @@ -26,7 +26,13 @@ class PromptStudioOutputManager(BaseModel): db_comment="Field to store the document name", editable=True, ) - + eval_metrics = models.JSONField( + db_column="eval_metrics", + null=False, + blank=False, + default=list, + db_comment="Field to store the evaluation metrics", + ) tool_id = models.ForeignKey( CustomTool, on_delete=models.SET_NULL, From f71c7912319c77f3563e37ea12ca9dbfee08b7aa Mon Sep 17 00:00:00 2001 From: Tahier Hussain Date: Tue, 27 Feb 2024 04:24:41 +0530 Subject: [PATCH 04/14] Handled summarize view, raw view, persistence of eval data, and output analyzer in the UI --- .../combined-output/CombinedOutput.jsx | 35 ++- .../document-manager/DocumentManager.css | 9 +- .../document-manager/DocumentManager.jsx | 231 +++++++++++++++--- .../document-parser/DocumentParser.jsx | 13 +- .../document-viewer/DocumentViewer.jsx | 46 ++++ .../editable-text/EditableText.jsx | 16 +- .../components/custom-tools/header/Header.jsx | 69 ++++-- .../manage-docs-modal/ManageDocsModal.jsx | 28 ++- .../custom-tools/notes-card/NotesCard.jsx | 36 ++- .../OutputAnalyzerCard.jsx | 111 +++++++++ .../OutputAnalyzerHeader.jsx | 32 +++ .../OutputAnalyzerList.css | 71 ++++++ .../OutputAnalyzerList.jsx | 38 +++ .../OutputForDocModal.jsx | 12 +- .../custom-tools/pdf-viewer/PdfViewer.jsx | 80 +----- .../custom-tools/prompt-card/PromptCard.jsx | 215 ++++++++-------- .../SelectLlmProfileModal.jsx | 159 ++++++++++++ .../custom-tools/text-viewer/TextViewer.jsx | 24 ++ .../custom-tools/tool-ide/ToolIde.jsx | 50 ++-- .../custom-tools/tools-main/ToolsMain.jsx | 3 +- .../helpers/custom-tools/CustomToolsHelper.js | 14 +- frontend/src/helpers/GetStaticData.js | 24 ++ frontend/src/index.css | 20 +- frontend/src/pages/OutputAnalyzerPage.jsx | 7 + frontend/src/routes/Router.jsx | 5 + frontend/src/store/custom-tool-store.js | 1 - 26 files changed, 1045 insertions(+), 304 deletions(-) create mode 100644 frontend/src/components/custom-tools/document-viewer/DocumentViewer.jsx create mode 100644 frontend/src/components/custom-tools/output-analyzer-card/OutputAnalyzerCard.jsx create mode 100644 frontend/src/components/custom-tools/output-analyzer-header/OutputAnalyzerHeader.jsx create mode 100644 frontend/src/components/custom-tools/output-analyzer-list/OutputAnalyzerList.css create mode 100644 frontend/src/components/custom-tools/output-analyzer-list/OutputAnalyzerList.jsx create mode 100644 frontend/src/components/custom-tools/select-llm-profile-modal/SelectLlmProfileModal.jsx create mode 100644 frontend/src/components/custom-tools/text-viewer/TextViewer.jsx create mode 100644 frontend/src/pages/OutputAnalyzerPage.jsx diff --git a/frontend/src/components/custom-tools/combined-output/CombinedOutput.jsx b/frontend/src/components/custom-tools/combined-output/CombinedOutput.jsx index da3db4600..2835b8aa2 100644 --- a/frontend/src/components/custom-tools/combined-output/CombinedOutput.jsx +++ b/frontend/src/components/custom-tools/combined-output/CombinedOutput.jsx @@ -1,4 +1,4 @@ -import { Button, Segmented, Space } from "antd"; +import { Segmented } from "antd"; import jsYaml from "js-yaml"; import Prism from "prismjs"; import "prismjs/components/prism-json"; @@ -6,6 +6,7 @@ import "prismjs/plugins/line-numbers/prism-line-numbers.css"; import "prismjs/plugins/line-numbers/prism-line-numbers.js"; import "prismjs/themes/prism.css"; import { useEffect, useState } from "react"; +import PropTypes from "prop-types"; import { handleException, promptType } from "../../../helpers/GetStaticData"; import { useAxiosPrivate } from "../../../hooks/useAxiosPrivate"; @@ -20,27 +21,31 @@ const outputTypes = { yaml: "YAML", }; -function CombinedOutput() { +function CombinedOutput({ doc, setFilledFields }) { const [combinedOutput, setCombinedOutput] = useState({}); const [yamlData, setYamlData] = useState(null); const [selectedOutputType, setSelectedOutputType] = useState( outputTypes.json ); const [isOutputLoading, setIsOutputLoading] = useState(false); - const { details, selectedDoc } = useCustomToolStore(); + const { details } = useCustomToolStore(); const { sessionDetails } = useSessionStore(); const { setAlertDetails } = useAlertStore(); const axiosPrivate = useAxiosPrivate(); useEffect(() => { - if (!selectedDoc) { + if (!doc) { return; } + let filledFields = 0; setIsOutputLoading(true); handleOutputApiRequest() .then((res) => { const data = res?.data || []; + data.sort((a, b) => { + return new Date(b.modified_at) - new Date(a.modified_at); + }); const prompts = details?.prompts; const output = {}; prompts.forEach((item) => { @@ -60,9 +65,17 @@ function CombinedOutput() { } output[item?.prompt_key] = outputDetails?.output || ""; + + if (outputDetails?.output?.length > 0) { + filledFields++; + } }); setCombinedOutput(output); + if (setFilledFields) { + setFilledFields(filledFields); + } + const yamlDump = jsYaml.dump(output); setYamlData(yamlDump); }) @@ -74,7 +87,7 @@ function CombinedOutput() { .finally(() => { setIsOutputLoading(false); }); - }, [selectedDoc]); + }, [doc]); useEffect(() => { Prism.highlightAll(); @@ -87,7 +100,7 @@ function CombinedOutput() { const handleOutputApiRequest = async () => { const requestOptions = { method: "GET", - url: `/api/v1/unstract/${sessionDetails?.orgId}/prompt-studio/prompt-output/?tool_id=${details?.tool_id}&doc_name=${selectedDoc}`, + url: `/api/v1/unstract/${sessionDetails?.orgId}/prompt-studio/prompt-output/?tool_id=${details?.tool_id}&doc_name=${doc}`, headers: { "X-CSRFToken": sessionDetails?.csrfToken, }, @@ -107,11 +120,6 @@ function CombinedOutput() { return (
- -
- -
-
{ + const fileNameTxt = removeFileExtension(selectedDoc); + const files = [ + { + fileName: selectedDoc, + viewType: viewTypes.pdf, + }, + { + fileName: `extract/${fileNameTxt}.txt`, + viewType: viewTypes.extract, + }, + { + fileName: `summarize/${fileNameTxt}.txt`, + viewType: viewTypes.summarize, + }, + ]; + + setFileUrl(""); + setExtractTxt(""); + setSummaryTxt(""); + files.forEach((item) => { + handleFetchContent(item); + }); + }, [selectedDoc]); + + const handleFetchContent = (fileDetails) => { + if (!selectedDoc) { + setFileUrl(""); + setExtractTxt(""); + setSummaryTxt(""); + return; + } + + const requestOptions = { + method: "GET", + url: `/api/v1/unstract/${sessionDetails?.orgId}/prompt-studio/file/fetch_contents?file_name=${fileDetails?.fileName}&tool_id=${details?.tool_id}`, + }; + + handleLoadingStateUpdate(fileDetails?.viewType, true); + axiosPrivate(requestOptions) + .then((res) => { + const data = res?.data?.data; + if (fileDetails?.viewType === viewTypes.pdf) { + const base64String = data || ""; + const blob = base64toBlob(base64String); + setFileUrl(URL.createObjectURL(blob)); + return; + } + + if (fileDetails?.viewType === viewTypes?.extract) { + setExtractTxt(data); + } + + if (fileDetails?.viewType === viewTypes.summarize) { + setSummaryTxt(data); + } + }) + .catch((err) => {}) + .finally(() => { + handleLoadingStateUpdate(fileDetails?.viewType, false); + }); + }; + + const handleLoadingStateUpdate = (viewType, value) => { + if (viewType === viewTypes.pdf) { + setIsDocLoading(value); + } + + if (viewType === viewTypes.extract) { + setIsExtractLoading(value); + } + + if (viewType === viewTypes.summarize) { + setIsSummaryLoading(value); + } + }; + + const handleActiveKeyChange = (key) => { + setActiveKey(key); + }; useEffect(() => { const index = [...listOfDocs].findIndex((item) => item === selectedDoc); @@ -49,42 +168,82 @@ function DocumentManager({ generateIndex, handleUpdateTool, handleDocChange }) { return (
-
- -
-
- - +
+
+ +
+ + + +
+
+ + +
+
- + {activeKey === "1" && ( + 0} + setOpenManageDocsModal={setOpenManageDocsModal} + > + + + )} + {activeKey === "2" && ( + 0} + setOpenManageDocsModal={setOpenManageDocsModal} + > + + + )} + {activeKey === "3" && ( + 0} + setOpenManageDocsModal={setOpenManageDocsModal} + > + + + )} { const promptsAndNotes = details?.prompts || []; let name = ""; @@ -93,21 +94,23 @@ function DocumentParser({ promptId, promptStudioUpdateStatus.isUpdating ); - axiosPrivate(requestOptions) + + return axiosPrivate(requestOptions) .then((res) => { const data = res?.data; const modifiedDetails = { ...details }; const modifiedPrompts = [...(modifiedDetails?.prompts || [])].map( (item) => { if (item?.prompt_id === data?.prompt_id) { - data.evalMetrics = item?.evalMetrics || []; return data; } return item; } ); modifiedDetails["prompts"] = modifiedPrompts; - updateCustomTool({ details: modifiedDetails }); + if (!isPromptUpdate) { + updateCustomTool({ details: modifiedDetails }); + } handleUpdateStatus( isUpdateStatus, promptId, diff --git a/frontend/src/components/custom-tools/document-viewer/DocumentViewer.jsx b/frontend/src/components/custom-tools/document-viewer/DocumentViewer.jsx new file mode 100644 index 000000000..aabd40c63 --- /dev/null +++ b/frontend/src/components/custom-tools/document-viewer/DocumentViewer.jsx @@ -0,0 +1,46 @@ +import PropTypes from "prop-types"; +import { SpinnerLoader } from "../../widgets/spinner-loader/SpinnerLoader"; +import { EmptyState } from "../../widgets/empty-state/EmptyState"; +import { Typography } from "antd"; + +function DocumentViewer({ + children, + doc, + isLoading, + isContentAvailable, + setOpenManageDocsModal, +}) { + if (isLoading) { + return ; + } + + if (!doc) { + return ( + setOpenManageDocsModal(true)} + /> + ); + } + + if (!isContentAvailable) { + return ( +
+ Failed to load the document +
+ ); + } + + return <>{children}; +} + +DocumentViewer.propTypes = { + children: PropTypes.any.isRequired, + doc: PropTypes.string, + isLoading: PropTypes.bool.isRequired, + isContentAvailable: PropTypes.bool.isRequired, + setOpenManageDocsModal: PropTypes.func, +}; + +export { DocumentViewer }; diff --git a/frontend/src/components/custom-tools/editable-text/EditableText.jsx b/frontend/src/components/custom-tools/editable-text/EditableText.jsx index ac5b14b00..6ba422c94 100644 --- a/frontend/src/components/custom-tools/editable-text/EditableText.jsx +++ b/frontend/src/components/custom-tools/editable-text/EditableText.jsx @@ -15,6 +15,8 @@ function EditableText({ isTextarea, }) { const [text, setText] = useState(""); + const name = isTextarea ? "prompt" : "prompt_key"; + const [triggerHandleChange, setTriggerHandleChange] = useState(false); const [isHovered, setIsHovered] = useState(false); const divRef = useRef(null); const { disableLlmOrDocChange } = useCustomToolStore(); @@ -45,11 +47,19 @@ function EditableText({ const onSearchDebounce = useCallback( debounce((event) => { - handleChange(event, promptId, false, true); + setTriggerHandleChange(true); }, 1000), [] ); + useEffect(() => { + if (!triggerHandleChange) { + return; + } + handleChange(text, promptId, name, true, true); + setTriggerHandleChange(false); + }, [triggerHandleChange]); + const handleClickOutside = (event) => { if (divRef.current && !divRef.current.contains(event.target)) { // Clicked outside the div @@ -64,7 +74,7 @@ function EditableText({ value={text} onChange={handleTextChange} placeholder="Enter Prompt" - name="prompt" + name={name} size="small" style={{ backgroundColor: "transparent" }} variant={`${!isEditing && !isHovered ? "borderless" : "outlined"}`} @@ -84,7 +94,7 @@ function EditableText({ value={text} onChange={handleTextChange} placeholder="Enter Key" - name="prompt_key" + name={name} size="small" style={{ backgroundColor: "transparent" }} variant={`${!isEditing && !isHovered ? "borderless" : "outlined"}`} diff --git a/frontend/src/components/custom-tools/header/Header.jsx b/frontend/src/components/custom-tools/header/Header.jsx index 498512cd8..355e7de10 100644 --- a/frontend/src/components/custom-tools/header/Header.jsx +++ b/frontend/src/components/custom-tools/header/Header.jsx @@ -4,13 +4,12 @@ import { DiffOutlined, EditOutlined, ExportOutlined, - FilePdfOutlined, FileTextOutlined, MessageOutlined, } from "@ant-design/icons"; import { Button, Tooltip, Typography } from "antd"; import PropTypes from "prop-types"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; import "./Header.css"; @@ -21,26 +20,34 @@ import { useCustomToolStore } from "../../../store/custom-tool-store"; import { useSessionStore } from "../../../store/session-store"; import { CustomButton } from "../../widgets/custom-button/CustomButton"; import { PreAndPostAmbleModal } from "../pre-and-post-amble-modal/PreAndPostAmbleModal"; +import { SelectLlmProfileModal } from "../select-llm-profile-modal/SelectLlmProfileModal"; function Header({ setOpenCusSynonymsModal, - setOpenManageDocsModal, setOpenManageLlmModal, handleUpdateTool, }) { const [openPreOrPostAmbleModal, setOpenPreOrPostAmbleModal] = useState(false); + const [openSummLlmProfileModal, setOpenSummLlmProfileModal] = useState(false); const [preOrPostAmble, setPreOrPostAmble] = useState(""); const [isExportLoading, setIsExportLoading] = useState(false); - const { details } = useCustomToolStore(); + const [summarizeLlmBtnText, setSummarizeLlmBtnText] = useState(null); + const [llmItems, setLlmItems] = useState([]); + const { details, llmProfiles } = useCustomToolStore(); const { sessionDetails } = useSessionStore(); const { setAlertDetails } = useAlertStore(); const axiosPrivate = useAxiosPrivate(); const navigate = useNavigate(); + useEffect(() => { + getLlmProfilesDropdown(); + }, []); + const handleOpenPreOrPostAmbleModal = (type) => { setOpenPreOrPostAmbleModal(true); setPreOrPostAmble(type); }; + const handleClosePreOrPostAmbleModal = () => { setOpenPreOrPostAmbleModal(false); setPreOrPostAmble(""); @@ -68,6 +75,16 @@ function Header({ }); }; + const getLlmProfilesDropdown = () => { + const items = [...llmProfiles].map((item) => { + return { + value: item?.profile_id, + label: item?.profile_name, + }; + }); + setLlmItems(items); + }; + return (
@@ -89,25 +106,31 @@ function Header({
- - - +
- - - +
- - - +
@@ -143,13 +166,19 @@ function Header({ type={preOrPostAmble} handleUpdateTool={handleUpdateTool} /> +
); } Header.propTypes = { setOpenCusSynonymsModal: PropTypes.func.isRequired, - setOpenManageDocsModal: PropTypes.func.isRequired, setOpenManageLlmModal: PropTypes.func.isRequired, handleUpdateTool: PropTypes.func.isRequired, }; diff --git a/frontend/src/components/custom-tools/manage-docs-modal/ManageDocsModal.jsx b/frontend/src/components/custom-tools/manage-docs-modal/ManageDocsModal.jsx index 7630be445..87e97a980 100644 --- a/frontend/src/components/custom-tools/manage-docs-modal/ManageDocsModal.jsx +++ b/frontend/src/components/custom-tools/manage-docs-modal/ManageDocsModal.jsx @@ -94,7 +94,27 @@ function ManageDocsModal({ setRows(newRows); }, [listOfDocs, selectedDoc, disableLlmOrDocChange]); - const handleUploadChange = (info) => { + const beforeUpload = (file) => { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.readAsDataURL(file); + reader.onload = () => { + const fileName = file.name; + const fileAlreadyExists = [...listOfDocs].includes(fileName); + if (!fileAlreadyExists) { + resolve(file); + } else { + setAlertDetails({ + type: "error", + content: "File name already exists", + }); + reject(new Error("File name already exists")); + } + }; + }); + }; + + const handleUploadChange = async (info) => { if (info.file.status === "uploading") { setIsUploading(true); } @@ -109,14 +129,14 @@ function ManageDocsModal({ const docName = info?.file?.name; const newListOfDocs = [...listOfDocs]; newListOfDocs.push(docName); + setOpen(false); + await generateIndex(info?.file?.name); const body = { selectedDoc: docName, listOfDocs: newListOfDocs, }; updateCustomTool(body); handleUpdateTool({ output: docName }); - setOpen(false); - generateIndex(info?.file?.name); } else if (info.file.status === "error") { setIsUploading(false); setAlertDetails({ @@ -175,6 +195,8 @@ function ManageDocsModal({ onChange={handleUploadChange} disabled={isUploading || !defaultLlmProfile} showUploadList={false} + accept=".pdf" + beforeUpload={beforeUpload} > + {updateStatus?.promptId === details?.prompt_id && ( + <> + {updateStatus?.status === + promptStudioUpdateStatus.isUpdating && ( + } + color="processing" + className="display-flex-align-center" + > + Updating + + )} + {updateStatus?.status === promptStudioUpdateStatus.done && ( + } + color="success" + className="display-flex-align-center" + > + Done + + )} + + )} + + Output Viewer + + +
+
+
+ ); +} + +export { OutputAnalyzerHeader }; diff --git a/frontend/src/components/custom-tools/output-analyzer-list/OutputAnalyzerList.css b/frontend/src/components/custom-tools/output-analyzer-list/OutputAnalyzerList.css new file mode 100644 index 000000000..358230c07 --- /dev/null +++ b/frontend/src/components/custom-tools/output-analyzer-list/OutputAnalyzerList.css @@ -0,0 +1,71 @@ +/* Styles for OutputAnalyzerList */ + +.output-analyzer-layout { + height: 100%; + display: flex; + flex-direction: column; + background-color: var(--page-bg-2); + overflow-y: hidden; +} + +.output-analyzer-header { + padding: 8px; + background-color: #F5F7F9; + display: flex; + justify-content: space-between; + align-items: center; +} + +.output-analyzer-header .title { + font-size: 16px; +} + +.output-analyzer-body { + padding: 12px; + height: 100%; + overflow-y: auto; +} + +.output-analyzer-body2 { + height: 700px; + display: flex; + flex-direction: column; +} + +.output-analyzer-card-head { + padding: 12px; + display: flex; + justify-content: space-between; + background-color: #ECEFF3; +} + +.output-analyzer-main { + flex: 1; + overflow-y: hidden; +} + +.output-analyzer-left-box { + padding-right: 6px; + height: 100%; + overflow-y: auto; +} + +.output-analyzer-left-box > div { + height: 100%; + background-color: var(--white); +} + +.output-analyzer-right-box { + padding-left: 6px; + height: 100%; +} + +.output-analyzer-right-box > div { + height: 100%; + background-color: var(--white); + padding: 0px 12px; +} + +.output-analyzer-card-gap { + margin-bottom: 12px; +} \ No newline at end of file diff --git a/frontend/src/components/custom-tools/output-analyzer-list/OutputAnalyzerList.jsx b/frontend/src/components/custom-tools/output-analyzer-list/OutputAnalyzerList.jsx new file mode 100644 index 000000000..7b8b3d392 --- /dev/null +++ b/frontend/src/components/custom-tools/output-analyzer-list/OutputAnalyzerList.jsx @@ -0,0 +1,38 @@ +import { OutputAnalyzerHeader } from "../output-analyzer-header/OutputAnalyzerHeader"; +import "./OutputAnalyzerList.css"; +import { OutputAnalyzerCard } from "../output-analyzer-card/OutputAnalyzerCard"; +import { useCustomToolStore } from "../../../store/custom-tool-store"; +import { useEffect, useState } from "react"; +import { promptType } from "../../../helpers/GetStaticData"; + +function OutputAnalyzerList() { + const [totalFields, setTotalFields] = useState(0); + const { listOfDocs, details } = useCustomToolStore(); + + useEffect(() => { + const prompts = [...(details?.prompts || [])]; + const promptsFiltered = prompts.filter( + (item) => item?.prompt_type === promptType.prompt + ); + setTotalFields(promptsFiltered.length || 0); + }, [details]); + + return ( +
+
+ +
+
+ {listOfDocs.map((doc) => { + return ( +
+ +
+ ); + })} +
+
+ ); +} + +export { OutputAnalyzerList }; diff --git a/frontend/src/components/custom-tools/output-for-doc-modal/OutputForDocModal.jsx b/frontend/src/components/custom-tools/output-for-doc-modal/OutputForDocModal.jsx index 1bfb89d5d..f2b57e22a 100644 --- a/frontend/src/components/custom-tools/output-for-doc-modal/OutputForDocModal.jsx +++ b/frontend/src/components/custom-tools/output-for-doc-modal/OutputForDocModal.jsx @@ -7,6 +7,7 @@ import { useSessionStore } from "../../../store/session-store"; import { useAxiosPrivate } from "../../../hooks/useAxiosPrivate"; import "./OutputForDocModal.css"; import { CheckCircleFilled, CloseCircleFilled } from "@ant-design/icons"; +import { useNavigate } from "react-router-dom"; const columns = [ { @@ -32,6 +33,8 @@ function OutputForDocModal({ const { details, listOfDocs } = useCustomToolStore(); const { sessionDetails } = useSessionStore(); const axiosPrivate = useAxiosPrivate(); + const navigate = useNavigate(); + useEffect(() => { handleGetOutputForDocs(); }, [open]); @@ -53,7 +56,7 @@ function OutputForDocModal({ .then((res) => { const data = res?.data || []; data.sort((a, b) => { - return new Date(b.created_at) - new Date(a.created_at); + return new Date(b.modified_at) - new Date(a.modified_at); }); handleRowsGeneration(data); }) @@ -67,7 +70,6 @@ function OutputForDocModal({ [...listOfDocs].forEach((item) => { const output = data.find((outputValue) => outputValue?.doc_name === item); const isSuccess = output?.output?.length > 0; - const content = isSuccess ? output?.output : "Failed"; const result = { key: item, @@ -81,7 +83,7 @@ function OutputForDocModal({ )} {" "} - {content} + {isSuccess ? output?.output : "Failed"} ), }; @@ -108,7 +110,9 @@ function OutputForDocModal({
- +
diff --git a/frontend/src/components/custom-tools/pdf-viewer/PdfViewer.jsx b/frontend/src/components/custom-tools/pdf-viewer/PdfViewer.jsx index 5fd9f9201..4ee872189 100644 --- a/frontend/src/components/custom-tools/pdf-viewer/PdfViewer.jsx +++ b/frontend/src/components/custom-tools/pdf-viewer/PdfViewer.jsx @@ -1,87 +1,11 @@ import { Viewer, Worker } from "@react-pdf-viewer/core"; import { defaultLayoutPlugin } from "@react-pdf-viewer/default-layout"; import { pageNavigationPlugin } from "@react-pdf-viewer/page-navigation"; -import { Typography } from "antd"; import PropTypes from "prop-types"; -import { useEffect, useState } from "react"; -import { handleException } from "../../../helpers/GetStaticData"; -import { useAxiosPrivate } from "../../../hooks/useAxiosPrivate"; -import { useAlertStore } from "../../../store/alert-store"; -import { useCustomToolStore } from "../../../store/custom-tool-store"; -import { useSessionStore } from "../../../store/session-store"; -import { EmptyState } from "../../widgets/empty-state/EmptyState"; -import { SpinnerLoader } from "../../widgets/spinner-loader/SpinnerLoader"; - -function PdfViewer({ setOpenManageDocsModal }) { - const [fileUrl, setFileUrl] = useState(""); - const [isLoading, setIsLoading] = useState(false); +function PdfViewer({ fileUrl }) { const newPlugin = defaultLayoutPlugin(); const pageNavigationPluginInstance = pageNavigationPlugin(); - const { sessionDetails } = useSessionStore(); - const { setAlertDetails } = useAlertStore(); - const { selectedDoc, details } = useCustomToolStore(); - const axiosPrivate = useAxiosPrivate(); - - const base64toBlob = (data) => { - const bytes = atob(data); - let length = bytes.length; - const out = new Uint8Array(length); - - while (length--) { - out[length] = bytes.charCodeAt(length); - } - - return new Blob([out], { type: "application/pdf" }); - }; - - useEffect(() => { - if (!selectedDoc) { - setFileUrl(""); - return; - } - - const requestOptions = { - method: "GET", - url: `/api/v1/unstract/${sessionDetails?.orgId}/prompt-studio/file/fetch_contents?file_name=${selectedDoc}&tool_id=${details?.tool_id}`, - }; - - setIsLoading(true); - axiosPrivate(requestOptions) - .then((res) => { - const base64String = res?.data?.data || ""; - const blob = base64toBlob(base64String); - setFileUrl(URL.createObjectURL(blob)); - }) - .catch((err) => { - setAlertDetails(handleException(err, "Failed to load the document")); - }) - .finally(() => { - setIsLoading(false); - }); - }, [selectedDoc]); - - if (isLoading) { - return ; - } - - if (!selectedDoc) { - return ( - setOpenManageDocsModal(true)} - /> - ); - } - - if (!fileUrl) { - return ( -
- Failed to load the document -
- ); - } return (
@@ -96,7 +20,7 @@ function PdfViewer({ setOpenManageDocsModal }) { } PdfViewer.propTypes = { - setOpenManageDocsModal: PropTypes.func.isRequired, + fileUrl: PropTypes.any, }; export { PdfViewer }; diff --git a/frontend/src/components/custom-tools/prompt-card/PromptCard.jsx b/frontend/src/components/custom-tools/prompt-card/PromptCard.jsx index 9e5eebcb5..2cd09c16e 100644 --- a/frontend/src/components/custom-tools/prompt-card/PromptCard.jsx +++ b/frontend/src/components/custom-tools/prompt-card/PromptCard.jsx @@ -4,6 +4,7 @@ import { DeleteOutlined, EditOutlined, LeftOutlined, + LoadingOutlined, PlayCircleOutlined, RightOutlined, SearchOutlined, @@ -63,6 +64,7 @@ function PromptCard({ const [result, setResult] = useState({ promptOutputId: null, output: "", + evalMetrics: [], }); const [outputIds, setOutputIds] = useState([]); const [coverage, setCoverage] = useState(0); @@ -75,7 +77,6 @@ function PromptCard({ llmProfiles, selectedDoc, listOfDocs, - evalMetrics, updateCustomTool, details, disableLlmOrDocChange, @@ -214,24 +215,16 @@ function PromptCard({ return sortedMetrics; }; + const handleTypeChange = (value) => { + handleChange(value, promptDetails?.prompt_id, "enforce_type", true).then( + () => { + handleRun(); + } + ); + }; + // Generate the result for the currently selected document const handleRun = () => { - if (!promptDetails?.prompt_key) { - setAlertDetails({ - type: "error", - content: "Prompt key is not set", - }); - return; - } - - if (!promptDetails?.prompt) { - setAlertDetails({ - type: "error", - content: "Prompt cannot be empty", - }); - return; - } - if (!promptDetails?.profile_manager?.length) { setAlertDetails({ type: "error", @@ -251,41 +244,39 @@ function PromptCard({ setIsRunLoading(true); setIsCoverageLoading(true); setCoverage(0); + setCoverageTotal(0); + + let method = "POST"; + let url = `/api/v1/unstract/${sessionDetails?.orgId}/prompt-studio/prompt-output/`; + if (result?.promptOutputId) { + method = "PATCH"; + url += `${result?.promptOutputId}/`; + } handleRunApiRequest(selectedDoc) .then((res) => { const data = res?.data; const value = data[promptDetails?.prompt_key]; - let method = "POST"; - let url = `/api/v1/unstract/${sessionDetails?.orgId}/prompt-studio/prompt-output/`; - if (result?.promptOutputId) { - method = "PATCH"; - url += `${result?.promptOutputId}/`; - } - handleUpdateOutput(value, selectedDoc, method, url); - - if (value?.length > 0) { + if (value !== null && String(value)?.length > 0) { setCoverage((prev) => prev + 1); - } else { - setAlertDetails({ - type: "error", - content: `Failed to generate output for ${selectedDoc}`, - }); } - handleCoverage(); - const modifiedEvalMetrics = { ...evalMetrics }; - modifiedEvalMetrics[`${promptDetails?.prompt_id}`] = - sortEvalMetricsByType( - data[`${promptDetails?.prompt_key}__evaluation`] || [] - ); - updateCustomTool({ evalMetrics: modifiedEvalMetrics }); + // Handle Eval + let evalMetrics = []; + if (promptDetails?.evaluate) { + evalMetrics = data[`${promptDetails?.prompt_key}__evaluation`] || []; + } + handleUpdateOutput(value, selectedDoc, evalMetrics, method, url); }) .catch((err) => { - setIsCoverageLoading(false); - setAlertDetails(handleException(err)); + handleUpdateOutput(null, selectedDoc, [], method, url); + setAlertDetails( + handleException(err, `Failed to generate output for ${selectedDoc}`) + ); }) .finally(() => { setIsRunLoading(false); + setCoverageTotal((prev) => prev + 1); + handleCoverage(); }); }; @@ -300,14 +291,32 @@ function PromptCard({ return; } - setCoverageTotal(1); listOfDocsToProcess.forEach((item) => { + let method = "POST"; + let url = `/api/v1/unstract/${sessionDetails?.orgId}/prompt-studio/prompt-output/`; + const outputId = outputIds.find((output) => output?.docName === item); + if (outputId?.promptOutputId?.length) { + method = "PATCH"; + url += `${outputId?.promptOutputId}/`; + } handleRunApiRequest(item) .then((res) => { const data = res?.data; - handleCoverageData(data, item); + const outputValue = data[promptDetails?.prompt_key]; + if (outputValue !== null && String(outputValue)?.length > 0) { + setCoverage((prev) => prev + 1); + } + + // Handle Eval + let evalMetrics = []; + if (promptDetails?.evaluate) { + evalMetrics = + data[`${promptDetails?.prompt_key}__evaluation`] || []; + } + handleUpdateOutput(outputValue, item, evalMetrics, method, url); }) .catch((err) => { + handleUpdateOutput(null, item, [], method, url); setAlertDetails( handleException(err, `Failed to generate output for ${item}`) ); @@ -344,34 +353,20 @@ function PromptCard({ }); }; - const handleCoverageData = (data, docName) => { - const outputValue = data[promptDetails?.prompt_key]; - let method = "POST"; - let url = `/api/v1/unstract/${sessionDetails?.orgId}/prompt-studio/prompt-output/`; - const outputId = outputIds.find((output) => output?.docName === docName); - if (outputId?.promptOutputId?.length) { - method = "PATCH"; - url += `${outputId?.promptOutputId}/`; - } - handleUpdateOutput(outputValue, docName, method, url); - - if (outputValue?.length > 0) { - setCoverage((prev) => prev + 1); - } else { - setAlertDetails({ - type: "error", - content: `Failed to generate output for ${docName}`, - }); - } - }; - - const handleUpdateOutput = (outputValue, docName, method, url) => { + const handleUpdateOutput = ( + outputValue, + docName, + evalMetrics, + method, + url + ) => { const body = { - output: outputValue, + output: outputValue !== null ? String(outputValue) : null, tool_id: details?.tool_id, prompt_id: promptDetails?.prompt_id, profile_manager: promptDetails?.profile_manager, doc_name: docName, + eval_metrics: evalMetrics, }; const requestOptions = { @@ -386,14 +381,24 @@ function PromptCard({ axiosPrivate(requestOptions) .then((res) => { - if (docName !== selectedDoc) { - return; - } const data = res?.data; - setResult({ - promptOutputId: data?.prompt_output_id || null, - output: data?.output || "", - }); + const promptOutputId = data?.prompt_output_id || null; + if (docName === selectedDoc) { + setResult({ + promptOutputId: promptOutputId, + output: data?.output, + evalMetrics: sortEvalMetricsByType(data?.eval_metrics || []), + }); + } + + const isOutputIdAvailable = outputIds.find( + (item) => item?.promptOutputId === promptOutputId + ); + if (!isOutputIdAvailable) { + const listOfOutputIds = [...outputIds]; + listOfOutputIds.push({ promptOutputId, docName }); + setOutputIds(listOfOutputIds); + } }) .catch((err) => { setAlertDetails(handleException(err, "Failed to persist the result")); @@ -405,19 +410,19 @@ function PromptCard({ setResult({ promptOutputId: null, output: "", + evalMetrics: [], }); return; } setIsRunLoading(true); handleOutputApiRequest(true) - .then((res) => { - const data = res?.data; - + .then((data) => { if (!data || data?.length === 0) { setResult({ promptOutputId: null, output: "", + evalMetrics: [], }); return; } @@ -425,7 +430,8 @@ function PromptCard({ const outputResult = data[0]; setResult({ promptOutputId: outputResult?.prompt_output_id, - output: outputResult?.output || "", + output: outputResult?.output, + evalMetrics: sortEvalMetricsByType(outputResult?.eval_metrics || []), }); }) .catch((err) => { @@ -443,11 +449,7 @@ function PromptCard({ setCoverage(0); handleOutputApiRequest() - .then((res) => { - const data = res?.data; - data.sort((a, b) => { - return new Date(b.created_at) - new Date(a.created_at); - }); + .then((data) => { handleGetCoverageData(data); }) .catch((err) => { @@ -470,7 +472,13 @@ function PromptCard({ }; return axiosPrivate(requestOptions) - .then((res) => res) + .then((res) => { + const data = res?.data; + data.sort((a, b) => { + return new Date(b.modified_at) - new Date(a.modified_at); + }); + return data; + }) .catch((err) => { throw err; }); @@ -488,7 +496,7 @@ function PromptCard({ } if ( - item?.output?.length > 0 && + item?.output !== undefined && [...listOfDocs].includes(item?.doc_name) ) { ids.push({ @@ -581,6 +589,15 @@ function PromptCard({ /> + {isCoverageLoading && ( + } + color="processing" + className="display-flex-align-center" + > + Generating Response + + )} {updateStatus?.promptId === promptDetails?.prompt_id && ( <> {updateStatus?.status === @@ -680,8 +697,10 @@ function PromptCard({ 0) && - "prompt-card-comp-layout-border" + !( + isRunLoading || + (result?.output !== undefined && outputIds?.length > 0) + ) && "prompt-card-comp-layout-border" }`} >
@@ -708,6 +727,7 @@ function PromptCard({ icon={} loading={isCoverageLoading} onClick={() => setOpenOutputForDoc(true)} + disabled={outputIds?.length === 0} > Coverage: {coverage} of {listOfDocs?.length || 0} docs @@ -725,14 +745,7 @@ function PromptCard({ disabled={disableLlmOrDocChange.includes( promptDetails?.prompt_id )} - onChange={(value) => - handleChange( - value, - promptDetails?.prompt_id, - "enforce_type", - true - ) - } + onChange={(value) => handleTypeChange(value)} />
@@ -781,18 +794,17 @@ function PromptCard({
- {evalMetrics?.[`${promptDetails?.prompt_id}`] && ( + {result?.evalMetrics?.length > 0 && (
- {evalMetrics?.[`${promptDetails?.prompt_id}`].map( - (evalMetric) => ( - - ) - )} + {result?.evalMetrics.map((evalMetric) => ( + + ))}
)} - {(isRunLoading || result?.output?.length > 0) && ( + {(isRunLoading || + (result?.output !== undefined && outputIds?.length > 0)) && ( <>
@@ -816,7 +828,6 @@ function PromptCard({ setOpen={setOpenEval} promptDetails={promptDetails} handleChange={handleChange} - handleRun={handleRun} /> { + setIsContext(details?.summarize_context); + }, []); + + useEffect(() => { + if (!selectedLlm) { + setBtnText(""); + return; + } + + const llmItem = [...llmItems].find((item) => item?.value === selectedLlm); + setBtnText(llmItem?.label || ""); + }, [selectedLlm]); + + useEffect(() => { + if (llmItems?.length) { + setSelectedLlm(details?.summarize_llm_profile); + } + }, [llmItems]); + + const handleLlmProfileChange = (fieldName, value) => { + const body = { + [fieldName]: value, + }; + handleUpdateTool(body) + .then(() => { + setAlertDetails({ + type: "success", + content: "Successfully updated the LLM profile", + }); + }) + .catch((err) => { + setAlertDetails( + handleException(err, "Failed to update the LLM profile") + ); + }) + .finally(() => { + if (fieldName === "summarize_context") { + setIsContext(value); + } + }); + }; + + const onSearchDebounce = useCallback( + debounce((event) => { + handleLlmProfileChange(event.target.name, event.target.value); + }, 1000), + [] + ); + + return ( + setOpen(null)} + maskClosable={false} + centered + footer={null} + > +
+
+ + Summarize Manager + +
+
+ + - handleLlmProfileChange("summarize_llm_profile", value) + handleLlmProfileChange(fieldNames.SUMMARIZE_LLM_PROFILE, value) } /> @@ -111,33 +142,34 @@ function SelectLlmProfileModal({ - handleLlmProfileChange("summarize_context", value) + handleLlmProfileChange(fieldNames.SUMMARIZE_CONTEXT, value) } /> Summarize Context
- + handleLlmProfileChange( - "summarize_as_source", + fieldNames.SUMMARIZE_AS_SOURCE, e.target.checked ) } From d60d5cfc174a9cd0e2dae95531759f5eb5251950 Mon Sep 17 00:00:00 2001 From: Tahier Hussain Date: Tue, 27 Feb 2024 13:13:19 +0530 Subject: [PATCH 07/14] Fixed the popup issue to display the error message --- frontend/src/helpers/GetStaticData.js | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/frontend/src/helpers/GetStaticData.js b/frontend/src/helpers/GetStaticData.js index 214fcb0d2..be060d76a 100644 --- a/frontend/src/helpers/GetStaticData.js +++ b/frontend/src/helpers/GetStaticData.js @@ -244,9 +244,13 @@ const getTimeForLogs = () => { const handleException = (err, errMessage) => { if (err?.response?.data?.type === "validation_error") { // Handle validation errors - } else if ( - ["client_error", "server_error"].includes(err?.response?.data?.type) - ) { + return { + type: "error", + content: errMessage || "Something went wrong", + }; + } + + if (["client_error", "server_error"].includes(err?.response?.data?.type)) { // Handle client_error, server_error return { type: "error", @@ -255,12 +259,12 @@ const handleException = (err, errMessage) => { errMessage || "Something went wrong", }; - } else { - return { - type: "error", - content: err?.message, - }; } + + return { + type: "error", + content: err?.message || errMessage, + }; }; const base64toBlob = (data) => { From ddfce884dc112f98d64147705771df5e8c974337 Mon Sep 17 00:00:00 2001 From: Deepak Date: Tue, 27 Feb 2024 18:14:32 +0530 Subject: [PATCH 08/14] Added migration files for summarize and eval --- ...customtool_summarize_as_source_and_more.py | 42 +++++++++++++++++++ ..._promptstudiooutputmanager_eval_metrics.py | 24 +++++++++++ 2 files changed, 66 insertions(+) create mode 100644 backend/prompt_studio/prompt_studio_core/migrations/0004_customtool_summarize_as_source_and_more.py create mode 100644 backend/prompt_studio/prompt_studio_output_manager/migrations/0007_promptstudiooutputmanager_eval_metrics.py diff --git a/backend/prompt_studio/prompt_studio_core/migrations/0004_customtool_summarize_as_source_and_more.py b/backend/prompt_studio/prompt_studio_core/migrations/0004_customtool_summarize_as_source_and_more.py new file mode 100644 index 000000000..125ca3b2b --- /dev/null +++ b/backend/prompt_studio/prompt_studio_core/migrations/0004_customtool_summarize_as_source_and_more.py @@ -0,0 +1,42 @@ +# Generated by Django 4.2.1 on 2024-02-27 05:43 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("prompt_profile_manager", "0006_alter_profilemanager_x2text"), + ("prompt_studio_core", "0003_merge_20240125_1501"), + ] + + operations = [ + migrations.AddField( + model_name="customtool", + name="summarize_as_source", + field=models.BooleanField(default=True), + ), + migrations.AddField( + model_name="customtool", + name="summarize_context", + field=models.BooleanField(default=True), + ), + migrations.AddField( + model_name="customtool", + name="summarize_llm_profile", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="summarize_llm_profile", + to="prompt_profile_manager.profilemanager", + ), + ), + migrations.AddField( + model_name="customtool", + name="summarize_prompt", + field=models.TextField( + blank=True, db_comment="Field to store the summarize prompt" + ), + ), + ] diff --git a/backend/prompt_studio/prompt_studio_output_manager/migrations/0007_promptstudiooutputmanager_eval_metrics.py b/backend/prompt_studio/prompt_studio_output_manager/migrations/0007_promptstudiooutputmanager_eval_metrics.py new file mode 100644 index 000000000..b57a9bc6a --- /dev/null +++ b/backend/prompt_studio/prompt_studio_output_manager/migrations/0007_promptstudiooutputmanager_eval_metrics.py @@ -0,0 +1,24 @@ +# Generated by Django 4.2.1 on 2024-02-27 05:42 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ( + "prompt_studio_output_manager", + "0006_alter_promptstudiooutputmanager_output", + ), + ] + + operations = [ + migrations.AddField( + model_name="promptstudiooutputmanager", + name="eval_metrics", + field=models.JSONField( + db_column="eval_metrics", + db_comment="Field to store the evaluation metrics", + default=list, + ), + ), + ] From daef5ce1a73f0151d665938e4bc259a5bb23f026 Mon Sep 17 00:00:00 2001 From: Deepak Date: Tue, 27 Feb 2024 18:23:06 +0530 Subject: [PATCH 09/14] Added support for processor plugins --- .gitignore | 5 +- backend/prompt_studio/processor_loader.py | 76 +++++++++++++++++++ .../prompt_studio/prompt_studio_core/views.py | 15 ++++ 3 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 backend/prompt_studio/processor_loader.py diff --git a/.gitignore b/.gitignore index 9b9ca8f4a..9e300b9e3 100644 --- a/.gitignore +++ b/.gitignore @@ -607,6 +607,9 @@ $RECYCLE.BIN/ backend/plugins/authentication/* !backend/plugins/authentication/auth_sample +# Processor Plugins +backend/plugins/processor/* + # Tool registry unstract/tool-registry/src/unstract/tool_registry/*.json unstract/tool-registry/tests/*.yaml @@ -627,5 +630,3 @@ tools/*/sdks/ tools/*/data_dir/ docker/workflow_data/ - - diff --git a/backend/prompt_studio/processor_loader.py b/backend/prompt_studio/processor_loader.py new file mode 100644 index 000000000..28d12f8d9 --- /dev/null +++ b/backend/prompt_studio/processor_loader.py @@ -0,0 +1,76 @@ +import logging +import os +from importlib import import_module +from typing import Any + +from django.apps import apps + +logger = logging.getLogger(__name__) + + +class ProcessorConfig: + """Loader config for processor plugins.""" + + PLUGINS_APP = "plugins" + PLUGIN_DIR = "processor" + MODULE = "module" + METADATA = "metadata" + METADATA_NAME = "name" + METADATA_SERVICE_CLASS = "service_class" + METADATA_IS_ACTIVE = "is_active" + + +def load_plugins() -> list[Any]: + """Iterate through the processor plugins and register them.""" + plugins_app = apps.get_app_config(ProcessorConfig.PLUGINS_APP) + package_path = plugins_app.module.__package__ + processor_dir = os.path.join(plugins_app.path, ProcessorConfig.PLUGIN_DIR) + processor_package_path = f"{package_path}.{ProcessorConfig.PLUGIN_DIR}" + processor_plugins: list[Any] = [] + + for item in os.listdir(processor_dir): + # Loads a plugin if it is in a directory. + if os.path.isdir(os.path.join(processor_dir, item)): + processor_module_name = item + # Loads a plugin if it is a shared library. + # Module name is extracted from shared library name. + # `processor.platform_architecture.so` will be file name and + # `processor` will be the module name. + elif item.endswith(".so"): + processor_module_name = item.split(".")[0] + else: + continue + try: + full_module_path = ( + f"{processor_package_path}.{processor_module_name}" + ) + module = import_module(full_module_path) + metadata = getattr(module, ProcessorConfig.METADATA, {}) + + if metadata.get(ProcessorConfig.METADATA_IS_ACTIVE, False): + processor_plugins.append( + { + ProcessorConfig.MODULE: module, + ProcessorConfig.METADATA: module.metadata, + } + ) + logger.info( + "Loaded processor plugin: %s, is_active: %s", + module.metadata[ProcessorConfig.METADATA_NAME], + module.metadata[ProcessorConfig.METADATA_IS_ACTIVE], + ) + else: + logger.info( + "Processor plugin %s is not active.", + processor_module_name, + ) + except ModuleNotFoundError as exception: + logger.error( + "Error while importing processor plugin: %s", + exception, + ) + + if len(processor_plugins) == 0: + logger.info("No processor plugins found.") + + return processor_plugins diff --git a/backend/prompt_studio/prompt_studio_core/views.py b/backend/prompt_studio/prompt_studio_core/views.py index 5be5fa54c..cbd0c1890 100644 --- a/backend/prompt_studio/prompt_studio_core/views.py +++ b/backend/prompt_studio/prompt_studio_core/views.py @@ -6,6 +6,7 @@ from django.db.models import QuerySet from django.http import HttpRequest from permissions.permission import IsOwner +from prompt_studio.processor_loader import ProcessorConfig, load_plugins from prompt_studio.prompt_studio.exceptions import FilenameMissingError from prompt_studio.prompt_studio_core.constants import ( ToolStudioErrors, @@ -41,6 +42,8 @@ class PromptStudioCoreView(viewsets.ModelViewSet): permission_classes = [IsOwner] serializer_class = CustomToolSerializer + processor_plugins = load_plugins() + def get_queryset(self) -> Optional[QuerySet]: filter_args = FilterHelper.build_filter_args( self.request, @@ -139,6 +142,18 @@ def index_document(self, request: HttpRequest) -> Response: org_id=request.org_id, user_id=request.user.user_id, ) + + for processor_plugin in self.processor_plugins: + cls = processor_plugin[ProcessorConfig.METADATA][ + ProcessorConfig.METADATA_SERVICE_CLASS + ] + cls.process( + tool_id=tool_id, + file_name=file_name, + org_id=request.org_id, + user_id=request.user.user_id, + ) + if unique_id: return Response( {"message": "Document indexed successfully."}, From a48175f79ad616f4b72a915ec9994ab587644762 Mon Sep 17 00:00:00 2001 From: Tahier Hussain Date: Tue, 27 Feb 2024 18:49:58 +0530 Subject: [PATCH 10/14] Fixed scroll issue in the output analyzer --- .../custom-tools/output-analyzer-card/OutputAnalyzerCard.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/custom-tools/output-analyzer-card/OutputAnalyzerCard.jsx b/frontend/src/components/custom-tools/output-analyzer-card/OutputAnalyzerCard.jsx index 8db8fedd7..16b656160 100644 --- a/frontend/src/components/custom-tools/output-analyzer-card/OutputAnalyzerCard.jsx +++ b/frontend/src/components/custom-tools/output-analyzer-card/OutputAnalyzerCard.jsx @@ -91,7 +91,7 @@ function OutputAnalyzerCard({ doc, totalFields }) {
- +
From a41f5f8b02cdea0c0967585b9cd55913bb60c618 Mon Sep 17 00:00:00 2001 From: Deepak Date: Wed, 28 Feb 2024 14:31:35 +0530 Subject: [PATCH 11/14] Updated sdk version and resolved review comments --- .../file_management/file_management_helper.py | 4 +-- backend/pdm.lock | 10 +++---- .../prompt_studio_core/models.py | 10 +++++-- .../prompt_studio_helper.py | 28 +++++++++++-------- backend/pyproject.toml | 2 +- pdm.lock | 8 +++--- prompt-service/pdm.lock | 8 +++--- prompt-service/pyproject.toml | 2 +- pyproject.toml | 2 +- 9 files changed, 43 insertions(+), 31 deletions(-) diff --git a/backend/file_management/file_management_helper.py b/backend/file_management/file_management_helper.py index c4c0160f2..a6b9b600d 100644 --- a/backend/file_management/file_management_helper.py +++ b/backend/file_management/file_management_helper.py @@ -195,8 +195,8 @@ def fetch_file_contents( elif file_content_type == "text/plain": with fs.open(file_path, "r") as file: FileManagerHelper.logger.info(f"Reading text file: {file_path}") - encoded_string = file.read() - return encoded_string + text_content = file.read() + return text_content else: raise InvalidFileType diff --git a/backend/pdm.lock b/backend/pdm.lock index da10551b7..7fce08129 100644 --- a/backend/pdm.lock +++ b/backend/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "test", "deploy"] strategy = ["cross_platform", "inherit_metadata"] lock_version = "4.4.1" -content_hash = "sha256:73a791002f5f7bab1afe9eaee07e8fc946774da3448f9daabedbccfc9be4f202" +content_hash = "sha256:51aa34c4f085355e4448d1cd7c60c9b1a703e12d932c36b70a2fd5c960b340c3" [[package]] name = "aiobotocore" @@ -4667,7 +4667,7 @@ dependencies = [ [[package]] name = "unstract-sdk" -version = "0.11.2" +version = "0.12.0" requires_python = "<3.11.1,>=3.9" summary = "A framework for writing Unstract Tools/Apps" groups = ["default"] @@ -4684,8 +4684,8 @@ dependencies = [ "unstract-adapters~=0.2.2", ] files = [ - {file = "unstract_sdk-0.11.2-py3-none-any.whl", hash = "sha256:bcab064d87dc2985e5a4340e438a8ef71ef165b3e5078aba0ed2da3bda77f9e0"}, - {file = "unstract_sdk-0.11.2.tar.gz", hash = "sha256:0d6e3e00c9bbc6e569cc79922a2a45a54870c482cfa61287ea89a22ebd7fc606"}, + {file = "unstract_sdk-0.12.0-py3-none-any.whl", hash = "sha256:3e7b57a94df8cbdebe13998fface9eaf4b9eba667cd9f323d555b785599b51a6"}, + {file = "unstract_sdk-0.12.0.tar.gz", hash = "sha256:0208a653a59600fcbfaa9606341a11bb7e4381f64435754389fc46b735684395"}, ] [[package]] @@ -4700,7 +4700,7 @@ dependencies = [ "docker~=6.1.3", "jsonschema~=4.18.2", "unstract-adapters~=0.2.1", - "unstract-tool-sandbox @ file:///home/gayathrivijayakumar/code/Unstract/unstract/unstract/tool-registry/../tool-sandbox", + "unstract-tool-sandbox @ file:///home/ghost/Documents/zipstack/unstract-oss/unstract/tool-registry/../tool-sandbox", ] [[package]] diff --git a/backend/prompt_studio/prompt_studio_core/models.py b/backend/prompt_studio/prompt_studio_core/models.py index 4a2327e7e..82d41948f 100644 --- a/backend/prompt_studio/prompt_studio_core/models.py +++ b/backend/prompt_studio/prompt_studio_core/models.py @@ -44,6 +44,7 @@ class CustomTool(BaseModel): related_name="default_profile", null=True, blank=True, + db_comment="Default LLM Profile used in prompt", ) summarize_llm_profile = models.ForeignKey( ProfileManager, @@ -51,9 +52,14 @@ class CustomTool(BaseModel): related_name="summarize_llm_profile", null=True, blank=True, + db_comment="LLM Profile used for summarize", + ) + summarize_context = models.BooleanField( + default=True, db_comment="Flag to summarize content" + ) + summarize_as_source = models.BooleanField( + default=True, db_comment="Flag to use summarized content as source" ) - summarize_context = models.BooleanField(default=True) - summarize_as_source = models.BooleanField(default=True) summarize_prompt = models.TextField( blank=True, db_comment="Field to store the summarize prompt", diff --git a/backend/prompt_studio/prompt_studio_core/prompt_studio_helper.py b/backend/prompt_studio/prompt_studio_core/prompt_studio_helper.py index 191618cb5..3b7dced4a 100644 --- a/backend/prompt_studio/prompt_studio_core/prompt_studio_helper.py +++ b/backend/prompt_studio/prompt_studio_core/prompt_studio_helper.py @@ -81,30 +81,37 @@ def fetch_prompt_from_tool(tool_id: str) -> list[ToolStudioPrompt]: @staticmethod def index_document( - tool_id: str, file_name: str, org_id: str, user_id: str, is_summary: bool = False + tool_id: str, + file_name: str, + org_id: str, + user_id: str, + is_summary: bool = False, ) -> Any: """Method to index a document. Args: - tool_id (str):Id of the tool + tool_id (str): Id of the tool file_name (str): File to parse + org_id (str): The ID of the organization to which the user belongs. + user_id (str): The ID of the user who uploaded the document. + is_summary (bool, optional): Whether the document is a summary + or not. Defaults to False. Raises: ToolNotValid IndexingError """ tool: CustomTool = CustomTool.objects.get(pk=tool_id) - if is_summary: - default_profile: ProfileManager = tool.summarize_llm_profile + default_profile = tool.summarize_llm_profile file_path = file_name else: - default_profile: ProfileManager = tool.default_profile + default_profile = tool.default_profile file_path = FileManagerHelper.handle_sub_directory_for_tenants( org_id, is_create=False, user_id=user_id, tool_id=tool_id ) file_path = str(Path(file_path) / file_name) - + if not default_profile: raise DefaultProfileError() stream_log.publish( @@ -185,16 +192,15 @@ def prompt_responder( prompts: list[ToolStudioPrompt] = [] prompts.append(prompt_instance) tool: CustomTool = prompt_instance.tool_id - - + if tool.summarize_as_source: directory, filename = os.path.split(file_path) - file_path: str = os.path.join( + file_path = os.path.join( directory, TSPKeys.SUMMARIZE, os.path.splitext(filename)[0] + ".txt", ) - + stream_log.publish( tool.tool_id, stream_log.log( @@ -290,7 +296,7 @@ def _fetch_response( file_name=path, tool_id=str(tool.tool_id), org_id=org_id, - is_summary=tool.summarize_as_source + is_summary=tool.summarize_as_source, ) output: dict[str, Any] = {} diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 9494a1fda..3a1e962ff 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -30,7 +30,7 @@ dependencies = [ "python-socketio==5.9.0", # For log_events "social-auth-app-django==5.3.0", # For OAuth "social-auth-core==4.4.2", # For OAuth - "unstract-sdk~=0.11.2", + "unstract-sdk~=0.12.0", "unstract-adapters~=0.2.2", # ! IMPORTANT! # Indirect local dependencies usually need to be added in their own projects diff --git a/pdm.lock b/pdm.lock index a617b0be0..e071baeb4 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "hook-check-django-migrations", "lint"] strategy = ["cross_platform", "inherit_metadata"] lock_version = "4.4.1" -content_hash = "sha256:34bd617283cb8620b48d2ff82e83a4721d9464962bb6ccebd4d9f860efaed221" +content_hash = "sha256:32fcfebc305b03ed156f6a208a4edcf40cd407f555fb695d3cb932aaa43ece4f" [[package]] name = "absolufy-imports" @@ -4836,7 +4836,7 @@ dependencies = [ [[package]] name = "unstract-sdk" -version = "0.11.2" +version = "0.12.0" requires_python = "<3.11.1,>=3.9" summary = "A framework for writing Unstract Tools/Apps" groups = ["hook-check-django-migrations"] @@ -4853,8 +4853,8 @@ dependencies = [ "unstract-adapters~=0.2.2", ] files = [ - {file = "unstract_sdk-0.11.2-py3-none-any.whl", hash = "sha256:bcab064d87dc2985e5a4340e438a8ef71ef165b3e5078aba0ed2da3bda77f9e0"}, - {file = "unstract_sdk-0.11.2.tar.gz", hash = "sha256:0d6e3e00c9bbc6e569cc79922a2a45a54870c482cfa61287ea89a22ebd7fc606"}, + {file = "unstract_sdk-0.12.0-py3-none-any.whl", hash = "sha256:3e7b57a94df8cbdebe13998fface9eaf4b9eba667cd9f323d555b785599b51a6"}, + {file = "unstract_sdk-0.12.0.tar.gz", hash = "sha256:0208a653a59600fcbfaa9606341a11bb7e4381f64435754389fc46b735684395"}, ] [[package]] diff --git a/prompt-service/pdm.lock b/prompt-service/pdm.lock index 82dec09a0..652afff29 100644 --- a/prompt-service/pdm.lock +++ b/prompt-service/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "deploy"] strategy = ["cross_platform", "inherit_metadata"] lock_version = "4.4.1" -content_hash = "sha256:c02615c1fe1a21cddaa8ee726e7d3166446a694f45debed47272c2d8ee1504e0" +content_hash = "sha256:64a1667d589e799eb7d6cbcb67e2bc4adcfdfdc8335f21ae6168562e3db368e5" [[package]] name = "aiohttp" @@ -3677,7 +3677,7 @@ files = [ [[package]] name = "unstract-sdk" -version = "0.11.2" +version = "0.12.0" requires_python = "<3.11.1,>=3.9" summary = "A framework for writing Unstract Tools/Apps" groups = ["default"] @@ -3694,8 +3694,8 @@ dependencies = [ "unstract-adapters~=0.2.2", ] files = [ - {file = "unstract_sdk-0.11.2-py3-none-any.whl", hash = "sha256:bcab064d87dc2985e5a4340e438a8ef71ef165b3e5078aba0ed2da3bda77f9e0"}, - {file = "unstract_sdk-0.11.2.tar.gz", hash = "sha256:0d6e3e00c9bbc6e569cc79922a2a45a54870c482cfa61287ea89a22ebd7fc606"}, + {file = "unstract_sdk-0.12.0-py3-none-any.whl", hash = "sha256:3e7b57a94df8cbdebe13998fface9eaf4b9eba667cd9f323d555b785599b51a6"}, + {file = "unstract_sdk-0.12.0.tar.gz", hash = "sha256:0208a653a59600fcbfaa9606341a11bb7e4381f64435754389fc46b735684395"}, ] [[package]] diff --git a/prompt-service/pyproject.toml b/prompt-service/pyproject.toml index ae624cbee..4cc1facce 100644 --- a/prompt-service/pyproject.toml +++ b/prompt-service/pyproject.toml @@ -17,7 +17,7 @@ dependencies = [ "flask~=3.0", "llama-index==0.9.28", "python-dotenv==1.0.0", - "unstract-sdk~=0.11.2", + "unstract-sdk~=0.12.0", ] requires-python = ">=3.9,<3.11.1" readme = "README.md" diff --git a/pyproject.toml b/pyproject.toml index e8c3160f4..ee6a68e15 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ hook-check-django-migrations = [ "psycopg2-binary==2.9.9", "python-dotenv==1.0.0", "python-magic==0.4.27", - "unstract-sdk~=0.11.2", + "unstract-sdk~=0.12.0", "unstract-adapters~=0.2.2", "-e unstract-connectors @ file:///${PROJECT_ROOT}/unstract/connectors", "-e unstract-core @ file:///${PROJECT_ROOT}/unstract/core", From 5c1e02d6332fbed7ac1f3932869eabfbdf3c4195 Mon Sep 17 00:00:00 2001 From: Deepak Date: Wed, 28 Feb 2024 14:34:03 +0530 Subject: [PATCH 12/14] Updated migrations --- ...0005_alter_adapterinstance_adapter_type.py | 27 ++++++++++ ...ter_customtool_default_profile_and_more.py | 53 +++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 backend/adapter_processor/migrations/0005_alter_adapterinstance_adapter_type.py create mode 100644 backend/prompt_studio/prompt_studio_core/migrations/0005_alter_customtool_default_profile_and_more.py diff --git a/backend/adapter_processor/migrations/0005_alter_adapterinstance_adapter_type.py b/backend/adapter_processor/migrations/0005_alter_adapterinstance_adapter_type.py new file mode 100644 index 000000000..c0631e723 --- /dev/null +++ b/backend/adapter_processor/migrations/0005_alter_adapterinstance_adapter_type.py @@ -0,0 +1,27 @@ +# Generated by Django 4.2.1 on 2024-02-28 09:03 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("adapter_processor", "0004_alter_adapterinstance_adapter_type"), + ] + + operations = [ + migrations.AlterField( + model_name="adapterinstance", + name="adapter_type", + field=models.CharField( + choices=[ + ("UNKNOWN", "UNKNOWN"), + ("LLM", "LLM"), + ("EMBEDDING", "EMBEDDING"), + ("VECTOR_DB", "VECTOR_DB"), + ("OCR", "OCR"), + ("X2TEXT", "X2TEXT"), + ], + db_comment="Type of adapter LLM/EMBEDDING/VECTOR_DB", + ), + ), + ] diff --git a/backend/prompt_studio/prompt_studio_core/migrations/0005_alter_customtool_default_profile_and_more.py b/backend/prompt_studio/prompt_studio_core/migrations/0005_alter_customtool_default_profile_and_more.py new file mode 100644 index 000000000..f8c53210a --- /dev/null +++ b/backend/prompt_studio/prompt_studio_core/migrations/0005_alter_customtool_default_profile_and_more.py @@ -0,0 +1,53 @@ +# Generated by Django 4.2.1 on 2024-02-28 09:03 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("prompt_profile_manager", "0006_alter_profilemanager_x2text"), + ("prompt_studio_core", "0004_customtool_summarize_as_source_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="customtool", + name="default_profile", + field=models.ForeignKey( + blank=True, + db_comment="Default LLM Profile used in prompt", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="default_profile", + to="prompt_profile_manager.profilemanager", + ), + ), + migrations.AlterField( + model_name="customtool", + name="summarize_as_source", + field=models.BooleanField( + db_comment="Flag to use summarized content as source", + default=True, + ), + ), + migrations.AlterField( + model_name="customtool", + name="summarize_context", + field=models.BooleanField( + db_comment="Flag to summarize content", default=True + ), + ), + migrations.AlterField( + model_name="customtool", + name="summarize_llm_profile", + field=models.ForeignKey( + blank=True, + db_comment="LLM Profile used for summarize", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="summarize_llm_profile", + to="prompt_profile_manager.profilemanager", + ), + ), + ] From 6744598e8bd470a6132bc545b35a0dc4e50cd2af Mon Sep 17 00:00:00 2001 From: Deepak Date: Wed, 28 Feb 2024 20:23:08 +0530 Subject: [PATCH 13/14] Added processor directory --- backend/plugins/processor/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 backend/plugins/processor/.gitkeep diff --git a/backend/plugins/processor/.gitkeep b/backend/plugins/processor/.gitkeep new file mode 100644 index 000000000..e69de29bb From 9309da5f913d37aa856e8faa9cb878f6a781e415 Mon Sep 17 00:00:00 2001 From: Deepak Date: Wed, 28 Feb 2024 20:44:57 +0530 Subject: [PATCH 14/14] Updated sdk version to 0.12.1 --- backend/file_management/file_management_helper.py | 6 ++++-- backend/pdm.lock | 8 ++++---- .../prompt_studio_core/prompt_studio_helper.py | 8 +++++++- backend/pyproject.toml | 2 +- pdm.lock | 8 ++++---- prompt-service/pdm.lock | 8 ++++---- prompt-service/pyproject.toml | 2 +- pyproject.toml | 2 +- 8 files changed, 26 insertions(+), 18 deletions(-) diff --git a/backend/file_management/file_management_helper.py b/backend/file_management/file_management_helper.py index a6b9b600d..5c834d852 100644 --- a/backend/file_management/file_management_helper.py +++ b/backend/file_management/file_management_helper.py @@ -246,11 +246,13 @@ def handle_sub_directory_for_tenants( raise OrgIdNotValid() base_path = Path(settings.PROMPT_STUDIO_FILE_PATH) file_path: Path = base_path / org_id / user_id / tool_id + extract_file_path: Path = Path(file_path, "extract") + summarize_file_path: Path = Path(file_path, "summarize") if is_create: try: os.makedirs(file_path, exist_ok=True) - os.makedirs(f"{file_path}/extract", exist_ok=True) - os.makedirs(f"{file_path}/summarize", exist_ok=True) + os.makedirs(extract_file_path, exist_ok=True) + os.makedirs(summarize_file_path, exist_ok=True) except OSError as e: FileManagerHelper.logger.error( f"Error while creating {file_path}: {e}" diff --git a/backend/pdm.lock b/backend/pdm.lock index 7fce08129..9823da806 100644 --- a/backend/pdm.lock +++ b/backend/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "test", "deploy"] strategy = ["cross_platform", "inherit_metadata"] lock_version = "4.4.1" -content_hash = "sha256:51aa34c4f085355e4448d1cd7c60c9b1a703e12d932c36b70a2fd5c960b340c3" +content_hash = "sha256:27911cc8da87345bc67ffc964493ec9434a770ed2e380403ef87094e8a55e409" [[package]] name = "aiobotocore" @@ -4667,7 +4667,7 @@ dependencies = [ [[package]] name = "unstract-sdk" -version = "0.12.0" +version = "0.12.1" requires_python = "<3.11.1,>=3.9" summary = "A framework for writing Unstract Tools/Apps" groups = ["default"] @@ -4684,8 +4684,8 @@ dependencies = [ "unstract-adapters~=0.2.2", ] files = [ - {file = "unstract_sdk-0.12.0-py3-none-any.whl", hash = "sha256:3e7b57a94df8cbdebe13998fface9eaf4b9eba667cd9f323d555b785599b51a6"}, - {file = "unstract_sdk-0.12.0.tar.gz", hash = "sha256:0208a653a59600fcbfaa9606341a11bb7e4381f64435754389fc46b735684395"}, + {file = "unstract_sdk-0.12.1-py3-none-any.whl", hash = "sha256:960d8a7b92a1ad32894650edd9254aaf7c3f7555053991b3860e55ab684624a8"}, + {file = "unstract_sdk-0.12.1.tar.gz", hash = "sha256:06b1a7bf17948d548137502127458d83046c2e0b596ff326a292dbe94041a13a"}, ] [[package]] diff --git a/backend/prompt_studio/prompt_studio_core/prompt_studio_helper.py b/backend/prompt_studio/prompt_studio_core/prompt_studio_helper.py index 3b7dced4a..33bc189ce 100644 --- a/backend/prompt_studio/prompt_studio_core/prompt_studio_helper.py +++ b/backend/prompt_studio/prompt_studio_core/prompt_studio_helper.py @@ -388,6 +388,12 @@ def dynamic_indexer( vector_db = str(profile_manager.vector_store.id) x2text_adapter = str(profile_manager.x2text.id) file_hash = ToolUtils.get_hash_from_file(file_path=file_name) + extract_file_path = None + if not is_summary: + directory, filename = os.path.split(file_name) + extract_file_path: str = os.path.join( + directory, "extract", os.path.splitext(filename)[0] + ".txt" + ) return str( tool_index.index_file( tool_id=tool_id, @@ -399,6 +405,6 @@ def dynamic_indexer( chunk_size=profile_manager.chunk_size, chunk_overlap=profile_manager.chunk_overlap, reindex=profile_manager.reindex, - is_summary=is_summary, + output_file_path=extract_file_path, ) ) diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 3a1e962ff..faf134db2 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -30,7 +30,7 @@ dependencies = [ "python-socketio==5.9.0", # For log_events "social-auth-app-django==5.3.0", # For OAuth "social-auth-core==4.4.2", # For OAuth - "unstract-sdk~=0.12.0", + "unstract-sdk~=0.12.1", "unstract-adapters~=0.2.2", # ! IMPORTANT! # Indirect local dependencies usually need to be added in their own projects diff --git a/pdm.lock b/pdm.lock index e071baeb4..8fe2528d8 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "hook-check-django-migrations", "lint"] strategy = ["cross_platform", "inherit_metadata"] lock_version = "4.4.1" -content_hash = "sha256:32fcfebc305b03ed156f6a208a4edcf40cd407f555fb695d3cb932aaa43ece4f" +content_hash = "sha256:7b566c4c5f996943e1437b7c15b44c44f6d4e2c6c30374202688b21f945afb63" [[package]] name = "absolufy-imports" @@ -4836,7 +4836,7 @@ dependencies = [ [[package]] name = "unstract-sdk" -version = "0.12.0" +version = "0.12.1" requires_python = "<3.11.1,>=3.9" summary = "A framework for writing Unstract Tools/Apps" groups = ["hook-check-django-migrations"] @@ -4853,8 +4853,8 @@ dependencies = [ "unstract-adapters~=0.2.2", ] files = [ - {file = "unstract_sdk-0.12.0-py3-none-any.whl", hash = "sha256:3e7b57a94df8cbdebe13998fface9eaf4b9eba667cd9f323d555b785599b51a6"}, - {file = "unstract_sdk-0.12.0.tar.gz", hash = "sha256:0208a653a59600fcbfaa9606341a11bb7e4381f64435754389fc46b735684395"}, + {file = "unstract_sdk-0.12.1-py3-none-any.whl", hash = "sha256:960d8a7b92a1ad32894650edd9254aaf7c3f7555053991b3860e55ab684624a8"}, + {file = "unstract_sdk-0.12.1.tar.gz", hash = "sha256:06b1a7bf17948d548137502127458d83046c2e0b596ff326a292dbe94041a13a"}, ] [[package]] diff --git a/prompt-service/pdm.lock b/prompt-service/pdm.lock index 652afff29..d59328445 100644 --- a/prompt-service/pdm.lock +++ b/prompt-service/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "deploy"] strategy = ["cross_platform", "inherit_metadata"] lock_version = "4.4.1" -content_hash = "sha256:64a1667d589e799eb7d6cbcb67e2bc4adcfdfdc8335f21ae6168562e3db368e5" +content_hash = "sha256:6877a7e5b2b53d33a6f17b473de66b9d607119780e13c420f9216f7fcadd1186" [[package]] name = "aiohttp" @@ -3677,7 +3677,7 @@ files = [ [[package]] name = "unstract-sdk" -version = "0.12.0" +version = "0.12.1" requires_python = "<3.11.1,>=3.9" summary = "A framework for writing Unstract Tools/Apps" groups = ["default"] @@ -3694,8 +3694,8 @@ dependencies = [ "unstract-adapters~=0.2.2", ] files = [ - {file = "unstract_sdk-0.12.0-py3-none-any.whl", hash = "sha256:3e7b57a94df8cbdebe13998fface9eaf4b9eba667cd9f323d555b785599b51a6"}, - {file = "unstract_sdk-0.12.0.tar.gz", hash = "sha256:0208a653a59600fcbfaa9606341a11bb7e4381f64435754389fc46b735684395"}, + {file = "unstract_sdk-0.12.1-py3-none-any.whl", hash = "sha256:960d8a7b92a1ad32894650edd9254aaf7c3f7555053991b3860e55ab684624a8"}, + {file = "unstract_sdk-0.12.1.tar.gz", hash = "sha256:06b1a7bf17948d548137502127458d83046c2e0b596ff326a292dbe94041a13a"}, ] [[package]] diff --git a/prompt-service/pyproject.toml b/prompt-service/pyproject.toml index 4cc1facce..315787a51 100644 --- a/prompt-service/pyproject.toml +++ b/prompt-service/pyproject.toml @@ -17,7 +17,7 @@ dependencies = [ "flask~=3.0", "llama-index==0.9.28", "python-dotenv==1.0.0", - "unstract-sdk~=0.12.0", + "unstract-sdk~=0.12.1", ] requires-python = ">=3.9,<3.11.1" readme = "README.md" diff --git a/pyproject.toml b/pyproject.toml index ee6a68e15..922af43df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ hook-check-django-migrations = [ "psycopg2-binary==2.9.9", "python-dotenv==1.0.0", "python-magic==0.4.27", - "unstract-sdk~=0.12.0", + "unstract-sdk~=0.12.1", "unstract-adapters~=0.2.2", "-e unstract-connectors @ file:///${PROJECT_ROOT}/unstract/connectors", "-e unstract-core @ file:///${PROJECT_ROOT}/unstract/core",