dify/api/controllers/common/fields.py

508 lines
14 KiB
Python

from __future__ import annotations
from typing import Annotated, Any, Literal
from pydantic import BaseModel, ConfigDict, Field, RootModel, WithJsonSchema, computed_field
from fields.base import ResponseModel
from graphon.file import helpers as file_helpers
from models.model import IconType
type JSONValue = str | int | float | bool | None | dict[str, Any] | list[Any]
type JSONObject = dict[str, Any]
URLString = Annotated[str, WithJsonSchema({"format": "url", "type": "string"})]
UUIDString = Annotated[str, WithJsonSchema({"format": "uuid", "type": "string"})]
Int64 = Annotated[int, WithJsonSchema({"format": "int64", "type": "integer"})]
FloatNumber = Annotated[float, WithJsonSchema({"format": "float", "type": "number"})]
DoubleNumber = Annotated[float, WithJsonSchema({"format": "double", "type": "number"})]
DecimalString = Annotated[str, WithJsonSchema({"format": "decimal", "type": "string"})]
FeatureToggleObject = Annotated[
JSONObject,
WithJsonSchema({"type": "object", "properties": {"enabled": {"type": "boolean"}}}),
]
TextToSpeechObject = Annotated[
JSONObject,
WithJsonSchema(
{
"type": "object",
"properties": {
"enabled": {"type": "boolean"},
"voice": {"type": "string"},
"language": {"type": "string"},
"autoPlay": {"type": "string"},
},
}
),
]
FileUploadObject = Annotated[
JSONObject,
WithJsonSchema(
{
"type": "object",
"properties": {
"enabled": {"type": "boolean"},
"number_limits": {"type": "integer"},
"allowed_file_types": {
"type": "array",
"items": {
"type": "string",
"enum": ["document", "image", "audio", "video", "custom"],
},
},
"allowed_file_extensions": {"type": "array", "items": {"type": "string"}},
"allowed_file_upload_methods": {
"type": "array",
"items": {
"type": "string",
"enum": ["remote_url", "local_file"],
},
},
"image": {
"type": "object",
"properties": {
"enabled": {"type": "boolean"},
"number_limits": {"type": "integer"},
"detail": {"type": "string"},
"transfer_methods": {"type": "array", "items": {"type": "string"}},
},
},
},
}
),
]
USER_INPUT_FORM_TYPES = (
"text-input",
"select",
"paragraph",
"number",
"external_data_tool",
"file",
"file-list",
"checkbox",
"json_object",
)
USER_INPUT_FORM_ITEM_CONFIG_SCHEMA: dict[str, object] = {
"type": "object",
"required": ["label", "variable"],
"properties": {
"variable": {"type": "string"},
"label": {"type": "string"},
"description": {"type": "string"},
"required": {"type": "boolean"},
"hide": {"type": "boolean"},
# Defaults are typed by the form kind: strings, numbers, booleans,
# JSON objects, and file references are all valid public values.
"default": {},
"type": {"enum": list(USER_INPUT_FORM_TYPES), "type": "string"},
"max_length": {"anyOf": [{"type": "integer"}, {"type": "null"}]},
"options": {"type": "array", "items": {"type": "string"}},
"allowed_file_types": {"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}]},
"allowed_file_extensions": {"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}]},
"allowed_file_upload_methods": {"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}]},
"json_schema": {
"anyOf": [
{"type": "object", "additionalProperties": True},
{"type": "null"},
]
},
"config": {"type": "object", "additionalProperties": True},
},
}
UserInputFormList = Annotated[
list[JSONObject],
WithJsonSchema(
{
"type": "array",
"items": {
"type": "object",
"oneOf": [
{
"type": "object",
"additionalProperties": False,
"properties": {form_type: USER_INPUT_FORM_ITEM_CONFIG_SCHEMA},
"required": [form_type],
}
for form_type in USER_INPUT_FORM_TYPES
],
},
}
),
]
class SystemParameters(BaseModel):
image_file_size_limit: int
video_file_size_limit: int
audio_file_size_limit: int
file_size_limit: int
workflow_file_upload_limit: int
class BlockingUsageResponse(ResponseModel):
model_config = ConfigDict(extra="allow")
prompt_tokens: int | None = None
prompt_unit_price: DecimalString | None = None
prompt_price_unit: DecimalString | None = None
prompt_price: DecimalString | None = None
completion_tokens: int | None = None
completion_unit_price: DecimalString | None = None
completion_price_unit: DecimalString | None = None
completion_price: DecimalString | None = None
total_tokens: int | None = None
total_price: DecimalString | None = None
currency: str | None = None
latency: DoubleNumber | None = None
class BlockingRetrieverResourceResponse(ResponseModel):
model_config = ConfigDict(extra="allow")
id: UUIDString | None = None
message_id: UUIDString | None = None
position: int
dataset_id: UUIDString | None = None
dataset_name: str | None = None
document_id: UUIDString | None = None
document_name: str | None = None
data_source_type: str | None = None
segment_id: UUIDString | None = None
score: FloatNumber | None = None
hit_count: int | None = None
word_count: int | None = None
segment_position: int | None = None
index_node_hash: str | None = None
content: str | None = None
summary: str | None = None
created_at: Int64 | None = None
class BlockingMetadataResponse(ResponseModel):
model_config = ConfigDict(extra="allow")
usage: BlockingUsageResponse | None = None
retriever_resources: list[BlockingRetrieverResourceResponse] | None = None
class ChatMessageBlockingResponse(ResponseModel):
model_config = ConfigDict(extra="allow")
event: Literal["message"]
task_id: UUIDString
id: UUIDString
message_id: UUIDString
conversation_id: UUIDString
mode: str
answer: str
metadata: BlockingMetadataResponse = Field(description="Metadata including usage and retriever resources.")
created_at: Int64
class PauseReasonResponseBase(ResponseModel):
model_config = ConfigDict(extra="allow")
form_id: UUIDString | None = None
node_id: str | None = None
node_title: str | None = None
form_content: str | None = None
inputs: list[JSONObject] = Field(default_factory=list)
actions: list[JSONObject] = Field(default_factory=list)
display_in_ui: bool | None = None
resolved_default_values: dict[str, Any] = Field(default_factory=dict)
form_token: str | None = None
approval_channels: list[str] = Field(default_factory=list)
expiration_time: Int64 | None = None
message: str | None = None
class ChatPauseReasonResponse(PauseReasonResponseBase):
"""Public pause reason emitted by a blocking Chatflow execution."""
TYPE: str
class WorkflowPauseReasonResponse(PauseReasonResponseBase):
"""Public pause reason emitted by a blocking Workflow execution."""
TYPE: str
class ChatPausedBlockingDataResponse(ResponseModel):
model_config = ConfigDict(extra="allow")
id: UUIDString
mode: str
conversation_id: UUIDString
message_id: UUIDString
workflow_run_id: UUIDString
answer: str
metadata: BlockingMetadataResponse = Field(description="Metadata including usage and retriever resources.")
created_at: Int64
paused_nodes: list[str]
reasons: list[ChatPauseReasonResponse]
status: Literal["paused"]
elapsed_time: FloatNumber
total_tokens: int
total_steps: int
class ChatPausedBlockingResponse(ResponseModel):
model_config = ConfigDict(extra="allow")
event: Literal["workflow_paused"]
task_id: UUIDString
id: UUIDString
message_id: UUIDString
conversation_id: UUIDString
mode: str
answer: str
metadata: BlockingMetadataResponse = Field(description="Metadata including usage and retriever resources.")
created_at: Int64
workflow_run_id: UUIDString
data: ChatPausedBlockingDataResponse
class ChatBlockingResponse(
RootModel[
Annotated[
ChatMessageBlockingResponse | ChatPausedBlockingResponse,
Field(discriminator="event"),
]
]
):
"""Blocking chat response for a completed message or paused Chatflow."""
class CompletionBlockingResponse(ResponseModel):
model_config = ConfigDict(extra="allow")
event: str
task_id: UUIDString
id: UUIDString
message_id: UUIDString
mode: str
answer: str
metadata: BlockingMetadataResponse = Field(description="Metadata including usage and retriever resources.")
created_at: Int64
class WorkflowBlockingDataBase(ResponseModel):
model_config = ConfigDict(extra="allow")
id: UUIDString
workflow_id: UUIDString
outputs: dict[str, Any] | None
error: str | None
elapsed_time: FloatNumber
total_tokens: int
total_steps: int
created_at: Int64
finished_at: Int64 | None
class WorkflowFinishedBlockingDataResponse(WorkflowBlockingDataBase):
status: Literal["succeeded", "failed", "stopped", "partial-succeeded"]
class WorkflowPausedBlockingDataResponse(WorkflowBlockingDataBase):
status: Literal["paused"]
paused_nodes: list[str]
reasons: list[WorkflowPauseReasonResponse]
class WorkflowFinishedBlockingResponse(ResponseModel):
model_config = ConfigDict(extra="allow")
task_id: UUIDString
workflow_run_id: UUIDString
data: WorkflowFinishedBlockingDataResponse
class WorkflowPausedBlockingResponse(ResponseModel):
model_config = ConfigDict(extra="allow")
task_id: UUIDString
workflow_run_id: UUIDString
data: WorkflowPausedBlockingDataResponse
class WorkflowBlockingResponse(RootModel[WorkflowFinishedBlockingResponse | WorkflowPausedBlockingResponse]):
"""Blocking workflow response for a finished or paused execution."""
class SimpleResultResponse(ResponseModel):
result: str = Field(description="Operation result.")
class GeneratedAppResponse(RootModel[JSONValue]):
root: JSONValue
class EventStreamResponse(RootModel[str]):
root: str
class TextFileResponse(RootModel[str]):
root: str
class RedirectResponse(RootModel[str]):
root: str
class BinaryFileResponse(RootModel[bytes]):
root: bytes
class AudioBinaryResponse(RootModel[bytes]):
root: bytes
class AudioTranscriptResponse(ResponseModel):
text: str
class ValidationResultResponse(ResponseModel):
result: Literal["success", "error"]
error: str | None = None
class SimpleResultMessageResponse(ResponseModel):
result: str
message: str
class SimpleMessageResponse(ResponseModel):
message: str
class SimpleDataResponse(ResponseModel):
data: str
class SimpleResultDataResponse(ResponseModel):
result: str
data: str
class SimpleResultStringListResponse(ResponseModel):
result: str
data: list[str]
class SimpleResultOptionalDataResponse(ResponseModel):
result: str
data: str | None = None
class AccessTokenData(ResponseModel):
access_token: str
class AccessTokenResultResponse(ResponseModel):
result: str
data: AccessTokenData
class VerificationTokenResponse(ResponseModel):
is_valid: bool
email: str
token: str
class LoginStatusResponse(ResponseModel):
logged_in: bool
app_logged_in: bool
class AccessModeResponse(ResponseModel):
access_mode: str = Field(serialization_alias="accessMode", validation_alias="accessMode")
class BooleanResultResponse(ResponseModel):
result: bool
class SuccessResponse(ResponseModel):
success: bool
class UsageCheckResponse(ResponseModel):
is_using: bool
class UsageCountResponse(ResponseModel):
is_using: bool
count: int
class AvatarUrlResponse(ResponseModel):
avatar_url: str
class TextContentResponse(ResponseModel):
content: str
class AllowedExtensionsResponse(ResponseModel):
allowed_extensions: list[str]
class UrlResponse(ResponseModel):
url: str
class RedirectUrlResponse(ResponseModel):
redirect_url: str
class ApiBaseUrlResponse(ResponseModel):
api_base_url: str
class NewAppResponse(ResponseModel):
new_app_id: str
permission_keys: list[str] = Field(default_factory=list)
class Parameters(BaseModel):
opening_statement: str | None = None
suggested_questions: list[str]
suggested_questions_after_answer: FeatureToggleObject
speech_to_text: FeatureToggleObject
text_to_speech: TextToSpeechObject
retriever_resource: FeatureToggleObject
annotation_reply: FeatureToggleObject
more_like_this: FeatureToggleObject
user_input_form: UserInputFormList
sensitive_word_avoidance: FeatureToggleObject
file_upload: FileUploadObject
system_parameters: SystemParameters = Field(description="System-level parameter limits.")
class Site(BaseModel):
model_config = ConfigDict(from_attributes=True)
title: str
chat_color_theme: str | None = None
chat_color_theme_inverted: bool
icon_type: str | None = None
icon: str | None = None
icon_background: str | None = None
description: str | None = None
copyright: str | None = None
privacy_policy: str | None = None
input_placeholder: str | None = None
custom_disclaimer: str | None = None
default_language: str
show_workflow_steps: bool
use_icon_as_answer_icon: bool
@computed_field(return_type=URLString | None) # type: ignore
@property
def icon_url(self) -> str | None:
if self.icon and self.icon_type == IconType.IMAGE:
return file_helpers.get_signed_file_url(self.icon)
return None