from decimal import Decimal from enum import Enum from typing import Any from pydantic import BaseModel, ConfigDict, Field, model_validator from dify_plugin.core.documentation.schema_doc import docs from dify_plugin.entities import I18nObject @docs( description="The default parameter name", ) class DefaultParameterName(Enum): """ Enum class for parameter template variable. """ TEMPERATURE = "temperature" TOP_P = "top_p" TOP_K = "top_k" PRESENCE_PENALTY = "presence_penalty" FREQUENCY_PENALTY = "frequency_penalty" MAX_TOKENS = "max_tokens" RESPONSE_FORMAT = "response_format" JSON_SCHEMA = "json_schema" @classmethod def value_of(cls, value: Any) -> "DefaultParameterName": """ Get parameter name from value. :param value: parameter value :return: parameter name """ for name in cls: if name.value == value: return name raise ValueError(f"invalid parameter name {value}") PARAMETER_RULE_TEMPLATE: dict[DefaultParameterName, dict] = { DefaultParameterName.TEMPERATURE: { "label": { "en_US": "Temperature", "zh_Hans": "温度", }, "type": "float", "help": { "en_US": "Controls randomness. Lower temperature results in less random completions. As the temperature approaches zero, the model will become deterministic and repetitive. Higher temperature results in more random completions.", # noqa: E501 "zh_Hans": "温度控制随机性。较低的温度会导致较少的随机完成。随着温度接近零,模型将变得确定性和重复性。" "较高的温度会导致更多的随机完成。", }, "required": False, "default": 0.0, "min": 0.0, "max": 1.0, "precision": 2, }, DefaultParameterName.TOP_P: { "label": { "en_US": "Top P", "zh_Hans": "Top P", }, "type": "float", "help": { "en_US": "Controls diversity via nucleus sampling: " "0.5 means half of all likelihood-weighted options are considered.", "zh_Hans": "通过核心采样控制多样性:0.5表示考虑了一半的所有可能性加权选项。", }, "required": False, "default": 1.0, "min": 0.0, "max": 1.0, "precision": 2, }, DefaultParameterName.TOP_K: { "label": { "en_US": "Top K", "zh_Hans": "Top K", }, "type": "int", "help": { "en_US": "Limits the number of tokens to consider for each step by keeping only the k most likely tokens.", "zh_Hans": "通过只保留每一步中最可能的 k 个标记来限制要考虑的标记数量。", }, "required": False, "default": 50, "min": 1, "max": 100, "precision": 0, }, DefaultParameterName.PRESENCE_PENALTY: { "label": { "en_US": "Presence Penalty", "zh_Hans": "存在惩罚", }, "type": "float", "help": { "en_US": "Applies a penalty to the log-probability of tokens already in the text.", "zh_Hans": "对文本中已有的标记的对数概率施加惩罚。", }, "required": False, "default": 0.0, "min": 0.0, "max": 1.0, "precision": 2, }, DefaultParameterName.FREQUENCY_PENALTY: { "label": { "en_US": "Frequency Penalty", "zh_Hans": "频率惩罚", }, "type": "float", "help": { "en_US": "Applies a penalty to the log-probability of tokens that appear in the text.", "zh_Hans": "对文本中出现的标记的对数概率施加惩罚。", }, "required": False, "default": 0.0, "min": 0.0, "max": 1.0, "precision": 2, }, DefaultParameterName.MAX_TOKENS: { "label": { "en_US": "Max Tokens", "zh_Hans": "最大标记", }, "type": "int", "help": { "en_US": "Specifies the upper limit on the length of generated results. " "If the generated results are truncated, you can increase this parameter.", "zh_Hans": "指定生成结果长度的上限。如果生成结果截断,可以调大该参数。", }, "required": False, "default": 64, "min": 1, "max": 2048, "precision": 0, }, DefaultParameterName.RESPONSE_FORMAT: { "label": { "en_US": "Response Format", "zh_Hans": "回复格式", }, "type": "string", "help": { "en_US": "Set a response format, ensure the output from llm is a valid code block as possible, " "such as JSON, XML, etc.", "zh_Hans": "设置一个返回格式,确保llm的输出尽可能是有效的代码块,如JSON、XML等", }, "required": False, "options": ["JSON", "XML"], }, DefaultParameterName.JSON_SCHEMA: { "label": { "en_US": "JSON Schema", }, "type": "text", "help": { "en_US": "Set a response json schema will ensure LLM to adhere it.", "zh_Hans": "设置返回的json schema,llm将按照它返回", }, "required": False, }, } @docs( description="The model type", ) class ModelType(Enum): """ Enum class for model type. """ LLM = "llm" TEXT_EMBEDDING = "text-embedding" RERANK = "rerank" SPEECH2TEXT = "speech2text" MODERATION = "moderation" TTS = "tts" TEXT2IMG = "text2img" @docs( description="The fetch from", ) class FetchFrom(Enum): """ Enum class for fetch from. """ PREDEFINED_MODEL = "predefined-model" CUSTOMIZABLE_MODEL = "customizable-model" @docs( description="The model feature", ) class ModelFeature(Enum): """ Enum class for llm feature. """ TOOL_CALL = "tool-call" MULTI_TOOL_CALL = "multi-tool-call" AGENT_THOUGHT = "agent-thought" VISION = "vision" STREAM_TOOL_CALL = "stream-tool-call" DOCUMENT = "document" VIDEO = "video" AUDIO = "audio" STRUCTURED_OUTPUT = "structured-output" @docs( description="The parameter type", ) class ParameterType(Enum): """ Enum class for parameter type. """ FLOAT = "float" INT = "int" STRING = "string" BOOLEAN = "boolean" TEXT = "text" @docs( description="The model property key", ) class ModelPropertyKey(Enum): """ Enum class for model property key. """ MODE = "mode" CONTEXT_SIZE = "context_size" MAX_CHUNKS = "max_chunks" FILE_UPLOAD_LIMIT = "file_upload_limit" SUPPORTED_FILE_EXTENSIONS = "supported_file_extensions" MAX_CHARACTERS_PER_CHUNK = "max_characters_per_chunk" DEFAULT_VOICE = "default_voice" VOICES = "voices" WORD_LIMIT = "word_limit" AUDIO_TYPE = "audio_type" MAX_WORKERS = "max_workers" @docs( description="The provider model", ) class ProviderModel(BaseModel): """ Model class for provider model. """ model: str = Field(..., description="The model name") label: I18nObject = Field(..., description="The label of the model") model_type: ModelType = Field(..., description="The model type") features: list[ModelFeature] | None = Field(default=None, description="The features of the model") fetch_from: FetchFrom = Field(default=FetchFrom.PREDEFINED_MODEL, description="The fetch from") model_properties: dict[ModelPropertyKey, Any] = Field(..., description="The model properties") deprecated: bool = Field(default=False, description="Whether the model is deprecated") model_config = ConfigDict(protected_namespaces=()) """ use model as label """ @model_validator(mode="before") @classmethod def validate_label(cls, data: dict) -> dict: if isinstance(data, dict) and not data.get("label"): data["label"] = I18nObject(en_US=data["model"]) return data @docs( description="The parameter rule of the model", ) class ParameterRule(BaseModel): """ Model class for parameter rule. """ name: str = Field(..., description="The name of the parameter") use_template: str | None = Field(default=None, description="The template of the parameter") label: I18nObject = Field(..., description="The label of the parameter") type: ParameterType = Field(..., description="The type of the parameter") help: I18nObject | None = Field(default=None, description="The help of the parameter") required: bool = Field(default=False, description="Whether the parameter is required") default: Any | None = Field(default=None, description="The default value of the parameter") min: float | None = Field(default=None, description="The minimum value of the parameter") max: float | None = Field(default=None, description="The maximum value of the parameter") precision: int | None = Field(default=None, description="The precision of the parameter") options: list[str] = Field(default=[], description="The options of the parameter") @model_validator(mode="before") @classmethod def validate_label(cls, data: dict) -> dict: if isinstance(data, dict): # check if there is a template if "use_template" in data: try: default_parameter_name = DefaultParameterName.value_of(data["use_template"]) default_parameter_rule = PARAMETER_RULE_TEMPLATE.get(default_parameter_name) if not default_parameter_rule: raise Exception(f"Invalid model parameter rule name {default_parameter_name}") copy_default_parameter_rule = default_parameter_rule.copy() copy_default_parameter_rule.update(data) data = copy_default_parameter_rule except ValueError: pass if not data.get("label"): data["label"] = I18nObject(en_US=data["name"]) return data @docs( description="The price config", ) class PriceConfig(BaseModel): """ Model class for pricing info. """ input: Decimal = Field(..., description="Input price") output: Decimal | None = Field(default=None, description="Output price") unit: Decimal = Field(..., description="Unit, e.g. 0.0001 -> per 10000 tokens") currency: str = Field(..., description="Currency, e.g. USD") @docs( description="AI model entity", ) class AIModelEntity(ProviderModel): """ Model class for AI model. """ parameter_rules: list[ParameterRule] = [] pricing: PriceConfig | None = None class ModelUsage(BaseModel): pass class PriceType(Enum): """ Enum class for price type. """ INPUT = "input" OUTPUT = "output" class PriceInfo(BaseModel): """ Model class for price info. """ unit_price: Decimal = Field(..., description="The unit price, e.g. 0.000001") unit: Decimal = Field(..., description="The unit, e.g. 1000") total_amount: Decimal = Field(..., description="The total amount") currency: str = Field(..., description="The currency, e.g. USD") class BaseModelConfig(BaseModel): provider: str model: str model_type: ModelType model_config = ConfigDict(protected_namespaces=()) class EmbeddingInputType(Enum): """ Enum for embedding input type. """ DOCUMENT = "document" QUERY = "query"