Files
dify/api/controllers/console/explore/completion.py
T

251 lines
9.9 KiB
Python

import logging
from typing import Any, Literal
from uuid import UUID
from flask import Response
from flask_restx import Resource
from pydantic import BaseModel, Field, field_validator
from werkzeug.exceptions import InternalServerError, NotFound, Unauthorized
import services
from controllers.common.fields import SimpleResultResponse
from controllers.common.schema import register_response_schema_models, register_schema_models
from controllers.console.app.error import (
AppUnavailableError,
CompletionRequestError,
ConversationCompletedError,
ProviderModelCurrentlyNotSupportError,
ProviderNotInitializeError,
ProviderQuotaExceededError,
)
from controllers.console.explore.error import NotChatAppError, NotCompletionAppError
from controllers.console.explore.installed_app_admission import get_installed_app
from controllers.console.flask_admission import console_account_admission
from controllers.console.wraps import model_validate
from controllers.web.error import InvokeRateLimitError as InvokeRateLimitHttpError
from core.app.entities.app_invoke_entities import InvokeFrom
from core.errors.error import (
ModelCurrentlyNotSupportError,
ProviderTokenNotInitError,
QuotaExceededError,
)
from extensions.ext_application_services import application_services
from graphon.model_runtime.errors.invoke import InvokeError
from libs import helper
from machinery.context import RequestContext
from models.model import AppMode
from services.account_errors import AccountNotFoundError
from services.app_definition_query_service import AppDefinitionUnavailableError
from services.errors.llm import InvokeRateLimitError
from services.installed_app_access_service import InstalledAppNotFoundError, InstalledAppRef
from services.installed_app_generation_service import InstalledAppNotChatError, InstalledAppNotCompletionError
from .. import console_ns
logger = logging.getLogger(__name__)
class CompletionMessageExplorePayload(BaseModel):
inputs: dict[str, Any]
query: str = ""
files: list[dict[str, Any]] | None = Field(default=None)
response_mode: Literal["blocking", "streaming"] | None = None
retriever_from: str = Field(default="explore_app")
class ChatMessagePayload(BaseModel):
inputs: dict[str, Any]
query: str
files: list[dict[str, Any]] | None = Field(default=None)
conversation_id: str | None = None
parent_message_id: str | None = None
retriever_from: str = Field(default="explore_app")
@field_validator("conversation_id", "parent_message_id", mode="before")
@classmethod
def normalize_uuid(cls, value: str | UUID | None) -> str | None:
"""
Accept blank IDs and validate UUID format when provided.
"""
if not value:
return None
try:
return helper.uuid_value(value)
except ValueError as exc:
raise ValueError("must be a valid UUID") from exc
register_schema_models(console_ns, CompletionMessageExplorePayload, ChatMessagePayload)
register_response_schema_models(console_ns, SimpleResultResponse)
# define completion api for user
@console_ns.route(
"/installed-apps/<uuid:installed_app_id>/completion-messages",
endpoint="installed_app_completion",
)
class CompletionApi(Resource):
@console_ns.expect(console_ns.models[CompletionMessageExplorePayload.__name__])
@console_ns.response(200, "Success")
@console_account_admission()
@get_installed_app
@model_validate(CompletionMessageExplorePayload)
def post(
self,
req_data: CompletionMessageExplorePayload,
request_context: RequestContext,
installed_app: InstalledAppRef,
) -> Response:
try:
response = application_services().installed_app_generation.generate_completion(
installed_app=installed_app,
account_id=request_context.account_id,
args=req_data.model_dump(exclude_none=True),
)
# response-contract:ignore compact_generate_response
return helper.compact_generate_response(response)
except AppDefinitionUnavailableError:
raise AppUnavailableError() from None
except InstalledAppNotCompletionError:
raise NotCompletionAppError() from None
except InstalledAppNotFoundError:
raise NotFound("Installed app not found") from None
except AccountNotFoundError:
raise Unauthorized("Account no longer exists.") from None
except services.errors.conversation.ConversationNotExistsError:
raise NotFound("Conversation Not Exists.")
except services.errors.conversation.ConversationCompletedError:
raise ConversationCompletedError()
except services.errors.app_model_config.AppModelConfigBrokenError:
logger.exception("App model config broken.")
raise AppUnavailableError()
except ProviderTokenNotInitError as ex:
raise ProviderNotInitializeError(ex.description)
except QuotaExceededError:
raise ProviderQuotaExceededError()
except ModelCurrentlyNotSupportError:
raise ProviderModelCurrentlyNotSupportError()
except InvokeError as e:
raise CompletionRequestError(e.description)
except ValueError as e:
raise e
except Exception:
logger.exception("internal server error.")
raise InternalServerError()
@console_ns.route(
"/installed-apps/<uuid:installed_app_id>/completion-messages/<string:task_id>/stop",
endpoint="installed_app_stop_completion",
)
class CompletionStopApi(Resource):
@console_ns.response(200, "Success", console_ns.models[SimpleResultResponse.__name__])
@console_account_admission()
@get_installed_app
def post(
self, request_context: RequestContext, installed_app: InstalledAppRef, task_id: str
) -> tuple[dict[str, object], int]:
try:
app_mode = application_services().app_definitions.get_mode(installed_app.app_id)
except AppDefinitionUnavailableError:
raise AppUnavailableError() from None
if app_mode != AppMode.COMPLETION:
raise NotCompletionAppError()
application_services().app_tasks.stop_task(
task_id=task_id,
invoke_from=InvokeFrom.EXPLORE,
user_id=request_context.account_id,
app_mode=AppMode.value_of(app_mode),
)
return SimpleResultResponse(result="success").model_dump(mode="json"), 200
@console_ns.route(
"/installed-apps/<uuid:installed_app_id>/chat-messages",
endpoint="installed_app_chat_completion",
)
class ChatApi(Resource):
@console_ns.expect(console_ns.models[ChatMessagePayload.__name__])
@console_ns.response(200, "Success")
@console_account_admission()
@get_installed_app
@model_validate(ChatMessagePayload)
def post(
self,
req_data: ChatMessagePayload,
request_context: RequestContext,
installed_app: InstalledAppRef,
) -> Response:
try:
response = application_services().installed_app_generation.generate_chat(
installed_app=installed_app,
account_id=request_context.account_id,
args=req_data.model_dump(exclude_none=True),
)
# response-contract:ignore compact_generate_response
return helper.compact_generate_response(response)
except AppDefinitionUnavailableError:
raise AppUnavailableError() from None
except InstalledAppNotChatError:
raise NotChatAppError() from None
except InstalledAppNotFoundError:
raise NotFound("Installed app not found") from None
except AccountNotFoundError:
raise Unauthorized("Account no longer exists.") from None
except services.errors.conversation.ConversationNotExistsError:
raise NotFound("Conversation Not Exists.")
except services.errors.conversation.ConversationCompletedError:
raise ConversationCompletedError()
except services.errors.app_model_config.AppModelConfigBrokenError:
logger.exception("App model config broken.")
raise AppUnavailableError()
except ProviderTokenNotInitError as ex:
raise ProviderNotInitializeError(ex.description)
except QuotaExceededError:
raise ProviderQuotaExceededError()
except ModelCurrentlyNotSupportError:
raise ProviderModelCurrentlyNotSupportError()
except InvokeError as e:
raise CompletionRequestError(e.description)
except InvokeRateLimitError as ex:
raise InvokeRateLimitHttpError(ex.description)
except ValueError as e:
raise e
except Exception:
logger.exception("internal server error.")
raise InternalServerError()
@console_ns.route(
"/installed-apps/<uuid:installed_app_id>/chat-messages/<string:task_id>/stop",
endpoint="installed_app_stop_chat_completion",
)
class ChatStopApi(Resource):
@console_ns.response(200, "Success", console_ns.models[SimpleResultResponse.__name__])
@console_account_admission()
@get_installed_app
def post(
self, request_context: RequestContext, installed_app: InstalledAppRef, task_id: str
) -> tuple[dict[str, object], int]:
try:
mode = application_services().app_definitions.get_mode(installed_app.app_id)
except AppDefinitionUnavailableError:
raise AppUnavailableError() from None
app_mode = AppMode.value_of(mode)
if app_mode not in {AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT}:
raise NotChatAppError()
application_services().app_tasks.stop_task(
task_id=task_id,
invoke_from=InvokeFrom.EXPLORE,
user_id=request_context.account_id,
app_mode=app_mode,
)
return SimpleResultResponse(result="success").model_dump(mode="json"), 200