📎 配套脚本附件正文未内嵌 · 共 9 个 · 点开可在线查看
📚 另需公共库(正文未重复内嵌,点名字直接看):_agent_cfg.py、_bootstrap.py、_config.py、_plugin_util.py
📂 查看全部 761 个脚本附件 →
⚠️ 脚本内含明文凭据(与本页正文一致),请勿在公网环境直接运行。
Dify · 专题十

模型列表为空与 Agent 创建报错

schema JSON、max_tokens 不能写进 model_properties。步骤未改,代码块已换成完整程序。

Dify_专题十_模型列表为空Agent创建报错_cursor.md

这是基于原文生成的 Cursor 版文档。

原文件未改动;本版只做三件事:统一阅读入口、修正已审出的明显问题、补充更适合快速排障的提示。

Cursor 版修订说明

阅读提示

运行环境:业务脚本均在 207 固定目录 /opt/workbuddy 执行,Python 为 /usr/bin/python3。 Windows 开发机仅保留 _probe_* 调试脚本;同步命令:python scripts_cursor/完整代码约定/00_部署_workbuddy到207.py。


原文主体(已纳入 Cursor 版纠错)

专题十:模型列表为空 / 创建 Agent 报错(插件 schema 校验失败)

阅读导航(何时打开 · 报错往哪查)

本文是单点修复/排障专题,配合《安装配置手册》使用;下文实战步骤与代码全部保留,此处只做索引。

现象本文还可能是
LLM 列表突然变空§三 llm.py patchmax_tokens 进了 model_properties
创建 Agent 一大段 JSON 报错§二 根因专题一 接入未完成
patch 后仍空§四 重启 pluginSyntaxError→专题八

本专题记录本次部署中一个症状隐蔽、根因在插件 schema 的问题:

OpenAI 插件 llm.py patch 后,GET /models/model-types/llm 返回空列表,创建 Agent 时模型下拉为空并报错;

同时涉及「插件改完必须重启验证」的通用教训。


一、现象

完整脚本:scripts_bash_cursor/Dify_专题十_模型列表为空Agent创建报错/专题十_内联_01.sh

运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题十_模型列表为空Agent创建报错/专题十_内联_01.sh

bash
#!/usr/bin/env bash
set -euo pipefail

  GET /console/api/workspaces/current/models/model-types/llm
  

返回: json {"data": []}


二、根因

OpenAI 插件 llm.py 的 get_customizable_model_schema patch 中,把 max_tokens 写进了 model_properties:

python
props["max_tokens"] = 8192   # ❌ 错误位置

Dify API 端(graphon.model_runtime.entities.model_entities.ModelPropertyKey)只承认以下 key:

完整程序:/opt/workbuddy/scripts_cursor/Dify_专题十_模型列表为空Agent创建报错/专题十_内联_01.py

运行/引用:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十_模型列表为空Agent创建报错/专题十_内联_01.py

python
class ModelPropertyKey(StrEnum):
    MODE = auto()
    CONTEXT_SIZE = auto()
    MAX_CHUNKS = auto()
    FILE_UPLOAD_LIMIT = auto()
    SUPPORTED_FILE_EXTENSIONS = auto()
    MAX_CHARACTERS_PER_CHUNK = auto()
    DEFAULT_VOICE = auto()
    VOICES = auto()
    WORD_LIMIT = auto()
    AUDIO_TYPE = auto()
    MAX_WORKERS = auto()

没有 MAX_TOKENS。插件返回的 schema 里 model_properties.max_tokens 会导致 API 端 Pydantic 校验失败,于是该模型被过滤掉,GET /models/model-types/llm 返回空列表,前端创建 Agent 时报错。

顺带说明:同一次排查还暴露另一个通用坑——插件代码改完后如果存在语法错误(如遗留临时调试代码),plugin_daemon 只在重新 import 时才报错;运行中的实例因为 sys.modules 缓存旧代码,会"一直正常",直到某天被重新加载才炸。所以改完插件必须立即重启并验证。


三、解决:修正 llm.py patch

3.1 文件位置

容器内:

text
/app/storage/cwd/langgenius/openai-1.0.3@<hash>/models/llm/llm.py

host 上对应 plugin_daemon 挂载卷,可用下面命令定位:

完整脚本:scripts_bash_cursor/Dify_专题十_模型列表为空Agent创建报错/专题十_内联_02.sh

运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题十_模型列表为空Agent创建报错/专题十_内联_02.sh

bash
#!/usr/bin/env bash
set -euo pipefail

docker exec docker-plugin_daemon-1 sh -c 'find /app/storage/cwd -path "*langgenius/openai*" -name llm.py | head -1'

3.2 正确代码

在 get_customizable_model_schema 的 fallback 分支里,只保留 context_size:

完整补丁:patches_cursor/Dify_专题十_模型列表为空Agent创建报错/专题十_内联_01.py

运行/引用:见 patches_cursor/Dify_专题十_模型列表为空Agent创建报错/专题十_内联_01.py

python
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,
        },
    )

3.3 同步修正 align_qwen_ctx.py

如果用了 align_qwen_ctx.py 对齐 context_size,脚本里也只写 context_size:

完整程序:/opt/workbuddy/scripts_cursor/Dify_专题十_模型列表为空Agent创建报错/专题十_内联_02.py

运行/引用:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十_模型列表为空Agent创建报错/专题十_内联_02.py

python
# 只保留 context_size,不要写 max_tokens
cred["model_properties"] = {"context_size": NEW_CTX}

四、重启与验证

4.1 语法校验(本地做)

把修改后的 llm.py 拿到本地或容器里 py_compile:

完整脚本:scripts_bash_cursor/Dify_专题十_模型列表为空Agent创建报错/专题十_内联_03.sh

运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题十_模型列表为空Agent创建报错/专题十_内联_03.sh

bash
#!/usr/bin/env bash
set -euo pipefail

python3 -m py_compile llm.py && echo "SYNTAX OK"

4.2 覆盖回容器并重启

完整脚本:scripts_bash_cursor/Dify_专题十_模型列表为空Agent创建报错/专题十_内联_04.sh

运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题十_模型列表为空Agent创建报错/专题十_内联_04.sh

bash
#!/usr/bin/env bash
set -euo pipefail

# 假设已把修复后的 llm.py 放到 /tmp/llm_fixed.py
docker cp /tmp/llm_fixed.py docker-plugin_daemon-1:/app/storage/cwd/langgenius/openai-1.0.3@<hash>/models/llm/llm.py

# 重启插件守护进程
cd /dify/dify-main/docker   # 或 /dify/dify/docker
docker compose restart plugin_daemon

注意:只改插件代码时,理论上只需 restart plugin_daemon;但如果 API 端已经缓存了旧 schema,建议同时 restart api worker。

4.3 怎么看日志

完整脚本:scripts_bash_cursor/Dify_专题十_模型列表为空Agent创建报错/专题十_内联_05.sh

运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题十_模型列表为空Agent创建报错/专题十_内联_05.sh

bash
#!/usr/bin/env bash
set -euo pipefail

# 1) 确认 plugin_daemon 没有 SyntaxError / Traceback
docker logs docker-plugin_daemon-1 2>&1 | tail -30

# 2) 确认 API 不再报 ValueError
docker logs docker-api-1 2>&1 | grep -iE 'ValueError|model_schema|qwen3.6-27b' | tail -20

4.4 怎么判断正常

调用控制台 API(可用 dify_api.py):

完整程序:/opt/workbuddy/scripts_cursor/Dify_专题十_模型列表为空Agent创建报错/专题十_内联_03.py

运行/引用:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十_模型列表为空Agent创建报错/专题十_内联_03.py

python
from dify_api import Dify
d = Dify("38205036@qq.com", "Difyai123456")
d.login()
r = d.get("/workspaces/current/models/model-types/llm")
print(r["text"])

正常应返回:

json
{"data":[{"provider":"langgenius/openai/openai","models":[{"model":"qwen3.6-27b","model_properties":{"context_size":64000,"mode":"chat"},"status":"active"}]}]}

然后再去控制台创建 Agent,模型下拉应能选中 qwen3.6-27b。


五、本次实战(真实遇到的情况)

5.1 现象

用户截图:添加 Agent 时报错,模型列表看不到 qwen3.6-27b。实际此前"问答已经跑通",今天突然不行。

5.2 定位

  1. GET /models/model-types/llm 返回 {"data":[]}——LLM 列表为空。
  2. 数据库里 qwen3.6-27b 记录存在且 is_valid=t,排除"凭据丢失"。
  3. docker-api-1 日志抓到一个 ValueError,内容竟是插件返回的完整 schema JSON;说明 API 端 Pydantic 校验失败。
  4. 查看 schema 内容发现 model_properties 带了 {"context_size":64000,"max_tokens":8192,"mode":"chat"}。
  5. 核对 Dify API 源码 ModelPropertyKey 枚举,确认没有 MAX_TOKENS,这就是校验失败的直接原因。

5.3 改了什么

5.4 怎么重启

完整脚本:scripts_bash_cursor/Dify_专题十_模型列表为空Agent创建报错/专题十_内联_06.sh

运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题十_模型列表为空Agent创建报错/专题十_内联_06.sh

bash
#!/usr/bin/env bash
set -euo pipefail

docker cp /tmp/llm_fixed.py docker-plugin_daemon-1:/app/storage/cwd/langgenius/openai-1.0.3@<hash>/models/llm/llm.py
cd /dify/dify-main/docker && docker compose restart plugin_daemon

5.5 怎么看日志

完整脚本:scripts_bash_cursor/Dify_专题十_模型列表为空Agent创建报错/专题十_内联_07.sh

运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题十_模型列表为空Agent创建报错/专题十_内联_07.sh

bash
#!/usr/bin/env bash
set -euo pipefail

# plugin_daemon 侧:确认 openai 插件正常加载
docker logs docker-plugin_daemon-1 2>&1 | tail -20

# API 侧:确认不再有 ValueError
docker logs docker-api-1 2>&1 | grep -iE 'ValueError|model_schema' | tail -20

5.6 怎么判断正常

完整脚本:scripts_bash_cursor/Dify_专题十_模型列表为空Agent创建报错/专题十_内联_08.sh

运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题十_模型列表为空Agent创建报错/专题十_内联_08.sh

bash
#!/usr/bin/env bash
set -euo pipefail

# 用 dify_api.py 或 curl 调控制台 API
python - <<'PY'
from dify_api import Dify
d = Dify("38205036@qq.com", "Difyai123456")
d.login()
r = d.get("/workspaces/current/models/model-types/llm")
print(r["text"])
d.close()
PY

预期返回中包含:

json
{"model":"qwen3.6-27b","model_properties":{"context_size":64000,"mode":"chat"},"status":"active"}

之后刷新控制台,创建 Agent 时模型下拉可选 qwen3.6-27b,不再报错。


六、防呆清单


七、关联文档


本专题 Python 程序一览

| 文件 | 用途 | |---|---|| 专题十_01_查LLM模型列表.py | 专题十 · 步骤 1:GET model-types/llm,确认 qwen3.6-27b 在列表中且无 schema 校验失败。 | | 专题十_02_修正llm_schema补丁_修改后.py | 专题十 · 步骤 2 · 修改后:修正 llm.py,移除 model_properties 中的 max_tokens。 |

专题十_01_查LLM模型列表.py

运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十_模型列表为空Agent创建报错/专题十_01_查LLM模型列表.py

python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""专题十 · 步骤 1:GET model-types/llm,确认 qwen3.6-27b 在列表中且无 schema 校验失败。"""
from __future__ import annotations

import json
import sys

import _bootstrap  # noqa: F401
import _config as C
from dify_api import Dify, pp


def main() -> int:
    d = Dify(C.DIFY_EMAIL, C.DIFY_PASSWORD)
    try:
        d.login()
        r = d.get("/workspaces/current/models/model-types/llm")
        pp("model-types/llm", r, limit=2000)
        data = (r.get("json") or {}).get("data", [])
        if not data:
            print("\n[FAIL] data 为空 —— 常见原因:model_properties 含非法 max_tokens")
            return 1
        found = False
        for p in data:
            if p.get("provider") != C.OPENAI_PROVIDER:
                continue
            for m in p.get("models", []):
                if m.get("model") == C.LLM_MODEL:
                    found = True
                    mp = m.get("model_properties") or {}
                    print("\n[OK] 找到模型:", json.dumps(m, ensure_ascii=False)[:500])
                    if "max_tokens" in mp:
                        print("[WARN] model_properties 仍含 max_tokens,可能再次触发校验失败")
        if not found:
            print("\n[FAIL] 未在列表中找到", C.LLM_MODEL)
            return 1
        return 0
    finally:
        d.close()


if __name__ == "__main__":
    sys.exit(main())

专题十_02_修正llm_schema补丁_修改后.py

运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十_模型列表为空Agent创建报错/专题十_02_修正llm_schema补丁_修改后.py

完整补丁:patches_cursor/Dify_专题十_模型列表为空Agent创建报错/专题十_内联_02.py

运行/引用:见 patches_cursor/Dify_专题十_模型列表为空Agent创建报错/专题十_内联_02.py

python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""专题十 · 步骤 2 · 修改后:修正 llm.py,移除 model_properties 中的 max_tokens。"""
from __future__ import annotations

import sys

import _bootstrap  # noqa: F401
import _plugin_patch as P
from dify_api import Dify

import _config as C  # noqa: F401


def main() -> int:
    d = Dify(C.DIFY_EMAIL, C.DIFY_PASSWORD)
    try:
        d.login()
        llm_path = P.find_plugin_file(d, "llm.py")
        content = P.read_remote_file(d, llm_path)
        new_content = P.replace_method(
            content, "get_customizable_model_schema", P.GET_CUSTOMIZABLE_MODEL_SCHEMA
        )
        P.deploy_plugin_file(d, llm_path, new_content)
        print("已应用专题十 llm.py 补丁(仅 context_size,不含 max_tokens)")
        return 0
    finally:
        d.close()


if __name__ == "__main__":
    sys.exit(main())