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

专题十六 · 配置页 Internal Server Error

对话正常、打开新 Agent 配置页红条:OpenAPI UUID 被当成 builtin 插件。

专题十六:新 Agent 配置页 Internal Server Error(OpenAPI 工具被当成 builtin)

本文解决什么:对话验收通过,但一打开新 Agent 配置页 就弹出红色 Internal Server Error。

不是:CPU / GPU 资源不足、vLLM 挂了、模拟 API 挂了。

环境:192.168.110.207 Dify 1.16.1;作业 1「运维巡检 Agent」挂了 OpenAPI 工具 getServerStatus。

HTML:docs/html_cursor/Dify_专题十六_新Agent配置页InternalServerError_cursor.html

关联:作业 1 工具调用 Agent;专题七/八(插件容器);专题十一(不要点发布)。

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


目录

  1. 现象与误判
  2. 第一部分 原因(理论与调用链)
  3. 第二部分 详细操作步骤
  4. 第三部分 配置修改方式
  5. 第四部分 CPU 与 GPU:为什么不是资源问题
  6. 第五部分 完整程序与补丁
  7. 第六部分 验收与回滚
  8. 第七部分 对本系统的建议

现象与误判

你看到的实际含义
问「帮我看看 gpu-server-01 的状态」能调工具、能答对运行时链路正常
打开 配置 页,右上角红条 Internal Server Error(可能连续两条)配置页拉工具凭据失败
预览区空白 / 刷新图标旁也有红叹号同一接口 500,前端 toast

容易误判成资源不足,因为红条文案太泛。对照:

若真是资源问题本专题
对话超时、Connection error、空泡、全体变慢对话 正常、延迟约数秒
116 nvidia-smi 顶满 / vLLM OOMvLLM /v1/models 正常
207 CPU 打满、8777 无响应/health 与 /server/status 正常

本环境实测:对话 HTTP 200,messages.status=normal;配置页接口对 OpenAPI UUID 返回 500。

本环境 ID:

项值
Agent运维巡检 Agent
agent_id01a0189c-7d7e-742f-83d1-74c5c16878da
OpenAPI provider 名server_status
tool_api_providers.id021a4445-098d-4821-81fe-ff0ebc660ecc

第一部分 原因(理论与调用链)

1.1 运行时 vs 配置页,走的不是同一条路

text
【对话 · 运行时 · 正常】
用户发问
  → docker-api-1  POST /agent/<id>/chat-messages
  → 快照 tools.dify_tools[]
       provider_type = "api"
       provider_id   = 021a4445-...   (tool_api_providers 表 UUID)
  → ToolManager.get_api_provider_controller(tenant_id, UUID)
  → GET http://192.168.110.207:8777/server/status?...
  → 第二次 LLM 格式化回答

【配置页 · 会 500】
浏览器打开「配置」
  → 前端按「内置插件」去拉凭据
  → GET /console/api/workspaces/current/tool-provider/builtin/<provider_id>/credential/info
       这里的 <provider_id> 被填成了上面那个 UUID
  → ToolManager.get_builtin_provider(UUID, tenant_id)
  → plugin_daemon:PluginNotFoundError: plugin not found
  → 接口 HTTP 500
  → 页面 toast「Internal Server Error」

Dify 1.16 新 Agent 的 Composer 工具候选列表只枚举 builtin 插件(audio / code / time / webscraper),不包含 工作区 OpenAPI 自定义工具。快照里却合法地挂了 provider_type=api 的工具。配置页仍用 builtin 路由去解释 provider_id,UUID 不可能是插件名。

1.2 日志原文(修复前)

text
Exception on /console/api/workspaces/current/tool-provider/builtin/021a4445-098d-4821-81fe-ff0ebc660ecc/credential/info [GET]
core.plugin.impl.exc.PluginNotFoundError: plugin not found

对照:真正的 builtin 工具(如 time)同一接口是 HTTP 200。

1.3 为什么不能改快照里的 UUID

运行时 get_api_provider_controller 必须用 UUID。若把 provider_id 改回名称 server_status,对话会报:

text
invalid input syntax for type uuid: "server_status"

因此:快照保持 UUID(保证对话);API 对「builtin 路由遇到非插件」做兜底(保证配置页)。不要为消红条去改作业 1 的工具挂载方式。

1.4 容器内改的是哪一个文件

项值
容器docker-api-1(镜像 langgenius/dify-api:1.16.1)
文件/app/api/services/tools/builtin_tools_manage_service.py
函数get_builtin_tool_provider_credential_info
路由GET /workspaces/current/tool-provider/builtin/<path:provider>/credential/info

不要改 list_builtin_tool_provider_tools 里同名的 get_builtin_provider 调用(曾误打过,会搞乱工具列表)。应用脚本会先从镜像恢复原文件,再只替换 credential_info 这一处。


第二部分 详细操作步骤

顺序:定位 → 打补丁 → 验收 → 硬刷新浏览器。

步骤速查

步目标通过误判排除
0非资源问题vLLM/模拟 API 正常不是 GPU/OOM
1复现+日志见 builtin 误调 UUID对话能过≠配置页能过
2打 API 容器补丁patch 脚本 PASS勿改快照 UUID
3硬刷新浏览器配置页无红条补丁随容器重建丢失→§3.3

与 作业 1 / 作业 5 / 作业 7 的 OpenAPI 工具 Agent 同类问题;对话验收通过仍可能配置页 500。

步骤 0 · 确认不是资源问题(30 秒)

在 207 上:

bash
curl -s -m 10 -H 'Authorization: Bearer 123456' http://116.62.10.183:8000/v1/models
curl -s 'http://127.0.0.1:8777/health'
curl -s 'http://127.0.0.1:8777/server/status?server_name=gpu-server-01'

期望:有 qwen3.6-27b;health ok;JSON 含 45%。

步骤 1 · 复现与定位

仓库根目录(Windows 用 dify_api.py SSH):

bash
/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十六_新Agent配置页InternalServerError/专题十六_01_复现与定位.py

完整程序:/opt/workbuddy/scripts_cursor/Dify_专题十六_新Agent配置页InternalServerError/专题十六_01_复现与定位.py

运行/引用:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十六_新Agent配置页InternalServerError/专题十六_01_复现与定位.py

python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""专题十六 · 步骤 1:复现并定位「打开 Agent 配置页 Internal Server Error」。

对照三项:
1) 对话仍能调 OpenAPI 工具(streaming 200)
2) 配置页接口 builtin/<UUID>/credential/info 是否 500
3) API 日志是否 PluginNotFoundError
"""
from __future__ import annotations

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()
        uuid = C.SERVER_STATUS_PROVIDER_ID
        print("provider UUID", uuid)

        r_api = d.get(
            "/workspaces/current/tool-provider/api/get?provider=%s" % C.SERVER_STATUS_PROVIDER
        )
        pp("1) OpenAPI provider 详情", r_api, limit=400)

        r_bad = d.get(
            "/workspaces/current/tool-provider/builtin/%s/credential/info" % uuid
        )
        pp("2) 配置页误打的 builtin credential/info", r_bad, limit=500)

        r_ok = d.get("/workspaces/current/tool-provider/builtin/time/credential/info")
        pp("3) 真正的 builtin(time)对照", r_ok, limit=300)

        r_chat = d.post(
            "/agent/%s/chat-messages" % C.HOMEWORK1_AGENT_ID,
            body={
                "inputs": {},
                "query": C.HOMEWORK1_PROBE_QUERY,
                "response_mode": "streaming",
                "files": [],
                "conversation_id": "",
            },
            extra="-m 120 --max-time 120",
            t=150,
        )
        text = r_chat.get("text") or ""
        print("\n4) 对话 HTTP", r_chat.get("code"), "getServerStatus" in text, "45%" in text)

        out, _ = d.sh(
            "docker logs %s --tail 40 2>&1 | grep -iE 'PluginNotFound|credential/info' | tail -10"
            % C.DIFY_API_CTR
        )
        print("\n5) api 日志片段:\n", out or "(无匹配,可能已打补丁)")

        code = r_bad.get("code")
        if code == "500":
            print("\n[定位完成] 配置页接口 500 = 本专题场景。下一步跑作业1_06。")
            return 0
        if code == "200":
            print("\n[已修复] credential/info 已是 200,配置页应不再红条。请 Ctrl+F5。")
            return 0
        print("\n[异常] 预期 500 或 200,实际 HTTP", code)
        return 1
    finally:
        d.close()


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

解读:

接口修复前修复后
.../tool-provider/api/get?provider=server_status200200
.../builtin/<UUID>/credential/info500200 空凭据
.../builtin/time/credential/info200200
对话 streaming200,含 getServerStatus仍 200

207 上看日志:

bash
bash /opt/workbuddy/scripts_bash_cursor/Dify_专题十六_新Agent配置页InternalServerError/专题十六_02_查日志与补丁.sh

完整脚本:scripts_bash_cursor/Dify_专题十六_新Agent配置页InternalServerError/专题十六_02_查日志与补丁.sh

运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题十六_新Agent配置页InternalServerError/专题十六_02_查日志与补丁.sh

bash
#!/usr/bin/env bash
# 专题十六 · 在 207 上确认配置页误打的 builtin 接口
set -euo pipefail

UUID="${1:-021a4445-098d-4821-81fe-ff0ebc660ecc}"

echo "=== docker-api-1 最近 PluginNotFound ==="
docker logs docker-api-1 --tail 80 2>&1 | grep -iE "PluginNotFound|credential/info" | tail -15 || true

echo
echo "=== 容器内文件是否已有兜底 ==="
docker exec docker-api-1 grep -n "skip builtin credential/info for non-plugin provider" \
  /app/api/services/tools/builtin_tools_manage_service.py || echo "(尚未打补丁)"

步骤 2 · 应用补丁(完整程序)

bash
/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十六_新Agent配置页InternalServerError/作业1_06_修复配置页InternalServerError.py

脚本会:

  1. 读取 docker-api-1 镜像,把 builtin_tools_manage_service.py 恢复成官方原文件(避免历史误打)。
  2. 只替换 get_builtin_tool_provider_credential_info 函数开头:找不到插件时返回空凭据。
  3. docker restart docker-api-1,等待控制台可访问。
  4. 再 GET 那条 builtin UUID 接口,期望 200。

完整程序:/opt/workbuddy/scripts_cursor/Dify_专题十六_新Agent配置页InternalServerError/作业1_06_修复配置页InternalServerError.py

运行/引用:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十六_新Agent配置页InternalServerError/作业1_06_修复配置页InternalServerError.py

python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""作业 1 · 修复打开 Agent 配置页 Internal Server Error(精确打到 credential/info)。"""
from __future__ import annotations

import sys

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

REMOTE = "/app/api/services/tools/builtin_tools_manage_service.py"

OLD = '''    def get_builtin_tool_provider_credential_info(
        tenant_id: str,
        provider: str,
        session: Session,
        user: Account | None = None,
        include_credential_ids: list[str] | None = None,
    ) -> ToolProviderCredentialInfoApiEntity:
        """
        get builtin tool provider credential info
        """
        provider_controller = ToolManager.get_builtin_provider(provider, tenant_id)
'''

NEW = '''    def get_builtin_tool_provider_credential_info(
        tenant_id: str,
        provider: str,
        session: Session,
        user: Account | None = None,
        include_credential_ids: list[str] | None = None,
    ) -> ToolProviderCredentialInfoApiEntity:
        """
        get builtin tool provider credential info
        """
        try:
            provider_controller = ToolManager.get_builtin_provider(provider, tenant_id)
        except Exception as exc:
            err = str(exc).lower()
            if type(exc).__name__ in (
                "PluginNotFoundError",
                "ToolProviderNotFoundError",
            ) or "plugin not found" in err:
                logger.warning(
                    "skip builtin credential/info for non-plugin provider %s: %s",
                    provider,
                    exc,
                )
                return ToolProviderCredentialInfoApiEntity(
                    supported_credential_types=[],
                    is_oauth_custom_client_enabled=False,
                    credentials=[],
                )
            raise
'''


def main() -> int:
    d = Dify(C.DIFY_EMAIL, C.DIFY_PASSWORD)
    try:
        d.login()
        img, _ = d.sh(
            "docker inspect %s --format '{{.Config.Image}}'" % C.DIFY_API_CTR
        )
        image = img.strip()
        print("image", image)

        # 从镜像恢复原文件(上次误改了 list_builtin_tool_provider_tools)
        d.sh(
            "docker run --rm --entrypoint cat %s %s > /tmp/builtin_tools_orig.py"
            % (image, REMOTE)
        )
        orig, _ = d.sh("wc -l /tmp/builtin_tools_orig.py; grep -c get_builtin_tool_provider_credential_info /tmp/builtin_tools_orig.py")
        print("orig", orig)

        d.sh("docker cp /tmp/builtin_tools_orig.py %s:%s" % (C.DIFY_API_CTR, REMOTE))
        content, _ = d.sh("docker exec %s cat %s" % (C.DIFY_API_CTR, REMOTE))
        if OLD not in content:
            print("恢复后仍找不到精确插入点")
            print("has func", "get_builtin_tool_provider_credential_info" in content)
            return 1
        if "skip builtin credential/info for non-plugin provider" in content.split("get_builtin_tool_provider_credential_info", 1)[-1][:800]:
            print("目标函数已有补丁")
        else:
            patched = content.replace(OLD, NEW, 1)
            tmp = "/tmp/builtin_tools_patched.py"
            with d.sftp.open(tmp, "w") as f:
                f.write(patched)
            d.sh("docker cp %s %s:%s" % (tmp, C.DIFY_API_CTR, REMOTE))
            print("已精确写入 credential_info 补丁")

        d.sh("docker restart %s" % C.DIFY_API_CTR)
        print("等待 api 起来...")
        d.sh(
            "for i in 1 2 3 4 5 6 7 8 9 10; do "
            "curl -sf -o /dev/null -m 3 http://127.0.0.1/console/api/setup || true; sleep 2; done"
        )
        d.login()
        path = (
            "/workspaces/current/tool-provider/builtin/%s/credential/info"
            % C.SERVER_STATUS_PROVIDER_ID
        )
        r = d.get(path)
        print("GET", path, "HTTP", r.get("code"))
        print((r.get("text") or "")[:400])
        ok_time = d.get("/workspaces/current/tool-provider/builtin/time/credential/info")
        print("time still", ok_time.get("code"))
        if r.get("code") != "200":
            return 1
        print("[PASS] 刷新浏览器打开运维巡检 Agent 配置页,红条应消失。")
        return 0
    finally:
        d.close()


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

不要手改 800 行文件里第一处 get_builtin_provider——必须用脚本里那段带函数签名的精确替换。

步骤 3 · 浏览器

  1. Ctrl+F5 强制刷新(旧 500 会被浏览器缓存成红条)。
  2. 打开 http://192.168.110.207 → 运维巡检 Agent → 配置。
  3. 红条应消失;再新开对话问 gpu-server-01,工具调用仍在。

第三部分 配置修改方式

3.1 要改的:Dify API 容器内 Python(不是 Agent 快照)

改什么改不改原因
Agent 快照 tools.dify_tools不改UUID 是运行时必需
OpenAPI Schema / 8777不改与配置页 500 无关
builtin_tools_manage_service.py 的 credential_info改配置页误打 builtin
工作区「集成 → 工具」不用删工具删了对话会挂

3.2 函数级修改对照

改动对比(get_builtin_tool_provider_credential_info)

项修改前修改后
get_builtin_provider直接调用,插件不存在就抛错try/except
找不到插件时HTTP 500 + 前端红条返回空凭据 JSON,HTTP 200
真正的 builtin(如 time)正常行为不变

修改前

完整补丁:patches_cursor/Dify_专题十六_新Agent配置页InternalServerError/配置页InternalServerError_credential_info_修改前.py

运行/引用:见 patches_cursor/Dify_专题十六_新Agent配置页InternalServerError/配置页InternalServerError_credential_info_修改前.py

python
# -*- coding: utf-8 -*-
"""配置页 Internal Server Error · 修改前(摘自 Dify 1.16.1
docker-api-1:/app/api/services/tools/builtin_tools_manage_service.py
函数 get_builtin_tool_provider_credential_info)。

本文件是「要替换的那段函数头部」的完整对照,不是整个 800+ 行服务文件。
应用请用:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十六_新Agent配置页InternalServerError/作业1_06_修复配置页InternalServerError.py
"""
from __future__ import annotations

from sqlalchemy.orm import Session

from core.tools.entities.api_entities import ToolProviderCredentialInfoApiEntity
from core.tools.tool_manager import ToolManager
from models.account import Account


class BuiltinToolManageService:
    @staticmethod
    def get_builtin_tool_provider_credential_info(
        tenant_id: str,
        provider: str,
        session: Session,
        user: Account | None = None,
        include_credential_ids: list[str] | None = None,
    ) -> ToolProviderCredentialInfoApiEntity:
        """
        get builtin tool provider credential info
        """
        provider_controller = ToolManager.get_builtin_provider(provider, tenant_id)
        supported_credential_types = provider_controller.get_supported_credential_types()
        credentials = BuiltinToolManageService.get_builtin_tool_provider_credentials(
            tenant_id,
            provider,
            session=session,
            user=user,
            include_credential_ids=include_credential_ids,
        )
        credential_info = ToolProviderCredentialInfoApiEntity(
            supported_credential_types=supported_credential_types,
            is_oauth_custom_client_enabled=BuiltinToolManageService.is_oauth_custom_client_enabled(tenant_id, provider),
            credentials=credentials,
        )
        return credential_info

修改后

完整补丁:patches_cursor/Dify_专题十六_新Agent配置页InternalServerError/配置页InternalServerError_credential_info_修改后.py

运行/引用:见 patches_cursor/Dify_专题十六_新Agent配置页InternalServerError/配置页InternalServerError_credential_info_修改后.py

python
# -*- coding: utf-8 -*-
"""配置页 Internal Server Error · 修改后(摘自 Dify 1.16.1
docker-api-1:/app/api/services/tools/builtin_tools_manage_service.py
函数 get_builtin_tool_provider_credential_info)。

相对修改前只多了 try/except:插件不存在时返回空凭据 JSON,HTTP 200。
应用请用:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题十六_新Agent配置页InternalServerError/作业1_06_修复配置页InternalServerError.py
"""
from __future__ import annotations

import logging

from sqlalchemy.orm import Session

from core.tools.entities.api_entities import ToolProviderCredentialInfoApiEntity
from core.tools.tool_manager import ToolManager
from models.account import Account

logger = logging.getLogger(__name__)


class BuiltinToolManageService:
    @staticmethod
    def get_builtin_tool_provider_credential_info(
        tenant_id: str,
        provider: str,
        session: Session,
        user: Account | None = None,
        include_credential_ids: list[str] | None = None,
    ) -> ToolProviderCredentialInfoApiEntity:
        """
        get builtin tool provider credential info
        """
        try:
            provider_controller = ToolManager.get_builtin_provider(provider, tenant_id)
        except Exception as exc:
            err = str(exc).lower()
            if type(exc).__name__ in (
                "PluginNotFoundError",
                "ToolProviderNotFoundError",
            ) or "plugin not found" in err:
                logger.warning(
                    "skip builtin credential/info for non-plugin provider %s: %s",
                    provider,
                    exc,
                )
                return ToolProviderCredentialInfoApiEntity(
                    supported_credential_types=[],
                    is_oauth_custom_client_enabled=False,
                    credentials=[],
                )
            raise
        supported_credential_types = provider_controller.get_supported_credential_types()
        credentials = BuiltinToolManageService.get_builtin_tool_provider_credentials(
            tenant_id,
            provider,
            session=session,
            user=user,
            include_credential_ids=include_credential_ids,
        )
        credential_info = ToolProviderCredentialInfoApiEntity(
            supported_credential_types=supported_credential_types,
            is_oauth_custom_client_enabled=BuiltinToolManageService.is_oauth_custom_client_enabled(tenant_id, provider),
            credentials=credentials,
        )
        return credential_info

兜底返回体(与真正 builtin 空凭据结构一致):

json
{
  "supported_credential_types": [],
  "is_oauth_custom_client_enabled": false,
  "credentials": []
}

真正的 builtin(time)仍走原来的 get_builtin_provider,不受 except 影响。

3.3 补丁会丢的情况

操作补丁
只 docker restart docker-api-1还在(写在容器可写层)
docker compose up -d --force-recreate / 换镜像丢失,再跑作业1_06
升级 Dify 小版本核对函数是否还在,再打

⚠️ 上面这张表是「直接改容器」的老办法。 现在已改成 override 挂载(见 3.4),补丁不再随容器重建而丢。

3.4 让补丁不再丢:用 docker-compose.override.yaml 挂载(当前生产做法)

问题:docker compose up -d --force-recreate、升级 Dify、或者 207 断电重启后 docker compose up -d,只要容器被重建,改在容器可写层里的 builtin_tools_manage_service.py 就回到原版,红条重现——而且往往是「重启完一阵子才发现」。

做法:把打过补丁的文件放到宿主机一个固定目录,用 compose 的 override 文件只读挂载覆盖容器内那个文件。

目录(宿主机):

text
/dify/dify-main/docker/
├── docker-compose.yaml              # 官方原文件,不动
├── docker-compose.override.yaml     # 新增:只做一件事 —— 挂补丁
└── volumes/
    └── patches/
        └── builtin_tools_manage_service.py      # 打过补丁的完整文件

docker-compose.override.yaml 全文:

yaml
# 修 Internal Server Error:用只读挂载覆盖 api 容器里的那个文件
# 这样容器重建 / 换镜像 / compose up -d 都不会把补丁冲掉
services:
  api:
    volumes:
      - ./volumes/patches/builtin_tools_manage_service.py:/app/api/services/tools/builtin_tools_manage_service.py:ro

生效:

bash
cd /dify/dify-main/docker
docker compose up -d api        # override 会被自动合并,无需 -f 指定
docker exec docker-api-1 sh -lc \
  'ls -la /app/api/services/tools/builtin_tools_manage_service.py && grep -c PluginNotFoundError /app/api/services/tools/builtin_tools_manage_service.py'

为什么这么做:

好处说明
抗重建up -d / --force-recreate / 换镜像后仍在(挂载优先于镜像内容)
可版本化补丁文件本身就是「真相」,和 Dify 源码分离,升级时对比方便
只读:ro,容器里误改不了,避免「补丁漂移」
不留调试残留不用 docker cp / docker exec 手改,重启即生效且可追溯

注意两点:

  1. override 是自动合并的,不要再写 -f docker-compose.override.yaml;写了会与默认合并一次、可能重复。
  2. 升级 Dify 后要确认函数签名还对得上:新版本如果改了 builtin_tools_manage_service.py 的结构,旧补丁文件会直接覆盖掉官方改动。做法是先 docker run 起一个新版容器把原文件捞出来 diff,再决定补丁怎么合。

小提示:线上那个 docker-compose.override.yaml 的注释是乱码(当年用非 UTF-8 编码写入,中文变成了 ?),配置本身不受影响;上面贴的是带正确注释的等价版本,需要时直接覆盖即可。

断电重启后的完整恢复顺序见 207 服务入口与断电重启恢复。


第四部分 CPU 与 GPU:为什么不是资源问题

本专题故障发生在 207 的 Dify API 进程(Python 查插件失败),与算力配额无关。下面把「真资源问题」和「本专题」分开写,避免以后有 GPU 时对错号。

4.1 CPU 侧(207,当前生产)

组件本专题关系真 CPU 不足时长什么样
FastAPI :8777配置页 不调用 它curl /health 超时、对话 observation 空
Xinference bge-m3 / rerank本 Agent 未绑知识库命中测试 >10s、知识库 Agent 变慢
docker-api-1只是查插件元数据,CPU 可忽略全控制台卡死、容器 Restarting
配置页 500代码路径抛 PluginNotFoundError不会表现为 nvidia-smi 或 load average

不要因为配置页红条去加 207 的 CPU、关 Rerank、或重启 Xinference。

4.2 GPU 侧(116 vLLM,当前 + 未来)

组件本专题关系真 GPU 不足时长什么样
Qwen3.6-27B 工具调用对话已成功,说明 GPU 够用CUDA OOM、vLLM 进程退出、/v1/models 超时
--max-num-seqs 8 排队配置页 不走 生成全员对话变慢,命中测试仍快
未来把 Embedding/Rerank 迁 GPU与本红条 无关见专题十三 3.2

配置页打开时 不会 向 116 发 chat/completions。所以:

4.3 何时才该查资源

同时满足才查 CPU/GPU:对话失败或极慢 并且 nvidia-smi / docker stats 异常。只打开配置页报错 → 走本专题步骤 1~2。


第五部分 完整程序与补丁

文件用途
/opt/workbuddy/scripts_cursor/Dify_专题十六_新Agent配置页InternalServerError/专题十六_01_复现与定位.py对照 api / builtin / 对话
scripts_bash_cursor/Dify_专题十六_新Agent配置页InternalServerError/专题十六_02_查日志与补丁.sh207 上看日志与是否已打补丁
/opt/workbuddy/scripts_cursor/Dify_专题十六_新Agent配置页InternalServerError/作业1_06_修复配置页InternalServerError.py恢复原文件 + 精确打补丁 + 重启 + 验收
patches_cursor/Dify_专题十六_新Agent配置页InternalServerError/配置页InternalServerError_credential_info_修改前.py函数对照 · 修改前
patches_cursor/Dify_专题十六_新Agent配置页InternalServerError/配置页InternalServerError_credential_info_修改后.py函数对照 · 修改后

换环境:只改 _config.py 的 SERVER_STATUS_PROVIDER_ID、HOMEWORK1_AGENT_ID、DIFY_API_CTR。


第六部分 验收与回滚

6.1 验收清单

6.2 回滚

从镜像恢复官方文件(作业1_06 开头已在做;单独回滚):

bash
IMG=$(docker inspect docker-api-1 --format '{{.Config.Image}}')
docker run --rm --entrypoint cat "$IMG" \
  /app/api/services/tools/builtin_tools_manage_service.py \
  > /tmp/builtin_tools_orig.py
docker cp /tmp/builtin_tools_orig.py docker-api-1:/app/api/services/tools/builtin_tools_manage_service.py
docker restart docker-api-1

回滚后配置页红条会回来,对话不受影响。


第七部分 对本系统的建议

  1. 挂 OpenAPI 工具用脚本写快照时,默认把本专题补丁纳入 Dify 重建清单(与专题四 system 合并、Langfuse UUID 补丁同类:都在容器内,重建即丢)。
  2. 不要用 UI「工具」页反复删加来消红条,除非确认 Composer 已能列出 API 工具;乱点发布会冲掉 max_tokens(专题十一)。
  3. Dify 升级后先跑 专题十六_01:若官方已修 Composer,credential/info 不再对 UUID 打 builtin,可不再打本补丁。
  4. 培训口径:红条 ≠ 模型挂了。先看对话,再看 docker-api-1 是否 PluginNotFoundError。

*专题十六 · 207 实战:作业 1 配置页红条。补丁已在本环境验证 HTTP 200。*