customizable-model return error: Model is not configured #16532

Closed
opened 2026-02-21 19:26:32 -05:00 by yindo · 1 comment
Owner

Originally created by @yyyykl on GitHub (Aug 28, 2025).

Self Checks

  • I have read the Contributing Guide and Language Policy.
  • This is only for bug report, if you would like to ask a question, please head to Discussions.
  • I have searched for existing issues search for existing issues, including closed ones.
  • I confirm that I am using English to submit this report, otherwise it will be closed.
  • 【中文用户 & Non English User】请使用英语提交,否则会被关闭 :)
  • Please do not modify this template :) and fill in all the required fields.

Dify version

1.8.0

Cloud or Self Hosted

Self Hosted (Source)

Steps to reproduce

I created a llm plugin, when i run single llm node, it return "Model is not configured"

Image Image

provider.yaml

provider: ai_lab
label:
  en_US: "AiLab"
description:
  en_US: "Models provided by ai_lab."
  zh_Hans: "AiLab 提供的模型。"
icon_small:
  en_US: "icon.svg"
icon_large:
  en_US: "icon.svg"
background: "#E5E7EB"
help:
  title:
    en_US: "Get your API Key from ai_lab"
    zh_Hans: "从 AiLab 获取 API Key"
  url:
    en_US: "https://__put_your_url_here__/account/api-keys"
supported_model_types:
  - llm
configurate_methods:
  - customizable-model
model_credential_schema:
  model:
    label:
      en_US: Model Name
      zh_Hans: 模型名称
    placeholder:
      en_US: Enter your model name
      zh_Hans: 输入模型名称
  credential_form_schemas:
    - variable: token
      label:
        en_US: token
      type: secret-input
      required: true
      placeholder:
        zh_Hans: token
        en_US: Enter your token
provider_credential_schema:
  credential_form_schemas:
  - variable: openai_api_key
    label:
      en_US: API Key
    type: secret-input
    required: false
    default: '1234567890'
    placeholder:
      zh_Hans: 在此输入您的 API Key
      en_US: Enter your API Key
models:
  llm:
    predefined:
      - "models/llm/*.yaml"
extra:
  python:
    provider_source: provider/ai_lab.py
    model_sources:
      - "models/llm/llm.py"

llm.yaml

model: claude-3-sonnet
label:
  zh_Hans: claude-3-sonnet
  en_US: claude-3-sonnet
model_type: llm
features:
  - multi-tool-call
  - agent-thought
  - stream-tool-call
model_properties:
  mode: chat
  context_size: 16385
parameter_rules:
  - name: temperature
    use_template: temperature
  - name: max_tokens
    use_template: max_tokens
    default: 512
    min: 1
    max: 16385
  - name: response_format
    use_template: response_format
pricing:
  input: '0.003'
  output: '0.004'
  unit: '0.001'
  currency: USD

llm.py

import logging
from collections.abc import Generator
from typing import Optional, Union
import requests
from requests_toolbelt.multipart.encoder import MultipartEncoder
from requests_toolbelt.utils import dump

from dify_plugin import LargeLanguageModel
from dify_plugin.entities import I18nObject
from dify_plugin.errors.model import (
    CredentialsValidateFailedError, InvokeError, InvokeAuthorizationError, InvokeRateLimitError,
)
from dify_plugin.entities.model import (
    AIModelEntity,
    FetchFrom,
    ModelType,
)
from dify_plugin.entities.model.llm import (
    AssistantPromptMessage,
    LLMResult,
    LLMResultChunk,
    LLMResultChunkDelta,
    LLMUsage,
)
from dify_plugin.entities.model.message import (
    PromptMessage,
    PromptMessageTool, PromptMessageRole,
)
from dify_plugin.plugin import logger

class AiLabLargeLanguageModel(LargeLanguageModel):
    """
    Model class for ai_lab large language model.
    """
    def _make_request(self, model: str,  token: str, stream: bool = False, messages: list[PromptMessage] = None, model_parameters: dict = None, tools: list[PromptMessageTool] = None):
        import json

        url = 'http://my-self-url'

        # 构造 multipart/form-data
        m = MultipartEncoder(
            fields={
                "stream": "false",  # Stream mode not supported yet
                "model_name": model,
                "message": json.dumps([{'role': message.role.value, 'content': message.content, 'name': message.name} for message in messages or []]),
                "thinking": "enabled",
                "max_retry_times": "3",
                "temperature": str(model_parameters.get('temperature', 0.7)),
                "max_tokens": str(model_parameters.get('max_tokens', 1024)),
                "toolConfig": '',
                "file": ""  # 如果不上传文件,可以置空字符串
            }
        )

        headers = {
            "accept": "application/json",
            "token": "sercet",
            "Content-Type": m.content_type 
        }
        logger.info(m)
        try:
            response = requests.post(url, data=m, headers=headers)
            response.raise_for_status()
            result = response.json()
        except Exception as e:
            logger.error(f"请求Claude3接口失败: {e}, {response.text}")
            raise

        if stream:
            usage = LLMUsage.empty_usage()
            usage.prompt_tokens = result['usage'].get('input_tokens', 0)
            usage.completion_tokens = result['usage'].get('output_tokens', 0)
            usage.total_tokens = result['usage'].get('input_tokens', 0) + result['usage'].get('output_tokens', 0)
            # 假设接口支持流式返回
            def stream_generator():
                yield LLMResultChunk(
                    model=result.get('real_model', ''),
                    prompt_messages=messages,
                    delta=LLMResultChunkDelta(
                        index=0,
                        message=AssistantPromptMessage(
                            role=PromptMessageRole.ASSISTANT,
                            content=result.get('result', ''),
                        ),
                        usage=usage,
                        finish_reason="Stream mode not supported yet",
                    ),
                )
            return stream_generator()
        else:
            usage = LLMUsage.empty_usage()
            usage.prompt_tokens = result['usage'].get('input_tokens', 0)
            usage.completion_tokens = result['usage'].get('output_tokens', 0)
            usage.total_tokens = result['usage'].get('input_tokens', 0) + result['usage'].get('output_tokens', 0)
            return LLMResult(
                model=result.get('real_model', ''),
                prompt_messages=messages,
                message=AssistantPromptMessage(
                    role=PromptMessageRole.ASSISTANT,
                    content=result.get('result', ''),
                ),
                usage=usage,
                system_fingerprint=None,
            )

    def _invoke(
        self,
        model: str,
        credentials: dict,
        prompt_messages: list[PromptMessage],
        model_parameters: dict,
        tools: Optional[list[PromptMessageTool]] = None,
        stop: Optional[list[str]] = None,
        stream: bool = True,
        user: Optional[str] = None,
    ) -> Union[LLMResult, Generator]:
        """
        Invoke large language model

        :param model: model name
        :param credentials: model credentials
        :param prompt_messages: prompt messages
        :param model_parameters: model parameters
        :param tools: tools for tool calling
        :param stop: stop words
        :param stream: is stream response
        :param user: unique user id
        :return: full response or stream response chunk generator result
        """
        logger.info(credentials)
        logger.info(prompt_messages)
        logger.info(model_parameters)
        logger.info(tools)
        return self._make_request(model, credentials.get('token'), stream, prompt_messages, model_parameters, tools)
   

    def get_num_tokens(
        self,
        model: str,
        credentials: dict,
        prompt_messages: list[PromptMessage],
        tools: Optional[list[PromptMessageTool]] = None,
    ) -> int:
        """
        Get number of tokens for given prompt messages

        :param model: model name
        :param credentials: model credentials
        :param prompt_messages: prompt messages
        :param tools: tools for tool calling
        :return:
        """
        return 0

    def validate_credentials(self, model: str, credentials: dict) -> None:
        """
        Validate model credentials

        :param model: model name
        :param credentials: model credentials
        :return:
        """
        try:
            self._make_request(model, credentials.get('token'), False, [PromptMessage(role=PromptMessageRole.USER, content="hello")], {}, [])
        except Exception as ex:
            raise CredentialsValidateFailedError(str(ex))

    def get_customizable_model_schema(
        self, model: str, credentials: dict
    ) -> AIModelEntity:
        """
        If your model supports fine-tuning, this method returns the schema of the base model
        but renamed to the fine-tuned model name.

        :param model: model name
        :param credentials: credentials

        :return: model schema
        """
        entity = AIModelEntity(
            model=model,
            label=I18nObject(zh_Hans=model, en_US=model),
            model_type=ModelType.LLM,
            features=[],
            fetch_from=FetchFrom.CUSTOMIZABLE_MODEL,
            model_properties={},
            parameter_rules=[],
        )

        return entity

    @property
    def _invoke_error_mapping(self) -> dict[type[InvokeError], list[type[Exception]]]:
        # 示例映射
        mapping = {
            InvokeAuthorizationError: [
                # vendor_sdk.AuthenticationError,
                # vendor_sdk.PermissionDeniedError,
            ],
            InvokeRateLimitError: [
                # vendor_sdk.RateLimitError,
            ],
            # ... 其他映射 ...
        }
        # 可以在这里加入基类的默认映射 (如果基类提供的话)
        # base_mapping = super()._invoke_error_mapping
        # mapping.update(base_mapping) # 注意合并策略
        return mapping

✔️ Expected Behavior

run correct

Actual Behavior

return error

Originally created by @yyyykl on GitHub (Aug 28, 2025). ### Self Checks - [x] I have read the [Contributing Guide](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md) and [Language Policy](https://github.com/langgenius/dify/issues/1542). - [x] This is only for bug report, if you would like to ask a question, please head to [Discussions](https://github.com/langgenius/dify/discussions/categories/general). - [x] I have searched for existing issues [search for existing issues](https://github.com/langgenius/dify/issues), including closed ones. - [x] I confirm that I am using English to submit this report, otherwise it will be closed. - [x] 【中文用户 & Non English User】请使用英语提交,否则会被关闭 :) - [x] Please do not modify this template :) and fill in all the required fields. ### Dify version 1.8.0 ### Cloud or Self Hosted Self Hosted (Source) ### Steps to reproduce I created a llm plugin, when i run single llm node, it return "Model is not configured" <img width="801" height="204" alt="Image" src="https://github.com/user-attachments/assets/184b31aa-bf4e-4c27-9e1d-94a39282213c" /> <img width="429" height="91" alt="Image" src="https://github.com/user-attachments/assets/f72bbab5-bba5-4c18-8f94-d4fbc02f9424" /> provider.yaml ``` provider: ai_lab label: en_US: "AiLab" description: en_US: "Models provided by ai_lab." zh_Hans: "AiLab 提供的模型。" icon_small: en_US: "icon.svg" icon_large: en_US: "icon.svg" background: "#E5E7EB" help: title: en_US: "Get your API Key from ai_lab" zh_Hans: "从 AiLab 获取 API Key" url: en_US: "https://__put_your_url_here__/account/api-keys" supported_model_types: - llm configurate_methods: - customizable-model model_credential_schema: model: label: en_US: Model Name zh_Hans: 模型名称 placeholder: en_US: Enter your model name zh_Hans: 输入模型名称 credential_form_schemas: - variable: token label: en_US: token type: secret-input required: true placeholder: zh_Hans: token en_US: Enter your token provider_credential_schema: credential_form_schemas: - variable: openai_api_key label: en_US: API Key type: secret-input required: false default: '1234567890' placeholder: zh_Hans: 在此输入您的 API Key en_US: Enter your API Key models: llm: predefined: - "models/llm/*.yaml" extra: python: provider_source: provider/ai_lab.py model_sources: - "models/llm/llm.py" ``` llm.yaml ``` model: claude-3-sonnet label: zh_Hans: claude-3-sonnet en_US: claude-3-sonnet model_type: llm features: - multi-tool-call - agent-thought - stream-tool-call model_properties: mode: chat context_size: 16385 parameter_rules: - name: temperature use_template: temperature - name: max_tokens use_template: max_tokens default: 512 min: 1 max: 16385 - name: response_format use_template: response_format pricing: input: '0.003' output: '0.004' unit: '0.001' currency: USD ``` llm.py ``` import logging from collections.abc import Generator from typing import Optional, Union import requests from requests_toolbelt.multipart.encoder import MultipartEncoder from requests_toolbelt.utils import dump from dify_plugin import LargeLanguageModel from dify_plugin.entities import I18nObject from dify_plugin.errors.model import ( CredentialsValidateFailedError, InvokeError, InvokeAuthorizationError, InvokeRateLimitError, ) from dify_plugin.entities.model import ( AIModelEntity, FetchFrom, ModelType, ) from dify_plugin.entities.model.llm import ( AssistantPromptMessage, LLMResult, LLMResultChunk, LLMResultChunkDelta, LLMUsage, ) from dify_plugin.entities.model.message import ( PromptMessage, PromptMessageTool, PromptMessageRole, ) from dify_plugin.plugin import logger class AiLabLargeLanguageModel(LargeLanguageModel): """ Model class for ai_lab large language model. """ def _make_request(self, model: str, token: str, stream: bool = False, messages: list[PromptMessage] = None, model_parameters: dict = None, tools: list[PromptMessageTool] = None): import json url = 'http://my-self-url' # 构造 multipart/form-data m = MultipartEncoder( fields={ "stream": "false", # Stream mode not supported yet "model_name": model, "message": json.dumps([{'role': message.role.value, 'content': message.content, 'name': message.name} for message in messages or []]), "thinking": "enabled", "max_retry_times": "3", "temperature": str(model_parameters.get('temperature', 0.7)), "max_tokens": str(model_parameters.get('max_tokens', 1024)), "toolConfig": '', "file": "" # 如果不上传文件,可以置空字符串 } ) headers = { "accept": "application/json", "token": "sercet", "Content-Type": m.content_type } logger.info(m) try: response = requests.post(url, data=m, headers=headers) response.raise_for_status() result = response.json() except Exception as e: logger.error(f"请求Claude3接口失败: {e}, {response.text}") raise if stream: usage = LLMUsage.empty_usage() usage.prompt_tokens = result['usage'].get('input_tokens', 0) usage.completion_tokens = result['usage'].get('output_tokens', 0) usage.total_tokens = result['usage'].get('input_tokens', 0) + result['usage'].get('output_tokens', 0) # 假设接口支持流式返回 def stream_generator(): yield LLMResultChunk( model=result.get('real_model', ''), prompt_messages=messages, delta=LLMResultChunkDelta( index=0, message=AssistantPromptMessage( role=PromptMessageRole.ASSISTANT, content=result.get('result', ''), ), usage=usage, finish_reason="Stream mode not supported yet", ), ) return stream_generator() else: usage = LLMUsage.empty_usage() usage.prompt_tokens = result['usage'].get('input_tokens', 0) usage.completion_tokens = result['usage'].get('output_tokens', 0) usage.total_tokens = result['usage'].get('input_tokens', 0) + result['usage'].get('output_tokens', 0) return LLMResult( model=result.get('real_model', ''), prompt_messages=messages, message=AssistantPromptMessage( role=PromptMessageRole.ASSISTANT, content=result.get('result', ''), ), usage=usage, system_fingerprint=None, ) def _invoke( self, model: str, credentials: dict, prompt_messages: list[PromptMessage], model_parameters: dict, tools: Optional[list[PromptMessageTool]] = None, stop: Optional[list[str]] = None, stream: bool = True, user: Optional[str] = None, ) -> Union[LLMResult, Generator]: """ Invoke large language model :param model: model name :param credentials: model credentials :param prompt_messages: prompt messages :param model_parameters: model parameters :param tools: tools for tool calling :param stop: stop words :param stream: is stream response :param user: unique user id :return: full response or stream response chunk generator result """ logger.info(credentials) logger.info(prompt_messages) logger.info(model_parameters) logger.info(tools) return self._make_request(model, credentials.get('token'), stream, prompt_messages, model_parameters, tools) def get_num_tokens( self, model: str, credentials: dict, prompt_messages: list[PromptMessage], tools: Optional[list[PromptMessageTool]] = None, ) -> int: """ Get number of tokens for given prompt messages :param model: model name :param credentials: model credentials :param prompt_messages: prompt messages :param tools: tools for tool calling :return: """ return 0 def validate_credentials(self, model: str, credentials: dict) -> None: """ Validate model credentials :param model: model name :param credentials: model credentials :return: """ try: self._make_request(model, credentials.get('token'), False, [PromptMessage(role=PromptMessageRole.USER, content="hello")], {}, []) except Exception as ex: raise CredentialsValidateFailedError(str(ex)) def get_customizable_model_schema( self, model: str, credentials: dict ) -> AIModelEntity: """ If your model supports fine-tuning, this method returns the schema of the base model but renamed to the fine-tuned model name. :param model: model name :param credentials: credentials :return: model schema """ entity = AIModelEntity( model=model, label=I18nObject(zh_Hans=model, en_US=model), model_type=ModelType.LLM, features=[], fetch_from=FetchFrom.CUSTOMIZABLE_MODEL, model_properties={}, parameter_rules=[], ) return entity @property def _invoke_error_mapping(self) -> dict[type[InvokeError], list[type[Exception]]]: # 示例映射 mapping = { InvokeAuthorizationError: [ # vendor_sdk.AuthenticationError, # vendor_sdk.PermissionDeniedError, ], InvokeRateLimitError: [ # vendor_sdk.RateLimitError, ], # ... 其他映射 ... } # 可以在这里加入基类的默认映射 (如果基类提供的话) # base_mapping = super()._invoke_error_mapping # mapping.update(base_mapping) # 注意合并策略 return mapping ``` ### ✔️ Expected Behavior run correct ### ❌ Actual Behavior return error
yindo added the 🐞 bug label 2026-02-21 19:26:32 -05:00
yindo closed this issue 2026-02-21 19:26:32 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Aug 28, 2025):

Hi @yyyykl! I'm Dosu and I’m helping the dify team.

The "Model is not configured" error in Dify custom LLM plugins almost always means the model credentials are missing, invalid, or not properly saved/linked in the database. This can happen if the credential schema in your provider.yaml doesn't match what your Python code expects, or if the credentials weren't saved correctly in the Dify UI/API. The backend checks for these credentials before marking a model as active and usable—if they're missing or invalid, you'll get this error (source).

Here's a checklist to resolve this:

  1. Double-check your provider.yaml: Make sure model_credential_schema and provider_credential_schema are correct and match what your Python code expects.
  2. In the Dify UI, ensure you have actually saved the required credentials for your custom model (e.g., token, model name). If you haven't, add them and save.
  3. Make sure the model name you use in the UI exactly matches what's referenced in your YAML and Python files.
  4. In your plugin Python code (e.g., models/llm/llm.py), confirm that you are correctly loading and using the credentials and model name passed from the configuration. If you miss required keys or don't handle them, Dify will treat the model as not configured.
  5. If you recently changed the credential schema or model files, try removing and re-adding the model in the Dify UI to force a refresh.

If you want to compare, you can find working examples of custom model plugins in the official plugins repo.

If you follow these steps and ensure credentials are present and valid, the error should resolve. If not, check the backend logs for credential validation errors—they often provide more detail about what's missing or invalid.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Chat with Dosu Join Discord Share on X

@dosubot[bot] commented on GitHub (Aug 28, 2025): <!-- Greeting --> Hi @yyyykl! I'm [Dosu](https://go.dosu.dev/dosubot) and I’m helping the dify team. <!-- Answer --> The "Model is not configured" error in Dify custom LLM plugins almost always means the model credentials are missing, invalid, or not properly saved/linked in the database. This can happen if the credential schema in your provider.yaml doesn't match what your Python code expects, or if the credentials weren't saved correctly in the Dify UI/API. The backend checks for these credentials before marking a model as active and usable—if they're missing or invalid, you'll get this error ([source](https://github.com/langgenius/dify/blob/79e6138ce2e300986fd736a4f2e32fab81625fb3/api/core/entities/provider_configuration.py)). Here's a checklist to resolve this: 1. Double-check your provider.yaml: Make sure model_credential_schema and provider_credential_schema are correct and match what your Python code expects. 2. In the Dify UI, ensure you have actually saved the required credentials for your custom model (e.g., token, model name). If you haven't, add them and save. 3. Make sure the model name you use in the UI exactly matches what's referenced in your YAML and Python files. 4. In your plugin Python code (e.g., models/llm/llm.py), confirm that you are correctly loading and using the credentials and model name passed from the configuration. If you miss required keys or don't handle them, Dify will treat the model as not configured. 5. If you recently changed the credential schema or model files, try removing and re-adding the model in the Dify UI to force a refresh. If you want to compare, you can find working examples of custom model plugins in the [official plugins repo](https://github.com/langgenius/dify-official-plugins/tree/main/models). If you follow these steps and ensure credentials are present and valid, the error should resolve. If not, check the backend logs for credential validation errors—they often provide more detail about what's missing or invalid. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/729d8221-0c07-470d-9aa1-d015e3552763?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/729d8221-0c07-470d-9aa1-d015e3552763?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/729d8221-0c07-470d-9aa1-d015e3552763?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/729d8221-0c07-470d-9aa1-d015e3552763?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/729d8221-0c07-470d-9aa1-d015e3552763?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/729d8221-0c07-470d-9aa1-d015e3552763?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/729d8221-0c07-470d-9aa1-d015e3552763?feedback_type=other)</sup>&nbsp;&nbsp;[![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/6088a4f4-073a-43f3-b3d4-4f06fb0c05a0/ask?utm_source=github)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/langgenius/dify/issues/24729)
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langgenius/dify#16532