mirror of
https://github.com/langgenius/dify.git
synced 2026-09-29 17:07:38 +08:00
251 lines
9.9 KiB
Python
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
|