From 9a5ae9f51f5d3476ace282de71dfafc99a9c0dc0 Mon Sep 17 00:00:00 2001 From: John Wang Date: Mon, 22 May 2023 17:39:28 +0800 Subject: [PATCH] Feat: optimize error desc (#152) --- api/controllers/console/app/__init__.py | 2 +- api/controllers/console/app/error.py | 14 ++--- .../console/datasets/datasets_document.py | 13 ++++- api/controllers/console/datasets/error.py | 16 +++--- .../console/datasets/hit_testing.py | 9 ++++ api/controllers/console/error.py | 5 +- api/controllers/console/workspace/error.py | 4 +- .../console/workspace/providers.py | 4 +- api/controllers/service_api/app/error.py | 16 +++--- api/controllers/service_api/dataset/error.py | 2 +- api/controllers/web/error.py | 20 ++++---- api/core/index/index_builder.py | 12 +++++ api/core/index/vector_index.py | 4 +- api/core/llm/provider/azure_provider.py | 51 ++++++++++++++----- web/i18n/lang/common.en.ts | 2 +- 15 files changed, 119 insertions(+), 55 deletions(-) diff --git a/api/controllers/console/app/__init__.py b/api/controllers/console/app/__init__.py index 1f22ab30c..f0c7956e0 100644 --- a/api/controllers/console/app/__init__.py +++ b/api/controllers/console/app/__init__.py @@ -17,6 +17,6 @@ def _get_app(app_id, mode=None): raise NotFound("App not found") if mode and app.mode != mode: - raise AppUnavailableError() + raise NotFound("The {} app not found".format(mode)) return app diff --git a/api/controllers/console/app/error.py b/api/controllers/console/app/error.py index c19f054be..8923f90bf 100644 --- a/api/controllers/console/app/error.py +++ b/api/controllers/console/app/error.py @@ -9,31 +9,33 @@ class AppNotFoundError(BaseHTTPException): class ProviderNotInitializeError(BaseHTTPException): error_code = 'provider_not_initialize' - description = "Provider Token not initialize." + description = "No valid model provider credentials found. " \ + "Please go to Settings -> Model Provider to complete your provider credentials." code = 400 class ProviderQuotaExceededError(BaseHTTPException): error_code = 'provider_quota_exceeded' - description = "Provider quota exceeded." + description = "Your quota for Dify Hosted OpenAI has been exhausted. " \ + "Please go to Settings -> Model Provider to complete your own provider credentials." code = 400 class ProviderModelCurrentlyNotSupportError(BaseHTTPException): error_code = 'model_currently_not_support' - description = "GPT-4 currently not support." + description = "Dify Hosted OpenAI trial currently not support the GPT-4 model." code = 400 class ConversationCompletedError(BaseHTTPException): error_code = 'conversation_completed' - description = "Conversation was completed." + description = "The conversation has ended. Please start a new conversation." code = 400 class AppUnavailableError(BaseHTTPException): error_code = 'app_unavailable' - description = "App unavailable." + description = "App unavailable, please check your app configurations." code = 400 @@ -45,5 +47,5 @@ class CompletionRequestError(BaseHTTPException): class AppMoreLikeThisDisabledError(BaseHTTPException): error_code = 'app_more_like_this_disabled' - description = "More like this disabled." + description = "The 'More like this' feature is disabled. Please refresh your page." code = 403 diff --git a/api/controllers/console/datasets/datasets_document.py b/api/controllers/console/datasets/datasets_document.py index 79e52d565..3b9efeaab 100644 --- a/api/controllers/console/datasets/datasets_document.py +++ b/api/controllers/console/datasets/datasets_document.py @@ -10,13 +10,14 @@ from werkzeug.exceptions import NotFound, Forbidden import services from controllers.console import api -from controllers.console.app.error import ProviderNotInitializeError +from controllers.console.app.error import ProviderNotInitializeError, ProviderQuotaExceededError, \ + ProviderModelCurrentlyNotSupportError from controllers.console.datasets.error import DocumentAlreadyFinishedError, InvalidActionError, DocumentIndexingError, \ InvalidMetadataError, ArchivedDocumentImmutableError from controllers.console.setup import setup_required from controllers.console.wraps import account_initialization_required from core.indexing_runner import IndexingRunner -from core.llm.error import ProviderTokenNotInitError +from core.llm.error import ProviderTokenNotInitError, QuotaExceededError, ModelCurrentlyNotSupportError from extensions.ext_redis import redis_client from libs.helper import TimestampField from extensions.ext_database import db @@ -222,6 +223,10 @@ class DatasetDocumentListApi(Resource): document = DocumentService.save_document_with_dataset_id(dataset, args, current_user) except ProviderTokenNotInitError: raise ProviderNotInitializeError() + except QuotaExceededError: + raise ProviderQuotaExceededError() + except ModelCurrentlyNotSupportError: + raise ProviderModelCurrentlyNotSupportError() return document @@ -259,6 +264,10 @@ class DatasetInitApi(Resource): ) except ProviderTokenNotInitError: raise ProviderNotInitializeError() + except QuotaExceededError: + raise ProviderQuotaExceededError() + except ModelCurrentlyNotSupportError: + raise ProviderModelCurrentlyNotSupportError() response = { 'dataset': dataset, diff --git a/api/controllers/console/datasets/error.py b/api/controllers/console/datasets/error.py index 014822d56..29142b80e 100644 --- a/api/controllers/console/datasets/error.py +++ b/api/controllers/console/datasets/error.py @@ -3,7 +3,7 @@ from libs.exception import BaseHTTPException class NoFileUploadedError(BaseHTTPException): error_code = 'no_file_uploaded' - description = "No file uploaded." + description = "Please upload your file." code = 400 @@ -27,25 +27,25 @@ class UnsupportedFileTypeError(BaseHTTPException): class HighQualityDatasetOnlyError(BaseHTTPException): error_code = 'high_quality_dataset_only' - description = "High quality dataset only." + description = "Current operation only supports 'high-quality' datasets." code = 400 class DatasetNotInitializedError(BaseHTTPException): error_code = 'dataset_not_initialized' - description = "Dataset not initialized." + description = "The dataset is still being initialized or indexing. Please wait a moment." code = 400 class ArchivedDocumentImmutableError(BaseHTTPException): error_code = 'archived_document_immutable' - description = "Cannot process an archived document." + description = "The archived document is not editable." code = 403 class DatasetNameDuplicateError(BaseHTTPException): error_code = 'dataset_name_duplicate' - description = "Dataset name already exists." + description = "The dataset name already exists. Please modify your dataset name." code = 409 @@ -57,17 +57,17 @@ class InvalidActionError(BaseHTTPException): class DocumentAlreadyFinishedError(BaseHTTPException): error_code = 'document_already_finished' - description = "Document already finished." + description = "The document has been processed. Please refresh the page or go to the document details." code = 400 class DocumentIndexingError(BaseHTTPException): error_code = 'document_indexing' - description = "Document indexing." + description = "The document is being processed and cannot be edited." code = 400 class InvalidMetadataError(BaseHTTPException): error_code = 'invalid_metadata' - description = "Invalid metadata." + description = "The metadata content is incorrect. Please check and verify." code = 400 diff --git a/api/controllers/console/datasets/hit_testing.py b/api/controllers/console/datasets/hit_testing.py index 16bb571df..771d49045 100644 --- a/api/controllers/console/datasets/hit_testing.py +++ b/api/controllers/console/datasets/hit_testing.py @@ -6,9 +6,12 @@ from werkzeug.exceptions import InternalServerError, NotFound, Forbidden import services from controllers.console import api +from controllers.console.app.error import ProviderNotInitializeError, ProviderQuotaExceededError, \ + ProviderModelCurrentlyNotSupportError from controllers.console.datasets.error import HighQualityDatasetOnlyError, DatasetNotInitializedError from controllers.console.setup import setup_required from controllers.console.wraps import account_initialization_required +from core.llm.error import ProviderTokenNotInitError, QuotaExceededError, ModelCurrentlyNotSupportError from libs.helper import TimestampField from services.dataset_service import DatasetService from services.hit_testing_service import HitTestingService @@ -92,6 +95,12 @@ class HitTestingApi(Resource): return {"query": response['query'], 'records': marshal(response['records'], hit_testing_record_fields)} except services.errors.index.IndexNotInitializedError: raise DatasetNotInitializedError() + except ProviderTokenNotInitError: + raise ProviderNotInitializeError() + except QuotaExceededError: + raise ProviderQuotaExceededError() + except ModelCurrentlyNotSupportError: + raise ProviderModelCurrentlyNotSupportError() except Exception as e: logging.exception("Hit testing failed.") raise InternalServerError(str(e)) diff --git a/api/controllers/console/error.py b/api/controllers/console/error.py index 3040423d7..e563364f2 100644 --- a/api/controllers/console/error.py +++ b/api/controllers/console/error.py @@ -3,13 +3,14 @@ from libs.exception import BaseHTTPException class AlreadySetupError(BaseHTTPException): error_code = 'already_setup' - description = "Application already setup." + description = "Dify has been successfully installed. Please refresh the page or return to the dashboard homepage." code = 403 class NotSetupError(BaseHTTPException): error_code = 'not_setup' - description = "Application not setup." + description = "Dify has not been initialized and installed yet. " \ + "Please proceed with the initialization and installation process first." code = 401 diff --git a/api/controllers/console/workspace/error.py b/api/controllers/console/workspace/error.py index c5e3a3fb6..cb744232e 100644 --- a/api/controllers/console/workspace/error.py +++ b/api/controllers/console/workspace/error.py @@ -21,11 +21,11 @@ class InvalidInvitationCodeError(BaseHTTPException): class AccountAlreadyInitedError(BaseHTTPException): error_code = 'account_already_inited' - description = "Account already inited." + description = "The account has been initialized. Please refresh the page." code = 400 class AccountNotInitializedError(BaseHTTPException): error_code = 'account_not_initialized' - description = "Account not initialized." + description = "The account has not been initialized yet. Please proceed with the initialization process first." code = 400 diff --git a/api/controllers/console/workspace/providers.py b/api/controllers/console/workspace/providers.py index dc9e9c45f..87dad0d93 100644 --- a/api/controllers/console/workspace/providers.py +++ b/api/controllers/console/workspace/providers.py @@ -90,8 +90,8 @@ class ProviderTokenApi(Resource): configs=args['token'] ) token_is_valid = True - except ValidateFailedError: - token_is_valid = False + except ValidateFailedError as ex: + raise ValueError(str(ex)) base64_encrypted_token = ProviderService.get_encrypted_token( tenant=current_user.current_tenant, diff --git a/api/controllers/service_api/app/error.py b/api/controllers/service_api/app/error.py index c59f570ef..b7f6e0f6f 100644 --- a/api/controllers/service_api/app/error.py +++ b/api/controllers/service_api/app/error.py @@ -4,43 +4,45 @@ from libs.exception import BaseHTTPException class AppUnavailableError(BaseHTTPException): error_code = 'app_unavailable' - description = "App unavailable." + description = "App unavailable, please check your app configurations." code = 400 class NotCompletionAppError(BaseHTTPException): error_code = 'not_completion_app' - description = "Not Completion App" + description = "Please check if your Completion app mode matches the right API route." code = 400 class NotChatAppError(BaseHTTPException): error_code = 'not_chat_app' - description = "Not Chat App" + description = "Please check if your Chat app mode matches the right API route." code = 400 class ConversationCompletedError(BaseHTTPException): error_code = 'conversation_completed' - description = "Conversation Completed." + description = "The conversation has ended. Please start a new conversation." code = 400 class ProviderNotInitializeError(BaseHTTPException): error_code = 'provider_not_initialize' - description = "Provider Token not initialize." + description = "No valid model provider credentials found. " \ + "Please go to Settings -> Model Provider to complete your provider credentials." code = 400 class ProviderQuotaExceededError(BaseHTTPException): error_code = 'provider_quota_exceeded' - description = "Provider quota exceeded." + description = "Your quota for Dify Hosted OpenAI has been exhausted. " \ + "Please go to Settings -> Model Provider to complete your own provider credentials." code = 400 class ProviderModelCurrentlyNotSupportError(BaseHTTPException): error_code = 'model_currently_not_support' - description = "GPT-4 currently not support." + description = "Dify Hosted OpenAI trial currently not support the GPT-4 model." code = 400 diff --git a/api/controllers/service_api/dataset/error.py b/api/controllers/service_api/dataset/error.py index d231e0b40..2131fe0ba 100644 --- a/api/controllers/service_api/dataset/error.py +++ b/api/controllers/service_api/dataset/error.py @@ -16,5 +16,5 @@ class DocumentIndexingError(BaseHTTPException): class DatasetNotInitedError(BaseHTTPException): error_code = 'dataset_not_inited' - description = "Dataset not inited." + description = "The dataset is still being initialized or indexing. Please wait a moment." code = 403 diff --git a/api/controllers/web/error.py b/api/controllers/web/error.py index ea72422a1..fdfe36f6d 100644 --- a/api/controllers/web/error.py +++ b/api/controllers/web/error.py @@ -4,43 +4,45 @@ from libs.exception import BaseHTTPException class AppUnavailableError(BaseHTTPException): error_code = 'app_unavailable' - description = "App unavailable." + description = "App unavailable, please check your app configurations." code = 400 class NotCompletionAppError(BaseHTTPException): error_code = 'not_completion_app' - description = "Not Completion App" + description = "Please check if your Completion app mode matches the right API route." code = 400 class NotChatAppError(BaseHTTPException): error_code = 'not_chat_app' - description = "Not Chat App" + description = "Please check if your Chat app mode matches the right API route." code = 400 class ConversationCompletedError(BaseHTTPException): error_code = 'conversation_completed' - description = "Conversation Completed." + description = "The conversation has ended. Please start a new conversation." code = 400 class ProviderNotInitializeError(BaseHTTPException): error_code = 'provider_not_initialize' - description = "Provider Token not initialize." + description = "No valid model provider credentials found. " \ + "Please go to Settings -> Model Provider to complete your provider credentials." code = 400 class ProviderQuotaExceededError(BaseHTTPException): error_code = 'provider_quota_exceeded' - description = "Provider quota exceeded." + description = "Your quota for Dify Hosted OpenAI has been exhausted. " \ + "Please go to Settings -> Model Provider to complete your own provider credentials." code = 400 class ProviderModelCurrentlyNotSupportError(BaseHTTPException): error_code = 'model_currently_not_support' - description = "GPT-4 currently not support." + description = "Dify Hosted OpenAI trial currently not support the GPT-4 model." code = 400 @@ -52,11 +54,11 @@ class CompletionRequestError(BaseHTTPException): class AppMoreLikeThisDisabledError(BaseHTTPException): error_code = 'app_more_like_this_disabled' - description = "More like this disabled." + description = "The 'More like this' feature is disabled. Please refresh your page." code = 403 class AppSuggestedQuestionsAfterAnswerDisabledError(BaseHTTPException): error_code = 'app_suggested_questions_after_answer_disabled' - description = "Function Suggested questions after answer disabled." + description = "The 'Suggested Questions After Answer' feature is disabled. Please refresh your page." code = 403 diff --git a/api/core/index/index_builder.py b/api/core/index/index_builder.py index 7f0486546..05f08075d 100644 --- a/api/core/index/index_builder.py +++ b/api/core/index/index_builder.py @@ -46,3 +46,15 @@ class IndexBuilder: prompt_helper=prompt_helper, embed_model=OpenAIEmbedding(**model_credentials), ) + + @classmethod + def get_fake_llm_service_context(cls, tenant_id: str) -> ServiceContext: + llm = LLMBuilder.to_llm( + tenant_id=tenant_id, + model_name='fake' + ) + + return ServiceContext.from_defaults( + llm_predictor=LLMPredictor(llm=llm), + embed_model=OpenAIEmbedding() + ) diff --git a/api/core/index/vector_index.py b/api/core/index/vector_index.py index f9d8542a8..fa1c93cc0 100644 --- a/api/core/index/vector_index.py +++ b/api/core/index/vector_index.py @@ -83,7 +83,7 @@ class VectorIndex: if not self._dataset.index_struct_dict: return - service_context = IndexBuilder.get_default_service_context(tenant_id=self._dataset.tenant_id) + service_context = IndexBuilder.get_fake_llm_service_context(tenant_id=self._dataset.tenant_id) index = vector_store.get_index( service_context=service_context, @@ -101,7 +101,7 @@ class VectorIndex: if not self._dataset.index_struct_dict: return - service_context = IndexBuilder.get_default_service_context(tenant_id=self._dataset.tenant_id) + service_context = IndexBuilder.get_fake_llm_service_context(tenant_id=self._dataset.tenant_id) index = vector_store.get_index( service_context=service_context, diff --git a/api/core/llm/provider/azure_provider.py b/api/core/llm/provider/azure_provider.py index d68ed3ccc..bd44a0cc4 100644 --- a/api/core/llm/provider/azure_provider.py +++ b/api/core/llm/provider/azure_provider.py @@ -1,22 +1,24 @@ import json +import logging from typing import Optional, Union import requests from core.llm.provider.base import BaseProvider +from core.llm.provider.errors import ValidateFailedError from models.provider import ProviderName class AzureProvider(BaseProvider): - def get_models(self, model_id: Optional[str] = None) -> list[dict]: - credentials = self.get_credentials(model_id) + def get_models(self, model_id: Optional[str] = None, credentials: Optional[dict] = None) -> list[dict]: + credentials = self.get_credentials(model_id) if not credentials else credentials url = "{}/openai/deployments?api-version={}".format( - credentials.get('openai_api_base'), - credentials.get('openai_api_version') + str(credentials.get('openai_api_base')), + str(credentials.get('openai_api_version')) ) headers = { - "api-key": credentials.get('openai_api_key'), + "api-key": str(credentials.get('openai_api_key')), "content-type": "application/json; charset=utf-8" } @@ -29,8 +31,10 @@ class AzureProvider(BaseProvider): 'name': '{} ({})'.format(deployment['id'], deployment['model']) } for deployment in result['data'] if deployment['status'] == 'succeeded'] else: - # TODO: optimize in future - raise Exception('Failed to get deployments from Azure OpenAI. Status code: {}'.format(response.status_code)) + if response.status_code == 401: + raise AzureAuthenticationError() + else: + raise AzureRequestFailedError('Failed to request Azure OpenAI. Status code: {}'.format(response.status_code)) def get_credentials(self, model_id: Optional[str] = None) -> dict: """ @@ -38,7 +42,7 @@ class AzureProvider(BaseProvider): """ config = self.get_provider_api_key(model_id=model_id) config['openai_api_type'] = 'azure' - config['deployment_name'] = model_id.replace('.', '') + config['deployment_name'] = model_id.replace('.', '') if model_id else None return config def get_provider_name(self): @@ -54,7 +58,7 @@ class AzureProvider(BaseProvider): config = { 'openai_api_type': 'azure', 'openai_api_version': '2023-03-15-preview', - 'openai_api_base': 'https://.openai.azure.com/', + 'openai_api_base': '', 'openai_api_key': '' } @@ -63,7 +67,7 @@ class AzureProvider(BaseProvider): config = { 'openai_api_type': 'azure', 'openai_api_version': '2023-03-15-preview', - 'openai_api_base': 'https://.openai.azure.com/', + 'openai_api_base': '', 'openai_api_key': '' } @@ -80,8 +84,23 @@ class AzureProvider(BaseProvider): """ Validates the given config. """ - # TODO: implement - pass + try: + if not isinstance(config, dict): + raise ValueError('Config must be a object.') + + if 'openai_api_version' not in config: + config['openai_api_version'] = '2023-03-15-preview' + + self.get_models(credentials=config) + except AzureAuthenticationError: + raise ValidateFailedError('Azure OpenAI Credentials validation failed, please check your API Key.') + except requests.ConnectionError: + raise ValidateFailedError('Azure OpenAI Credentials validation failed, please check your API Base Endpoint.') + except AzureRequestFailedError as ex: + raise ValidateFailedError('Azure OpenAI Credentials validation failed, error: {}.'.format(str(ex))) + except Exception as ex: + logging.exception('Azure OpenAI Credentials validation failed') + raise ex def get_encrypted_token(self, config: Union[dict | str]): """ @@ -101,3 +120,11 @@ class AzureProvider(BaseProvider): config = json.loads(token) config['openai_api_key'] = self.decrypt_token(config['openai_api_key']) return config + + +class AzureAuthenticationError(Exception): + pass + + +class AzureRequestFailedError(Exception): + pass diff --git a/web/i18n/lang/common.en.ts b/web/i18n/lang/common.en.ts index 96304d267..fa73fbd8a 100644 --- a/web/i18n/lang/common.en.ts +++ b/web/i18n/lang/common.en.ts @@ -149,7 +149,7 @@ const translation = { invalidApiKey: 'Invalid API key', azure: { apiBase: 'API Base', - apiBasePlaceholder: 'The API Base URL of your Azure OpenAI Resource.', + apiBasePlaceholder: 'The API Base URL of your Azure OpenAI Endpoint.', apiKey: 'API Key', apiKeyPlaceholder: 'Enter your API key here', helpTip: 'Learn Azure OpenAI Service',