def get_customizable_model_schema( self, model: str, credentials: dict ) -> AIModelEntity: schema = {item.model: item for item in self.predefined_models()}.get( _base_model(model) ) if schema is None: # Fallback for custom / non-OpenAI base models served via an # OpenAI-compatible endpoint (e.g. Qwen, DeepSeek, local vLLM): # clone a chat-capable predefined schema so the model is listable # and can be set as the default model. template = next( (m for m in self.predefined_models() if m.model_properties.get("mode") == "chat"), None, ) if template is None: template = self.predefined_models()[0] props = dict(template.model_properties) mp = (credentials or {}).get("model_properties") or {} # Align context_size to the actual vLLM --max-model-len (65536) so Dify # never lets prompts exceed what the served model can accept. The # credential-update API does NOT persist model_properties for this # provider, so we hardcode the safe ceiling here as the default and # still honor an explicit override when one is supplied. VLLM_MAX_CTX = 64000 try: props["context_size"] = int(mp["context_size"]) if "context_size" in mp else VLLM_MAX_CTX except (ValueError, TypeError): props["context_size"] = VLLM_MAX_CTX # NOTE: do NOT put max_tokens into model_properties. # Dify API-side ModelPropertyKey only allows {mode, context_size, ...} # and does NOT include MAX_TOKENS; adding it breaks Pydantic validation # when the API parses the schema from plugin_daemon. # max_tokens is already exposed as a parameter_rule by the cloned template. schema = template.model_copy(deep=True, update={"model_properties": props}) return schema.model_copy( deep=True, update={ "model": model, "label": I18nObject(zh_hans=model, en_us=model), "fetch_from": FetchFrom.CUSTOMIZABLE_MODEL, }, )