From dec7c3b889880dacdd753ad6dcf56dabb0a76e79 Mon Sep 17 00:00:00 2001 From: jagadeeswaran-zipstack Date: Tue, 18 Jun 2024 10:14:40 +0530 Subject: [PATCH 01/45] added multiple llm profiles --- backend/adapter_processor/serializers.py | 4 + .../prompt_studio_core/constants.py | 1 + .../prompt_studio_helper.py | 73 ++- .../prompt_studio/prompt_studio_core/views.py | 4 +- .../output_manager_helper.py | 26 +- frontend/package.json | 1 + .../combined-output/CombinedOutput.jsx | 36 ++ .../custom-tools/prompt-card/Header.jsx | 66 ++- .../custom-tools/prompt-card/PromptCard.css | 53 +- .../custom-tools/prompt-card/PromptCard.jsx | 262 ++++++---- .../prompt-card/PromptCardItems.jsx | 451 +++++++++++++----- .../custom-tools/token-usage/TokenUsage.jsx | 8 +- 12 files changed, 721 insertions(+), 264 deletions(-) diff --git a/backend/adapter_processor/serializers.py b/backend/adapter_processor/serializers.py index 087cfe221..7833dac88 100644 --- a/backend/adapter_processor/serializers.py +++ b/backend/adapter_processor/serializers.py @@ -116,6 +116,10 @@ def to_representation(self, instance: AdapterInstance) -> dict[str, str]: rep[common.ICON] = AdapterProcessor.get_adapter_data_with_key( instance.adapter_id, common.ICON ) + adapter_metadata = instance.get_adapter_meta_data() + model = adapter_metadata.get("model") + if model: + rep["model"] = adapter_metadata["model"] if instance.is_friction_less: rep["created_by_email"] = "Unstract" diff --git a/backend/prompt_studio/prompt_studio_core/constants.py b/backend/prompt_studio/prompt_studio_core/constants.py index 934d9b530..099dba0b9 100644 --- a/backend/prompt_studio/prompt_studio_core/constants.py +++ b/backend/prompt_studio/prompt_studio_core/constants.py @@ -85,6 +85,7 @@ class ToolStudioPromptKeys: NOTES = "NOTES" OUTPUT = "output" SEQUENCE_NUMBER = "sequence_number" + PROFILE_MANAGER_ID = "profile_manager" class FileViewTypes: 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 00d8d4309..fe176bfd4 100644 --- a/backend/prompt_studio/prompt_studio_core/prompt_studio_helper.py +++ b/backend/prompt_studio/prompt_studio_core/prompt_studio_helper.py @@ -4,6 +4,7 @@ import uuid from pathlib import Path from typing import Any, Optional +import time from account.constants import Common from account.models import User @@ -34,6 +35,7 @@ from prompt_studio.prompt_studio_output_manager.output_manager_helper import ( OutputManagerHelper, ) +from prompt_studio.prompt_studio_core.redis_utils import set_document_indexing, is_document_indexing, mark_document_indexed, get_indexed_document_id from unstract.sdk.constants import LogLevel from unstract.sdk.exceptions import IndexingError, SdkError from unstract.sdk.index import Index @@ -364,6 +366,7 @@ def prompt_responder( document_id: str, id: Optional[str] = None, run_id: str = None, + profile_manager_id: Optional[str] = None ) -> Any: """Execute chain/single run of the prompts. Makes a call to prompt service and returns the dict of response. @@ -374,6 +377,7 @@ def prompt_responder( user_id (str): User's ID document_id (str): UUID of the document uploaded id (Optional[str]): ID of the prompt + profile_manager_id (Optional[str]): UUID of the profile manager Raises: AnswerFetchError: Error from prompt-service @@ -442,6 +446,7 @@ def prompt_responder( org_id=org_id, document_id=document_id, run_id=run_id, + profile_manager_id=profile_manager_id ) OutputManagerHelper.handle_prompt_output_update( @@ -450,6 +455,8 @@ def prompt_responder( outputs=response["output"], document_id=document_id, is_single_pass_extract=False, + profile_manager_id=profile_manager_id, + tool=tool ) # TODO: Review if this catch-all is required except Exception as e: @@ -562,6 +569,7 @@ def _fetch_response( org_id: str, document_id: str, run_id: str, + profile_manager_id: Optional[str] = None ) -> Any: """Utility function to invoke prompt service. Used internally. @@ -572,6 +580,8 @@ def _fetch_response( prompt (ToolStudioPrompt): ToolStudioPrompt instance to fetch response org_id (str): UUID of the organization document_id (str): UUID of the document + profile_manager_id (Optional[str]): UUID of the profile manager + Raises: DefaultProfileError: If no default profile is selected @@ -580,6 +590,16 @@ def _fetch_response( Returns: Any: Output from LLM """ + + # Fetch the ProfileManager instance using the profile_manager_id if provided + if profile_manager_id: + try: + profile_manager = ProfileManager.objects.get(profile_id=profile_manager_id) + except ProfileManager.DoesNotExist: + raise DefaultProfileError(f"ProfileManager with ID {profile_manager_id} does not exist.") + else: + profile_manager = prompt.profile_manager + monitor_llm_instance: Optional[AdapterInstance] = tool.monitor_llm monitor_llm: Optional[str] = None challenge_llm_instance: Optional[AdapterInstance] = tool.challenge_llm @@ -600,21 +620,20 @@ def _fetch_response( challenge_llm = str(default_profile.llm.id) # Need to check the user who created profile manager - PromptStudioHelper.validate_adapter_status(prompt.profile_manager) + PromptStudioHelper.validate_adapter_status(profile_manager) # Need to check the user who created profile manager # has access to adapters - PromptStudioHelper.validate_profile_manager_owner_access(prompt.profile_manager) + PromptStudioHelper.validate_profile_manager_owner_access(profile_manager) # Not checking reindex here as there might be # change in Profile Manager - vector_db = str(prompt.profile_manager.vector_store.id) - embedding_model = str(prompt.profile_manager.embedding_model.id) - llm = str(prompt.profile_manager.llm.id) - x2text = str(prompt.profile_manager.x2text.id) - prompt_profile_manager: ProfileManager = prompt.profile_manager - if not prompt_profile_manager: + vector_db = str(profile_manager.vector_store.id) + embedding_model = str(profile_manager.embedding_model.id) + llm = str(profile_manager.llm.id) + x2text = str(profile_manager.x2text.id) + if not profile_manager: raise DefaultProfileError() PromptStudioHelper.dynamic_indexer( - profile_manager=prompt_profile_manager, + profile_manager=profile_manager, file_path=doc_path, tool_id=str(tool.tool_id), org_id=org_id, @@ -639,16 +658,16 @@ def _fetch_response( output[TSPKeys.PROMPT] = prompt.prompt output[TSPKeys.ACTIVE] = prompt.active - output[TSPKeys.CHUNK_SIZE] = prompt.profile_manager.chunk_size + output[TSPKeys.CHUNK_SIZE] = profile_manager.chunk_size output[TSPKeys.VECTOR_DB] = vector_db output[TSPKeys.EMBEDDING] = embedding_model - output[TSPKeys.CHUNK_OVERLAP] = prompt.profile_manager.chunk_overlap + output[TSPKeys.CHUNK_OVERLAP] = profile_manager.chunk_overlap output[TSPKeys.LLM] = llm output[TSPKeys.TYPE] = prompt.enforce_type output[TSPKeys.NAME] = prompt.prompt_key - output[TSPKeys.RETRIEVAL_STRATEGY] = prompt.profile_manager.retrieval_strategy - output[TSPKeys.SIMILARITY_TOP_K] = prompt.profile_manager.similarity_top_k - output[TSPKeys.SECTION] = prompt.profile_manager.section + output[TSPKeys.RETRIEVAL_STRATEGY] = profile_manager.retrieval_strategy + output[TSPKeys.SIMILARITY_TOP_K] = profile_manager.similarity_top_k + output[TSPKeys.SECTION] = profile_manager.section output[TSPKeys.X2TEXT_ADAPTER] = x2text # Eval settings for the prompt output[TSPKeys.EVAL_SETTINGS] = {} @@ -748,8 +767,33 @@ def dynamic_indexer( ) else: profile_manager.chunk_size = 0 + + doc_id_key = f"{document_id}_{org_id}_{tool_id}" + + # Check if the document is already indexed + indexed_doc_id = get_indexed_document_id(doc_id_key) + if indexed_doc_id: + return indexed_doc_id + + # Polling if document is already being indexed + if is_document_indexing(doc_id_key): + max_wait_time = 1800 # 30 minutes + wait_time = 0 + polling_interval = 5 # Poll every 5 seconds + while is_document_indexing(doc_id_key): + if wait_time >= max_wait_time: + raise IndexingAPIError("Indexing timed out. Please try again later.") + time.sleep(polling_interval) + wait_time += polling_interval + + # After waiting, check if the document is indexed + indexed_doc_id = get_indexed_document_id(doc_id_key) + if indexed_doc_id: + return indexed_doc_id try: + # Set the document as being indexed + set_document_indexing(doc_id_key) usage_kwargs = {"run_id": run_id} util = PromptIdeBaseTool(log_level=LogLevel.INFO, org_id=org_id) tool_index = Index(tool=util) @@ -772,6 +816,7 @@ def dynamic_indexer( profile_manager=profile_manager, doc_id=doc_id, ) + mark_document_indexed(doc_id_key, doc_id) return doc_id except (IndexingError, IndexingAPIError, SdkError) as e: doc_name = os.path.split(file_path)[1] diff --git a/backend/prompt_studio/prompt_studio_core/views.py b/backend/prompt_studio/prompt_studio_core/views.py index 235e266b4..186fed4d8 100644 --- a/backend/prompt_studio/prompt_studio_core/views.py +++ b/backend/prompt_studio/prompt_studio_core/views.py @@ -264,6 +264,7 @@ def fetch_response(self, request: HttpRequest, pk: Any = None) -> Response: document_id: str = request.data.get(ToolStudioPromptKeys.DOCUMENT_ID) id: str = request.data.get(ToolStudioPromptKeys.ID) run_id: str = request.data.get(ToolStudioPromptKeys.RUN_ID) + profile_manager: str = request.data.get(ToolStudioPromptKeys.PROFILE_MANAGER_ID, None ) if not run_id: # Generate a run_id run_id = CommonUtils.generate_uuid() @@ -275,6 +276,7 @@ def fetch_response(self, request: HttpRequest, pk: Any = None) -> Response: user_id=custom_tool.created_by.user_id, document_id=document_id, run_id=run_id, + profile_manager_id=profile_manager ) return Response(response, status=status.HTTP_200_OK) @@ -507,4 +509,4 @@ def export_tool_info(self, request: Request, pk: Any = None) -> Response: return Response(serialized_instances) else: - return Response(status=status.HTTP_204_NO_CONTENT) + return Response(status=status.HTTP_204_NO_CONTENT) \ No newline at end of file diff --git a/backend/prompt_studio/prompt_studio_output_manager/output_manager_helper.py b/backend/prompt_studio/prompt_studio_output_manager/output_manager_helper.py index 6f942e3b7..da0c4ae8b 100644 --- a/backend/prompt_studio/prompt_studio_output_manager/output_manager_helper.py +++ b/backend/prompt_studio/prompt_studio_output_manager/output_manager_helper.py @@ -1,15 +1,20 @@ import json import logging -from typing import Any +from typing import Any, Optional from prompt_studio.prompt_profile_manager.models import ProfileManager -from prompt_studio.prompt_studio.exceptions import AnswerFetchError from prompt_studio.prompt_studio.models import ToolStudioPrompt from prompt_studio.prompt_studio_document_manager.models import DocumentManager from prompt_studio.prompt_studio_output_manager.constants import ( PromptStudioOutputManagerKeys as PSOMKeys, ) from prompt_studio.prompt_studio_output_manager.models import PromptStudioOutputManager +from prompt_studio.prompt_studio_core.exceptions import ( + AnswerFetchError, + DefaultProfileError, +) +from prompt_studio.prompt_studio_core.models import CustomTool + logger = logging.getLogger(__name__) @@ -22,6 +27,8 @@ def handle_prompt_output_update( outputs: Any, document_id: str, is_single_pass_extract: bool, + profile_manager_id: Optional[str] = None, + tool: CustomTool = None ) -> None: """Handles updating prompt outputs in the database. @@ -29,16 +36,27 @@ def handle_prompt_output_update( prompts (list[ToolStudioPrompt]): List of prompts to update. outputs (Any): Outputs corresponding to the prompts. document_id (str): ID of the document. + profile_manager_id (Optional[str]): UUID of the profile manager is_single_pass_extract (bool): Flag indicating if single pass extract is active. """ + if profile_manager_id: + try: + default_profile = ProfileManager.objects.get(profile_id=profile_manager_id) + except ProfileManager.DoesNotExist: + raise DefaultProfileError(f"ProfileManager with ID {profile_manager_id} does not exist.") + else: + if tool: + default_profile = ProfileManager.get_default_llm_profile(tool=tool) + else: + raise DefaultProfileError(f"ProfileManager with ID {profile_manager_id} does not exist.") + # Check if prompts list is empty if not prompts: return # Return early if prompts list is empty tool = prompts[0].tool_id document_manager = DocumentManager.objects.get(pk=document_id) - default_profile = ProfileManager.get_default_llm_profile(tool=tool) # Iterate through each prompt in the list for prompt in prompts: if prompt.prompt_type == PSOMKeys.NOTES: @@ -46,7 +64,7 @@ def handle_prompt_output_update( if is_single_pass_extract: profile_manager = default_profile else: - profile_manager = prompt.profile_manager + profile_manager = default_profile output = json.dumps(outputs.get(prompt.prompt_key)) eval_metrics = outputs.get(f"{prompt.prompt_key}__evaluation", []) diff --git a/frontend/package.json b/frontend/package.json index a01f53738..674ef6689 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -20,6 +20,7 @@ "cronstrue": "^2.48.0", "emoji-picker-react": "^4.8.0", "emoji-regex": "^10.3.0", + "framer-motion": "^11.2.10", "handlebars": "^4.7.8", "http-proxy-middleware": "^2.0.6", "js-cookie": "^3.0.5", diff --git a/frontend/src/components/custom-tools/combined-output/CombinedOutput.jsx b/frontend/src/components/custom-tools/combined-output/CombinedOutput.jsx index 507bf6704..903222b56 100644 --- a/frontend/src/components/custom-tools/combined-output/CombinedOutput.jsx +++ b/frontend/src/components/custom-tools/combined-output/CombinedOutput.jsx @@ -17,15 +17,19 @@ import { useSessionStore } from "../../../store/session-store"; import { SpinnerLoader } from "../../widgets/spinner-loader/SpinnerLoader"; import "./CombinedOutput.css"; import { useExceptionHandler } from "../../../hooks/useExceptionHandler"; +import TabPane from "antd/es/tabs/TabPane"; +import { Tabs } from "antd"; function CombinedOutput({ docId, setFilledFields }) { const [combinedOutput, setCombinedOutput] = useState({}); const [isOutputLoading, setIsOutputLoading] = useState(false); + const [adapterData, setAdapterData] = useState([]); const { details, defaultLlmProfile, singlePassExtractMode, isSinglePassExtractLoading, + llmProfiles, } = useCustomToolStore(); const { sessionDetails } = useSessionStore(); const { setAlertDetails } = useAlertStore(); @@ -36,6 +40,9 @@ function CombinedOutput({ docId, setFilledFields }) { if (!docId || isSinglePassExtractLoading) { return; } + console.log(llmProfiles); + getAdapterInfo(); + console.log(adapterData); let filledFields = 0; setIsOutputLoading(true); @@ -111,6 +118,26 @@ function CombinedOutput({ docId, setFilledFields }) { }); }; + const getAdapterInfo = () => { + axiosPrivate + .get(`/api/v1/unstract/${sessionDetails.orgId}/adapter/?adapter_type=LLM`) + .then((res) => { + const adapterList = res.data; + setAdapterData(removeDuplicates(adapterList)); + }); + }; + + const removeDuplicates = (adapters) => { + const uniqueModels = new Set(); + return adapters.filter((adapter) => { + if (!uniqueModels.has(adapter.model)) { + uniqueModels.add(adapter.model); + return true; + } + return false; + }); + }; + if (isOutputLoading) { return ; } @@ -118,6 +145,15 @@ function CombinedOutput({ docId, setFilledFields }) { return (
+ + {"Default"}} key={"0"}> + {adapterData.map((adapter, index) => ( + {adapter.model}} + key={(index + 1).toString()} + > + ))} +
diff --git a/frontend/src/components/custom-tools/prompt-card/Header.jsx b/frontend/src/components/custom-tools/prompt-card/Header.jsx index 35ac9d5aa..2c3443ae4 100644 --- a/frontend/src/components/custom-tools/prompt-card/Header.jsx +++ b/frontend/src/components/custom-tools/prompt-card/Header.jsx @@ -3,10 +3,11 @@ import { DeleteOutlined, EditOutlined, LoadingOutlined, + PlayCircleFilled, PlayCircleOutlined, SyncOutlined, } from "@ant-design/icons"; -import { Button, Col, Row, Tag, Tooltip } from "antd"; +import { Button, Checkbox, Col, Row, Tag, Tooltip } from "antd"; import PropTypes from "prop-types"; import { promptStudioUpdateStatus } from "../../../helpers/GetStaticData"; @@ -31,6 +32,7 @@ function Header({ enableEdit, expandCard, setExpandCard, + enabledProfiles, }) { const { selectedDoc, @@ -40,11 +42,12 @@ function Header({ indexDocs, } = useCustomToolStore(); - const handleRunBtnClick = () => { + const handleRunBtnClick = (profileManager = null, coverAllDoc = true) => { setExpandCard(true); - handleRun(); + handleRun(profileManager, coverAllDoc, enabledProfiles); }; + console.log(promptDetails); return ( @@ -122,23 +125,44 @@ function Header({ {!singlePassExtractMode && ( - - - + <> + + + + + + + )} handleDelete(promptDetails?.prompt_id)} @@ -159,6 +183,7 @@ function Header({ + ); @@ -180,6 +205,7 @@ Header.propTypes = { enableEdit: PropTypes.func.isRequired, expandCard: PropTypes.bool.isRequired, setExpandCard: PropTypes.func.isRequired, + enabledProfiles: PropTypes.array.isRequired, }; 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 a57d12fd8..5770abdee 100644 --- a/frontend/src/components/custom-tools/prompt-card/PromptCard.css +++ b/frontend/src/components/custom-tools/prompt-card/PromptCard.css @@ -2,6 +2,7 @@ .prompt-card { border: 1px solid #d9d9d9; + border-radius: 0; } .prompt-card .ant-card-body { @@ -20,10 +21,6 @@ background-color: #eceff3; } -.prompt-card-rad { - border-radius: 8px 8px 0px 0px; -} - .prompt-card-head-info-icon { color: #575859; } @@ -61,8 +58,11 @@ background-color: #f5f7f9; } -.prompt-card-comp-layout-border { - border-radius: 0px 0px 10px 10px; +.prompt-card-llm-layout { + width: 100%; + padding: 8px 12px; + background-color: #f5f7f9; + row-gap: 2; } .prompt-card-actions-dropdowns { @@ -76,7 +76,8 @@ .prompt-card-result { padding-top: 12px; background-color: #fff8e6; - border-radius: 0px 0px 8px 8px; + display: flex; + justify-content: space-between; } .prompt-card-result .ant-typography { @@ -116,3 +117,41 @@ .prompt-card-collapse .ant-collapse-content-box { padding: 0px !important; } + +.llm-info { + display: flex; + align-items: center; +} + +.prompt-card-llm-title { + margin: 0 0 0 10px !important; +} + +.prompt-card-llm-icon { + display: flex; + justify-content: center; +} + +.prompt-cost-item { + font-size: 12px; + margin-right: 10px; +} + +.prompt-info { + display: flex; + justify-content: space-between; +} + +.llm-info-container > * { + margin-left: 10px; +} + +.prompt-card-llm-container { + border-right: 1px solid #0000000f; + width: fill-available !important; +} + +.prompt-card-llm { + width: fill-available !important; + max-width: 269px; +} diff --git a/frontend/src/components/custom-tools/prompt-card/PromptCard.jsx b/frontend/src/components/custom-tools/prompt-card/PromptCard.jsx index 2ef4a9645..fd7074828 100644 --- a/frontend/src/components/custom-tools/prompt-card/PromptCard.jsx +++ b/frontend/src/components/custom-tools/prompt-card/PromptCard.jsx @@ -31,16 +31,12 @@ function PromptCard({ updatePlaceHolder, }) { const [enforceTypeList, setEnforceTypeList] = useState([]); - const [page, setPage] = useState(0); - const [isRunLoading, setIsRunLoading] = useState(false); + const [isRunLoading, setIsRunLoading] = useState({}); const [promptKey, setPromptKey] = useState(""); const [promptText, setPromptText] = useState(""); const [selectedLlmProfileId, setSelectedLlmProfileId] = useState(null); const [openEval, setOpenEval] = useState(false); - const [result, setResult] = useState({ - promptOutputId: null, - output: "", - }); + const [result, setResult] = useState([]); const [coverage, setCoverage] = useState(0); const [coverageTotal, setCoverageTotal] = useState(0); const [isCoverageLoading, setIsCoverageLoading] = useState(false); @@ -99,6 +95,7 @@ function PromptCard({ level: msg?.level || "INFO", }); }, [messages]); + console.log(isRunLoading); useEffect(() => { setSelectedLlmProfileId(promptDetails?.profile_manager || null); @@ -106,12 +103,18 @@ function PromptCard({ useEffect(() => { resetInfoMsgs(); + handleGetOutput(); + handleGetCoverage(); if (isSinglePassExtractLoading) { return; } - - handleGetOutput(); - handleGetCoverage(); + if (selectedLlmProfileId !== promptDetails?.profile_id) { + handleChange( + selectedLlmProfileId, + promptDetails?.prompt_id, + "profile_manager" + ); + } }, [ selectedLlmProfileId, selectedDoc, @@ -142,20 +145,6 @@ function PromptCard({ updateCustomTool({ disableLlmOrDocChange: listOfIds }); }, [isCoverageLoading]); - useEffect(() => { - if (page < 1) { - return; - } - const llmProfile = llmProfiles[page - 1]; - if (llmProfile?.profile_id !== promptDetails?.profile_id) { - handleChange( - llmProfile?.profile_id, - promptDetails?.prompt_id, - "profile_manager" - ); - } - }, [page]); - useEffect(() => { if (isCoverageLoading && coverageTotal === listOfDocs?.length) { setIsCoverageLoading(false); @@ -176,34 +165,18 @@ function PromptCard({ if (!isProfilePresent) { setSelectedLlmProfileId(null); } - - const llmProfileId = promptDetails?.profile_manager; - if (!llmProfileId) { - setPage(0); - return; - } - const index = llmProfiles.findIndex( - (item) => item?.profile_id === llmProfileId - ); - setPage(index + 1); }, [llmProfiles]); - const handlePageLeft = () => { - if (page <= 1) { - return; - } - - const newPage = page - 1; - setPage(newPage); + // Function to update loading state for a specific document and profile + const handleIsRunLoading = (docId, profileId, isLoading) => { + setIsRunLoading((prevLoadingProfiles) => ({ + ...prevLoadingProfiles, + [`${docId}_${profileId}`]: isLoading, + })); }; - const handlePageRight = () => { - if (page >= llmProfiles?.length) { - return; - } - - const newPage = page + 1; - setPage(newPage); + const handleSelectDefaultLLM = (llmProfileId) => { + setSelectedLlmProfileId(llmProfileId); }; const handleTypeChange = (value) => { @@ -222,8 +195,14 @@ function PromptCard({ }); }; + console.log(llmProfiles, promptDetails); + // Generate the result for the currently selected document - const handleRun = () => { + const handleRun = ( + profileManagerId, + coverAllDoc = true, + selectedLlmProfiles + ) => { try { setPostHogCustomEvent("ps_prompt_run", { info: "Click on 'Run Prompt' button (Multi Pass)", @@ -232,7 +211,11 @@ function PromptCard({ // If an error occurs while setting custom posthog event, ignore it and continue } - if (!promptDetails?.profile_manager?.length) { + if ( + !profileManagerId && + !promptDetails?.profile_manager?.length && + !(!coverAllDoc && selectedLlmProfiles.length > 0) + ) { setAlertDetails({ type: "error", content: "LLM Profile is not selected", @@ -264,7 +247,11 @@ function PromptCard({ return; } - setIsRunLoading(true); + handleIsRunLoading( + selectedDoc.document_id, + profileManagerId || selectedLlmProfileId, + true + ); setIsCoverageLoading(true); setCoverage(0); setCoverageTotal(0); @@ -282,8 +269,9 @@ function PromptCard({ details?.summarize_llm_profile ) { // Summary needs to be indexed before running the prompt - setIsRunLoading(false); - handleStepsAfterRunCompletion(); + handleIsRunLoading(selectedDoc.document_id, selectedLlmProfileId, false); + setCoverageTotal(1); + handleCoverage(selectedLlmProfileId); setAlertDetails({ type: "error", content: `Summary needs to be indexed before running the prompt - ${selectedDoc?.document_name}.`, @@ -292,35 +280,75 @@ function PromptCard({ } handleDocOutputs(docId, true, null); - handleRunApiRequest(docId) - .then((res) => { - const data = res?.data?.output; - const value = data[promptDetails?.prompt_key]; - if (value || value === 0) { - setCoverage((prev) => prev + 1); - } - handleDocOutputs(docId, false, value); - handleGetOutput(); - }) - .catch((err) => { - setIsRunLoading(false); - handleDocOutputs(docId, false, null); - setAlertDetails( - handleException(err, `Failed to generate output for ${docId}`) + if (!profileManagerId) { + let selectedProfiles = llmProfiles; + if (!coverAllDoc && selectedLlmProfiles.length > 0) { + selectedProfiles = llmProfiles.filter((profile) => + selectedLlmProfiles.includes(profile.profile_id) ); - }) - .finally(() => { - handleStepsAfterRunCompletion(); - }); - }; - - const handleStepsAfterRunCompletion = () => { - setCoverageTotal(1); - handleCoverage(); + } + for (const profile of selectedProfiles) { + handleIsRunLoading(selectedDoc.document_id, profile.profile_id, true); + handleRunApiRequest(docId, profile.profile_id) + .then((res) => { + const data = res?.data?.output; + const value = data[promptDetails?.prompt_key]; + if (value || value === 0) { + setCoverage((prev) => prev + 1); + } + handleDocOutputs(docId, false, value); + handleGetOutput(profile.profile_id); + }) + .catch((err) => { + handleIsRunLoading( + selectedDoc.document_id, + profile.profile_id, + false + ); + handleDocOutputs(docId, false, null); + setAlertDetails( + handleException(err, `Failed to generate output for ${docId}`) + ); + }); + if (coverAllDoc) { + handleCoverage(profile.profile_id); + } + } + } else { + handleRunApiRequest(docId, profileManagerId) + .then((res) => { + const data = res?.data?.output; + const value = data[promptDetails?.prompt_key]; + if (value || value === 0) { + setCoverage((prev) => prev + 1); + } + handleDocOutputs(docId, false, value); + handleGetOutput(); + setCoverageTotal(1); + }) + .catch((err) => { + handleIsRunLoading( + selectedDoc.document_id, + selectedLlmProfileId, + false + ); + handleDocOutputs(docId, false, null); + setAlertDetails( + handleException(err, `Failed to generate output for ${docId}`) + ); + }) + .finally(() => { + handleIsRunLoading(selectedDoc.document_id, profileManagerId, false); + setIsCoverageLoading(false); + }); + if (coverAllDoc) { + handleCoverage(profileManagerId); + } + } }; // Get the coverage for all the documents except the one that's currently selected - const handleCoverage = () => { + const handleCoverage = (profileManagerId) => { const listOfDocsToProcess = [...listOfDocs].filter( (item) => item?.document_id !== selectedDoc?.document_id ); @@ -353,8 +381,9 @@ function PromptCard({ return; } + setIsCoverageLoading(true); handleDocOutputs(docId, true, null); - handleRunApiRequest(docId) + handleRunApiRequest(docId, profileManagerId) .then((res) => { const data = res?.data?.output; const outputValue = data[promptDetails?.prompt_key]; @@ -371,17 +400,21 @@ function PromptCard({ }) .finally(() => { totalCoverageValue++; + if (listOfDocsToProcess?.length >= totalCoverageValue) { + setIsCoverageLoading(false); + return; + } setCoverageTotal(totalCoverageValue); }); }); }; - const handleRunApiRequest = async (docId) => { + const handleRunApiRequest = async (docId, profileManagerId = null) => { const promptId = promptDetails?.prompt_id; const runId = generateUUID(); // Update the token usage state with default token usage for a specific document ID - const tokenUsageId = promptId + "__" + docId; + const tokenUsageId = promptId + "__" + docId + "__" + profileManagerId; setTokenUsage(tokenUsageId, defaultTokenUsage); // Set up an interval to fetch token usage data at regular intervals @@ -396,6 +429,10 @@ function PromptCard({ run_id: runId, }; + if (profileManagerId) { + body.profile_manager = profileManagerId; + } + const requestOptions = { method: "POST", url: `/api/v1/unstract/${sessionDetails?.orgId}/prompt-studio/fetch_response/${details?.tool_id}`, @@ -417,42 +454,55 @@ function PromptCard({ }); }; - const handleGetOutput = () => { - if (!selectedDoc || (!singlePassExtractMode && !selectedLlmProfileId)) { - setResult({ - promptOutputId: null, - output: "", - }); + const handleGetOutput = (profileManager = undefined) => { + if (!selectedDoc) { + setResult([]); return; } - setIsRunLoading(true); + if (singlePassExtractMode) { + setResult([]); + return; + } + + handleIsRunLoading( + selectedDoc.document_id, + profileManager || selectedLlmProfileId, + true + ); handleOutputApiRequest(true) .then((res) => { const data = res?.data; + console.log(data); if (!data || data?.length === 0) { - setResult({ - promptOutputId: null, - output: "", - }); + setResult([]); return; } - const outputResult = data[0]; - setResult({ - promptOutputId: outputResult?.prompt_output_id, - output: outputResult?.output, - evalMetrics: getEvalMetrics( - promptDetails?.evaluate, - outputResult?.eval_metrics || [] - ), + const outputResults = data.map((outputResult) => { + return { + runId: outputResult?.run_id, + promptOutputId: outputResult?.prompt_output_id, + profileManager: outputResult?.profile_manager, + output: outputResult?.output, + evalMetrics: getEvalMetrics( + promptDetails?.evaluate, + outputResult?.eval_metrics || [] + ), + }; }); + console.log(outputResults); + setResult(outputResults); }) .catch((err) => { setAlertDetails(handleException(err, "Failed to generate the result")); }) .finally(() => { - setIsRunLoading(false); + handleIsRunLoading( + selectedDoc.document_id, + profileManager || selectedLlmProfileId, + false + ); }); }; @@ -480,12 +530,16 @@ function PromptCard({ if (singlePassExtractMode) { profileManager = defaultLlmProfile; } - let url = `/api/v1/unstract/${sessionDetails?.orgId}/prompt-studio/prompt-output/?tool_id=${details?.tool_id}&prompt_id=${promptDetails?.prompt_id}&profile_manager=${profileManager}&is_single_pass_extract=${singlePassExtractMode}`; + let url = `/api/v1/unstract/${sessionDetails?.orgId}/prompt-studio/prompt-output/?tool_id=${details?.tool_id}&prompt_id=${promptDetails?.prompt_id}&is_single_pass_extract=${singlePassExtractMode}`; if (isOutput) { url += `&document_manager=${selectedDoc?.document_id}`; } + if (singlePassExtractMode) { + url += `&profile_manager=${profileManager}`; + } + const requestOptions = { method: "GET", url, @@ -507,7 +561,7 @@ function PromptCard({ } } else { data.forEach((item) => { - const tokenUsageId = `${item?.prompt_id}__${item?.document_manager}`; + const tokenUsageId = `${item?.prompt_id}__${item?.document_manager}__${item?.profile_manager}`; if (tokenUsage[tokenUsageId] === undefined) { setTokenUsage(tokenUsageId, item?.token_usage); @@ -547,8 +601,6 @@ function PromptCard({ progressMsg={progressMsg} handleRun={handleRun} handleChange={handleChange} - handlePageLeft={handlePageLeft} - handlePageRight={handlePageRight} handleTypeChange={handleTypeChange} handleDelete={handleDelete} updateStatus={updateStatus} @@ -557,7 +609,7 @@ function PromptCard({ setOpenEval={setOpenEval} setOpenOutputForDoc={setOpenOutputForDoc} selectedLlmProfileId={selectedLlmProfileId} - page={page} + handleSelectDefaultLLM={handleSelectDefaultLLM} /> {EvalModal && !singlePassExtractMode && ( profile.profile_id) + ); + const privateAxios = useAxiosPrivate(); + const { sessionDetails } = useSessionStore(); - useEffect(() => { - setExpandCard(true); - }, [isSinglePassExtractLoading]); + const divRef = useRef(null); const enableEdit = (event) => { event.stopPropagation(); @@ -72,7 +93,87 @@ function PromptCardItems({ setIsEditingTitle(true); setIsEditingPrompt(true); }; + const getModelOrAdapterId = (profile, adapters) => { + const result = { conf: {} }; + const keys = ["vector_store", "embedding_model", "llm", "x2text"]; + + keys.forEach((key) => { + const adapterName = profile[key]; + const adapter = adapters.find( + (adapter) => adapter.adapter_name === adapterName + ); + if (adapter) { + result.conf[key] = adapter.model || adapter.adapter_id.split("|")[0]; + if (adapter.adapter_type === "LLM") result.icon = adapter.icon; + } + }); + return result; + }; + + const handleCancel = () => { + setIsModalVisible(false); + }; + + const getAdapterInfo = async () => { + privateAxios + .get(`/api/v1/unstract/${sessionDetails.orgId}/adapter/`) + .then((res) => { + const adapterData = res.data; + + // Update llmProfiles with additional fields + const updatedProfiles = llmProfiles.map((profile) => { + return { ...getModelOrAdapterId(profile, adapterData), ...profile }; + }); + console.log(updatedProfiles); + setLlmProfileDetails( + updatedProfiles + .map((profile) => ({ + ...profile, + isDefault: profile.profile_id === selectedLlmProfileId, + isEnabled: enabledProfiles.includes(profile.profile_id), + })) + .sort((a, b) => { + if (a.isDefault) return -1; // Default profile comes first + if (b.isDefault) return 1; + if (a.isEnabled && !b.isEnabled) return -1; // Enabled profiles come before disabled + if (!a.isEnabled && b.isEnabled) return 1; + return 0; + }) + ); + }); + }; + + const tooltipContent = (adapterConf) => ( +
+ {Object.entries(adapterConf).map(([key, value]) => ( +
+ {key}: {value} +
+ ))} +
+ ); + + const handleExpandClick = (profile) => { + const output = + result.find((r) => r.profileManager === profile.profile_id)?.output || ""; + setModalContent(displayPromptResult(output, false)); + setIsModalVisible(true); + }; + const handleTagChange = (checked, profileId) => { + setEnabledProfiles((prevState) => + checked + ? [...prevState, profileId] + : prevState.filter((id) => id !== profileId) + ); + }; + useEffect(() => { + setExpandCard(true); + }, [isSinglePassExtractLoading]); + + useEffect(() => { + getAdapterInfo(); + }, [llmProfiles, selectedLlmProfileId, enabledProfiles]); return (
@@ -93,6 +194,7 @@ function PromptCardItems({ enableEdit={enableEdit} expandCard={expandCard} setExpandCard={setExpandCard} + enabledProfiles={enabledProfiles} />
@@ -152,15 +254,6 @@ function PromptCardItems({ - {!singlePassExtractMode && ( - - )}