| @@ -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 | |||
| @@ -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 | |||
| @@ -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, | |||
| @@ -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 | |||
| @@ -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)) | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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, | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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() | |||
| ) | |||
| @@ -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, | |||
| @@ -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://<your-domain-prefix>.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://<your-domain-prefix>.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 | |||
| @@ -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', | |||