diff --git a/src/dify_plugin/entities/model/provider.py b/src/dify_plugin/entities/model/provider.py index 1ae3ee39..d465ffef 100644 --- a/src/dify_plugin/entities/model/provider.py +++ b/src/dify_plugin/entities/model/provider.py @@ -65,10 +65,13 @@ class FormOption(BaseModel): value: str show_on: list[FormShowOnObject] = Field(default_factory=list) - def __init__(self, **data: object) -> None: - super().__init__(**data) - if not self.label: - self.label = I18nObject(en_us=self.value) + @model_validator(mode="before") + @classmethod + def validate_label(cls, data: dict) -> dict: + if isinstance(data, dict) and not data.get("label") and "value" in data: + data["label"] = I18nObject(en_US=str(data["value"])) + + return data @docs( diff --git a/src/dify_plugin/entities/model/schema.py b/src/dify_plugin/entities/model/schema.py index a5b6ce47..4a920cf2 100644 --- a/src/dify_plugin/entities/model/schema.py +++ b/src/dify_plugin/entities/model/schema.py @@ -304,7 +304,7 @@ class ProviderModel(BaseModel): @classmethod def validate_label(cls, data: dict) -> dict: if isinstance(data, dict) and not data.get("label"): - data["label"] = I18nObject(en_us=data["model"]) + data["label"] = I18nObject(en_US=data["model"]) return data @@ -369,7 +369,7 @@ def validate_label(cls, data: dict) -> dict: pass if not data.get("label"): - data["label"] = I18nObject(en_us=data["name"]) + data["label"] = I18nObject(en_US=data["name"]) return data diff --git a/src/dify_plugin/entities/parameters.py b/src/dify_plugin/entities/parameters.py index 5e1e7006..79253ba5 100644 --- a/src/dify_plugin/entities/parameters.py +++ b/src/dify_plugin/entities/parameters.py @@ -17,27 +17,27 @@ class I18nObject(BaseModel): validate_by_name=True, ) - zh_hans: str | None = Field(default=None, alias="zh_Hans") - pt_br: str | None = Field(default=None, alias="pt_BR") - ja_jp: str | None = Field(default=None, alias="ja_JP") - en_us: str = Field(alias="en_US") + zh_Hans: str | None = Field(default=None) # noqa: N815 + pt_BR: str | None = Field(default=None) # noqa: N815 + ja_JP: str | None = Field(default=None) # noqa: N815 + en_US: str # noqa: N815 @model_validator(mode="after") def fill_missing_translations(self) -> "I18nObject": - if not self.zh_hans: - self.zh_hans = self.en_us - if not self.pt_br: - self.pt_br = self.en_us - if not self.ja_jp: - self.ja_jp = self.en_us + if not self.zh_Hans: + self.zh_Hans = self.en_US + if not self.pt_BR: + self.pt_BR = self.en_US + if not self.ja_JP: + self.ja_JP = self.en_US return self def to_dict(self) -> dict: return { - "zh_Hans": self.zh_hans, - "en_US": self.en_us, - "pt_BR": self.pt_br, - "ja_JP": self.ja_jp, + "zh_Hans": self.zh_Hans, + "en_US": self.en_US, + "pt_BR": self.pt_BR, + "ja_JP": self.ja_JP, } diff --git a/src/dify_plugin/interfaces/model/ai_model.py b/src/dify_plugin/interfaces/model/ai_model.py index 663ea132..332d0393 100644 --- a/src/dify_plugin/interfaces/model/ai_model.py +++ b/src/dify_plugin/interfaces/model/ai_model.py @@ -292,28 +292,28 @@ def _get_customizable_model_schema( parameter_rule.required = default_parameter_rule["required"] if not parameter_rule.help and "help" in default_parameter_rule: parameter_rule.help = I18nObject( - en_us=default_parameter_rule["help"]["en_US"], + en_US=default_parameter_rule["help"]["en_US"], ) if ( parameter_rule.help - and not parameter_rule.help.en_us + and not parameter_rule.help.en_US and ( "help" in default_parameter_rule and "en_US" in default_parameter_rule["help"] ) ): - parameter_rule.help.en_us = default_parameter_rule["help"][ + parameter_rule.help.en_US = default_parameter_rule["help"][ "en_US" ] if ( parameter_rule.help - and not parameter_rule.help.zh_hans + and not parameter_rule.help.zh_Hans and ( "help" in default_parameter_rule and "zh_Hans" in default_parameter_rule["help"] ) ): - parameter_rule.help.zh_hans = default_parameter_rule[ + parameter_rule.help.zh_Hans = default_parameter_rule[ "help" ].get("zh_Hans", default_parameter_rule["help"]["en_US"]) except ValueError: diff --git a/src/dify_plugin/interfaces/model/openai_compatible/llm.py b/src/dify_plugin/interfaces/model/openai_compatible/llm.py index 0d811811..ce05dfb1 100644 --- a/src/dify_plugin/interfaces/model/openai_compatible/llm.py +++ b/src/dify_plugin/interfaces/model/openai_compatible/llm.py @@ -363,7 +363,7 @@ def get_customizable_model_schema( entity = AIModelEntity( model=model, - label=I18nObject(en_us=model), + label=I18nObject(en_US=model), model_type=ModelType.LLM, fetch_from=FetchFrom.CUSTOMIZABLE_MODEL, features=features, @@ -376,16 +376,16 @@ def get_customizable_model_schema( parameter_rules=[ ParameterRule( name=DefaultParameterName.TEMPERATURE.value, - label=I18nObject(en_us="Temperature", zh_hans="温度"), + label=I18nObject(en_US="Temperature", zh_Hans="温度"), help=I18nObject( - en_us=( + en_US=( "Kernel sampling threshold. Used to determine the " "randomness of the results." "The higher the value, the stronger the randomness." "The higher the possibility of getting different " "answers to the same question." ), - zh_hans=( + zh_Hans=( "核采样阈值。用于决定结果随机性,取值越高随机性越强即" "相同的问题得到的不同答案的可能性越高。" ), @@ -398,9 +398,9 @@ def get_customizable_model_schema( ), ParameterRule( name=DefaultParameterName.TOP_P.value, - label=I18nObject(en_us="Top P", zh_hans="Top P"), + label=I18nObject(en_US="Top P", zh_Hans="Top P"), help=I18nObject( - en_us=( + en_US=( "The probability threshold of the nucleus sampling " "method during the generation process." "The larger the value is, the higher the randomness " @@ -408,7 +408,7 @@ def get_customizable_model_schema( "The smaller the value is, the higher the certainty " "of generation will be." ), - zh_hans=( + zh_Hans=( "生成过程中核采样方法概率阈值。取值越大,生成的随机性" "越高;取值越小,生成的确定性越高。" ), @@ -421,15 +421,15 @@ def get_customizable_model_schema( ), ParameterRule( name=DefaultParameterName.FREQUENCY_PENALTY.value, - label=I18nObject(en_us="Frequency Penalty", zh_hans="频率惩罚"), + label=I18nObject(en_US="Frequency Penalty", zh_Hans="频率惩罚"), help=I18nObject( - en_us=( + en_US=( "For controlling the repetition rate of words used " "by the model." "Increasing this can reduce the repetition of the " "same words in the model's output." ), - zh_hans=( + zh_Hans=( "用于控制模型已使用字词的重复率。 提高此项可以降低模型在" "输出中重复相同字词的重复度。" ), @@ -441,15 +441,15 @@ def get_customizable_model_schema( ), ParameterRule( name=DefaultParameterName.PRESENCE_PENALTY.value, - label=I18nObject(en_us="Presence Penalty", zh_hans="存在惩罚"), + label=I18nObject(en_US="Presence Penalty", zh_Hans="存在惩罚"), help=I18nObject( - en_us=( + en_US=( "Used to control the repetition rate when " "generating models." "Increasing this can reduce the repetition rate " "of model generation." ), - zh_hans="用于控制模型生成时的重复度。提高此项可以降低模型生成的重复度。", + zh_Hans="用于控制模型生成时的重复度。提高此项可以降低模型生成的重复度。", ), type=ParameterType.FLOAT, default=float(credentials.get("presence_penalty", 0)), @@ -458,10 +458,10 @@ def get_customizable_model_schema( ), ParameterRule( name=DefaultParameterName.MAX_TOKENS.value, - label=I18nObject(en_us="Max Tokens", zh_hans="最大标记"), + label=I18nObject(en_US="Max Tokens", zh_Hans="最大标记"), help=I18nObject( - en_us="Maximum length of tokens for the model response.", - zh_hans="模型回答的tokens的最大长度。", + en_US="Maximum length of tokens for the model response.", + zh_Hans="模型回答的tokens的最大长度。", ), type=ParameterType.INT, default=512, diff --git a/src/dify_plugin/interfaces/model/openai_compatible/rerank.py b/src/dify_plugin/interfaces/model/openai_compatible/rerank.py index 40ae8e73..76ad1b59 100644 --- a/src/dify_plugin/interfaces/model/openai_compatible/rerank.py +++ b/src/dify_plugin/interfaces/model/openai_compatible/rerank.py @@ -176,7 +176,7 @@ def get_customizable_model_schema( del credentials return AIModelEntity( model=model, - label=I18nObject(en_us=model), + label=I18nObject(en_US=model), model_type=ModelType.RERANK, fetch_from=FetchFrom.CUSTOMIZABLE_MODEL, model_properties={}, diff --git a/src/dify_plugin/interfaces/model/openai_compatible/text_embedding.py b/src/dify_plugin/interfaces/model/openai_compatible/text_embedding.py index 21b2e046..032c9b97 100644 --- a/src/dify_plugin/interfaces/model/openai_compatible/text_embedding.py +++ b/src/dify_plugin/interfaces/model/openai_compatible/text_embedding.py @@ -217,7 +217,7 @@ def get_customizable_model_schema( """ return AIModelEntity( model=model, - label=I18nObject(en_us=model), + label=I18nObject(en_US=model), model_type=ModelType.TEXT_EMBEDDING, fetch_from=FetchFrom.CUSTOMIZABLE_MODEL, model_properties={ diff --git a/src/dify_plugin/interfaces/model/openai_compatible/tts.py b/src/dify_plugin/interfaces/model/openai_compatible/tts.py index b61608c1..eed873bf 100644 --- a/src/dify_plugin/interfaces/model/openai_compatible/tts.py +++ b/src/dify_plugin/interfaces/model/openai_compatible/tts.py @@ -152,7 +152,7 @@ def get_customizable_model_schema( return AIModelEntity( model=model, - label=I18nObject(en_us=model), + label=I18nObject(en_US=model), fetch_from=FetchFrom.CUSTOMIZABLE_MODEL, model_type=ModelType.TTS, model_properties={ diff --git a/tests/entities/models/test_provider.py b/tests/entities/models/test_provider.py new file mode 100644 index 00000000..042d2a37 --- /dev/null +++ b/tests/entities/models/test_provider.py @@ -0,0 +1,9 @@ +from dify_plugin.entities.model.provider import FormOption + + +def test_form_option_uses_value_as_default_label() -> None: + option = FormOption(value="gpt-4") + + assert option.label.en_US == "gpt-4" + assert option.label.zh_Hans == "gpt-4" + assert option.value == "gpt-4" diff --git a/tests/entities/test_parameters.py b/tests/entities/test_parameters.py new file mode 100644 index 00000000..7c940d76 --- /dev/null +++ b/tests/entities/test_parameters.py @@ -0,0 +1,48 @@ +import pytest +from pydantic import ValidationError + +from dify_plugin.entities import I18nObject + + +def test_i18n_object_uses_legacy_python_attribute_names() -> None: + i18n = I18nObject.model_validate({ + "en_US": "English", + "zh_Hans": "简体中文", + "pt_BR": "Português", + "ja_JP": "日本語", + }) + + assert i18n.en_US == "English" + assert i18n.zh_Hans == "简体中文" + assert i18n.pt_BR == "Português" + assert i18n.ja_JP == "日本語" + assert i18n.model_dump() == { + "en_US": "English", + "zh_Hans": "简体中文", + "pt_BR": "Português", + "ja_JP": "日本語", + } + + +def test_i18n_object_fills_missing_translations_from_english() -> None: + i18n = I18nObject(en_US="English") + + assert i18n.en_US == "English" + assert i18n.zh_Hans == "English" + assert i18n.pt_BR == "English" + assert i18n.ja_JP == "English" + + assert i18n.to_dict() == { + "en_US": "English", + "zh_Hans": "English", + "pt_BR": "English", + "ja_JP": "English", + } + + +def test_i18n_object_does_not_accept_new_python_attribute_names() -> None: + with pytest.raises(AttributeError): + _ = I18nObject(en_US="English").en_us + + with pytest.raises(ValidationError): + I18nObject(en_us="English") diff --git a/tests/interfaces/model/test_construct_model_provider.py b/tests/interfaces/model/test_construct_model_provider.py index 943d2c61..810dde27 100644 --- a/tests/interfaces/model/test_construct_model_provider.py +++ b/tests/interfaces/model/test_construct_model_provider.py @@ -19,7 +19,7 @@ def validate_provider_credentials(self, credentials: dict) -> None: provider_schema = ProviderEntity( provider="test", - label=I18nObject(en_us="test"), + label=I18nObject(en_US="test"), supported_model_types=[ModelType.LLM], configurate_methods=[], ) diff --git a/tests/interfaces/model/test_model_registry_get_model.py b/tests/interfaces/model/test_model_registry_get_model.py index 4b2aef36..58e716ca 100644 --- a/tests/interfaces/model/test_model_registry_get_model.py +++ b/tests/interfaces/model/test_model_registry_get_model.py @@ -42,7 +42,7 @@ def test_model_provider_get_model_instance_delegates_to_factory() -> None: provider_schema = ProviderEntity( provider="test", - label=I18nObject(en_us="test"), + label=I18nObject(en_US="test"), supported_model_types=[ModelType.LLM], configurate_methods=[], ) @@ -60,7 +60,7 @@ def test_model_provider_get_model_instance_get_multiple_instances() -> None: provider = MockModelProvider( provider_schemas=ProviderEntity( provider="test", - label=I18nObject(en_us="test"), + label=I18nObject(en_US="test"), supported_model_types=[ModelType.LLM], configurate_methods=[], ), diff --git a/tests/interfaces/model/utils.py b/tests/interfaces/model/utils.py index d4f191f8..3f42e743 100644 --- a/tests/interfaces/model/utils.py +++ b/tests/interfaces/model/utils.py @@ -14,7 +14,7 @@ def prepare_model_factory(models: Mapping[ModelType, type[AIModel]]) -> ModelFac return ModelFactory( ModelProviderConfiguration( provider="test", - label=I18nObject(en_us="test"), + label=I18nObject(en_US="test"), supported_model_types=list(models.keys()), configurate_methods=[], extra=ModelProviderConfigurationExtra( diff --git a/tests/interfaces/tool/test_costruct_tool.py b/tests/interfaces/tool/test_costruct_tool.py index 28b2621e..fd623d62 100644 --- a/tests/interfaces/tool/test_costruct_tool.py +++ b/tests/interfaces/tool/test_costruct_tool.py @@ -81,7 +81,7 @@ def _invoke( def _fetch_parameter_options(self, parameter: str) -> list[ParameterOption]: del parameter - return [ParameterOption(value="test", label=I18nObject(en_us="test"))] + return [ParameterOption(value="test", label=I18nObject(en_US="test"))] session = Session( session_id="test", @@ -95,5 +95,5 @@ def _fetch_parameter_options(self, parameter: str) -> list[ParameterOption]: session=session, ) assert tool.fetch_parameter_options("test") == [ - ParameterOption(value="test", label=I18nObject(en_us="test")) + ParameterOption(value="test", label=I18nObject(en_US="test")) ] diff --git a/tests/test_model_polling.py b/tests/test_model_polling.py index 67abc58c..788c7481 100644 --- a/tests/test_model_polling.py +++ b/tests/test_model_polling.py @@ -70,7 +70,7 @@ def prompt_messages(self) -> list[UserPromptMessage]: def model_entity(self) -> AIModelEntity: return AIModelEntity( model=self.model, - label=I18nObject(en_us=self.model), + label=I18nObject(en_US=self.model), model_type=ModelType.LLM, features=[ModelFeature.POLLING], fetch_from=FetchFrom.PREDEFINED_MODEL, diff --git a/tests/test_model_registry_get_model.py b/tests/test_model_registry_get_model.py index 111a167b..cee177e4 100644 --- a/tests/test_model_registry_get_model.py +++ b/tests/test_model_registry_get_model.py @@ -147,7 +147,7 @@ def mock_resolve_plugin_cls(self: PluginRegistration) -> None: # add MockLLM to models_mapping provider_configuration = ModelProviderConfiguration( provider="test", - label=I18nObject(zh_hans="test", en_us="test"), + label=I18nObject(zh_Hans="test", en_US="test"), models={}, supported_model_types=[ModelType.LLM], extra=ModelProviderConfigurationExtra( @@ -165,7 +165,7 @@ def mock_resolve_plugin_cls(self: PluginRegistration) -> None: MockModelProvider( provider_schemas=ProviderEntity( provider="test", - label=I18nObject(zh_hans="test", en_us="test"), + label=I18nObject(zh_Hans="test", en_US="test"), supported_model_types=[ModelType.LLM], configurate_methods=[], ), diff --git a/tests/test_trigger_factory.py b/tests/test_trigger_factory.py index e2489854..3a100446 100644 --- a/tests/test_trigger_factory.py +++ b/tests/test_trigger_factory.py @@ -105,8 +105,8 @@ def _fetch_parameter_options( del credentials del parameter return [ - ParameterOption(value="option1", label=I18nObject(en_us="Option 1")), - ParameterOption(value="option2", label=I18nObject(en_us="Option 2")), + ParameterOption(value="option1", label=I18nObject(en_US="Option 1")), + ParameterOption(value="option2", label=I18nObject(en_US="Option 2")), ] @@ -140,8 +140,8 @@ def test_trigger_factory_register_and_get_provider() -> None: identity=TriggerProviderIdentity( author="test", name="test_provider", - label=I18nObject(en_us="Test Provider"), - description=I18nObject(en_us="Test Provider Description"), + label=I18nObject(en_US="Test Provider"), + description=I18nObject(en_US="Test Provider Description"), ), subscription_constructor=TriggerSubscriptionConstructorConfiguration( parameters=[], @@ -162,16 +162,16 @@ def test_trigger_factory_register_and_get_provider() -> None: identity=EventIdentity( author="test", name="test_event", - label=I18nObject(en_us="Test Event"), + label=I18nObject(en_US="Test Event"), ), parameters=[ EventParameter( name="test_param", - label=I18nObject(en_us="Test Parameter"), + label=I18nObject(en_US="Test Parameter"), type=EventParameter.EventParameterType.STRING, ), ], - description=I18nObject(en_us="Human description"), + description=I18nObject(en_US="Human description"), extra=EventConfigurationExtra( python=EventConfigurationExtra.Python(source="test_event.py"), ), @@ -214,8 +214,8 @@ def test_trigger_factory_subscription_constructor() -> None: identity=TriggerProviderIdentity( author="test", name="test_provider", - label=I18nObject(en_us="Test Provider"), - description=I18nObject(en_us="Test Provider Description"), + label=I18nObject(en_US="Test Provider"), + description=I18nObject(en_US="Test Provider Description"), ), subscription_constructor=TriggerSubscriptionConstructorConfiguration( parameters=[], @@ -262,8 +262,8 @@ def test_trigger_factory_trigger_events() -> None: identity=TriggerProviderIdentity( author="test", name="test_provider", - label=I18nObject(en_us="Test Provider"), - description=I18nObject(en_us="Test Provider Description"), + label=I18nObject(en_US="Test Provider"), + description=I18nObject(en_US="Test Provider Description"), ), extra=TriggerProviderConfigurationExtra( python=TriggerProviderConfigurationExtra.Python(source="test_provider.py"), @@ -274,10 +274,10 @@ def test_trigger_factory_trigger_events() -> None: identity=EventIdentity( author="test", name="test_event", - label=I18nObject(en_us="Test Event"), + label=I18nObject(en_US="Test Event"), ), parameters=[], - description=I18nObject(en_us="Human description"), + description=I18nObject(en_US="Human description"), extra=EventConfigurationExtra( python=EventConfigurationExtra.Python(source="test_event.py"), ), @@ -343,8 +343,8 @@ def test_trigger_factory_error_handling() -> None: identity=TriggerProviderIdentity( author="test", name="test_provider", - label=I18nObject(en_us="Test Provider"), - description=I18nObject(en_us="Test Provider Description"), + label=I18nObject(en_US="Test Provider"), + description=I18nObject(en_US="Test Provider Description"), ), extra=TriggerProviderConfigurationExtra( python=TriggerProviderConfigurationExtra.Python(source="test_provider.py"),