Dify 接入智谱 GLM 编码套餐完整教程(附代理脚本,彻底解决 max_tokens 报错)

本文记录了我在实际项目中,把智谱 GLM 编码套餐接入 Dify 时踩过的所有坑,以及最终的完整解决方案。
包含可直接使用的代理脚本、Docker 一键部署方案,以及无 Docker 环境的替代方案。

适用人群: 用 Dify 搭建 AI 应用的开发者、买了智谱编码套餐但无法在 Dify 中使用的用户

阅读时长: 约 15 分钟

前置条件: 已部署 Dify(Docker 方式)、有智谱编码套餐 API Key


一、背景:为什么需要这篇文章?

智谱最近推出了 GLM Coding Plan(编码套餐),月费制,价格比按量付费便宜很多,支持 GLM-5.2、GLM-5.1、GLM-4.7 等最新模型。

很多开发者买了之后,想在 Dify 里用,结果遇到一连串报错:

❌ 报错1:余额不足或无可用资源包(错误码 1113)
❌ 报错2:Missing required arguments: max_tokens
❌ 报错3:部分参数无效,已移除:stop(unsupported)

根本原因: 编码套餐的额度只能通过 Anthropic 兼容端点使用,而 Dify 的模型供应商插件和这个端点之间存在参数不兼容的问题。

网上搜了一圈,没有完整的解决方案。所以我自己研究透了原理,写了个代理脚本彻底解决,分享出来。


二、问题原理分析

2.1 智谱的三套 API 端点

智谱目前提供三套不同的 API 接入方式:

端点地址协议格式编码套餐能用吗
原生 APIopen.bigmodel.cn/api/paas/v4智谱原生(类OpenAI)❌ 查不到套餐额度
Anthropic 兼容open.bigmodel.cn/api/anthropicAnthropic Messages✅ 正常使用
OpenAI 兼容open.bigmodel.cn/api/paas/v4OpenAI Chat Completions❌ 查不到套餐额度

关键结论: 编码套餐的额度 只能通过 Anthropic 端点使用

2.2 Dify 的三个智谱相关供应商

Dify 的模型供应商市场里有几个跟智谱相关的选项:

供应商版本协议问题
智谱(Anthropic)0.3.26Anthropic❌ 调用时不传 max_tokens,智谱报错
智谱 AI(原生)0.0.31智谱原生 paas/v4❌ 编码套餐查不到额度
OpenAI-API-compatible-OpenAI✅ 参数完整,但需要自己转换格式

2.3 三个报错的根因

报错1:余额不足(1113)

Dify 智谱AI原生供应商 → 请求发到 paas/v4 端点 → 编码套餐额度不在这个端点 → 报余额不足

报错2:Missing max_tokens

Dify Anthropic 供应商 → 调用插件时没传 max_tokens → 智谱 Anthropic 端点要求必填 → 报错

这是 Dify 的 Anthropic 插件(0.3.26 版本)的一个 bug,它在调用模型时不携带 max_tokens 参数。

报错3:stop(unsupported)

Dify 发送了 stop 参数 → 智谱 Anthropic 端点不支持 → 返回警告

这个是警告级别,不影响功能,但说明两边的参数定义不完全对齐。

2.4 解决思路

既然 Dify 的 OpenAI-API-compatible 供应商参数最完整(不会漏传 max_tokens),而编码套餐只能走 Anthropic 端点,那就:

Dify(OpenAI格式)→ 中间代理(格式转换)→ 智谱(Anthropic端点)

代理做三件事:

  1. 接收 Dify 发来的 OpenAI 格式请求
  2. 转换成 Anthropic 格式,发给智谱编码套餐端点
  3. 把智谱的 Anthropic 格式响应转回 OpenAI 格式返回给 Dify

三、完整解决方案

3.1 架构图

用户在 Dify 聊天
      ↓
Dify 后端(发送 OpenAI 格式请求)
      ↓
glm-proxy 代理(端口 5050)
  ① 自动补全 max_tokens
  ② OpenAI 格式 → Anthropic 格式转换
  ③ 移除不兼容的参数
      ↓
智谱编码套餐 API(open.bigmodel.cn/api/anthropic)
      ↓ 返回 Anthropic 格式响应
glm-proxy 代理
  ④ Anthropic 格式 → OpenAI 格式转换
  ⑤ 流式 SSE 响应适配
      ↓
Dify 前端正常显示回复

3.2 代理脚本完整代码

创建文件 glm_proxy.py

"""
GLM 代理服务
功能:Dify(OpenAI格式)→ 智谱编码套餐(Anthropic端点)的格式转换代理
解决:max_tokens 缺失、stop 参数不兼容、端点不匹配三大问题
"""

import os
from flask import Flask, request, Response
import requests
import json
import time
import uuid

app = Flask(__name__)

# ============ 配置区 ============
# 改成你自己的智谱编码套餐 API Key
API_KEY = os.environ.get("ZHIPU_API_KEY", "你的智谱APIKey")
ZHIPU_URL = "https://open.bigmodel.cn/api/anthropic/v1/messages"
PORT = 5050
# ================================


@app.route('/v1/chat/completions', methods=['POST'])
def chat_completions():
    """接收 OpenAI 格式请求,转换成 Anthropic 格式发给智谱"""
    data = request.get_json()
    is_stream = data.get('stream', False)

    # ---- OpenAI → Anthropic 格式转换 ----
    anthropic_req = {
        "model": data.get("model", "glm-5.2"),
        "max_tokens": data.get("max_tokens") or 4096,  # 关键:自动补全
        "messages": [],
    }

    # 转换 messages(处理 system 消息)
    system_content = None
    for msg in data.get("messages", []):
        role = msg.get("role", "user")
        content = msg.get("content", "")
        if role == "system":
            system_content = content
        elif role == "assistant":
            anthropic_req["messages"].append(
                {"role": "assistant", "content": content}
            )
        else:
            anthropic_req["messages"].append(
                {"role": "user", "content": content}
            )

    if system_content:
        anthropic_req["system"] = system_content

    # 可选参数
    if data.get("temperature") is not None:
        anthropic_req["temperature"] = data["temperature"]
    if data.get("top_p") is not None:
        anthropic_req["top_p"] = data["top_p"]

    # ---- 发送给智谱 ----
    headers = {
        "Content-Type": "application/json",
        "x-api-key": API_KEY,
        "anthropic-version": "2023-06-01",
    }

    # 始终用非流式请求(更稳定),返回时再拆分成流式
    resp = requests.post(
        ZHIPU_URL, json=anthropic_req, headers=headers, timeout=120
    )

    if resp.status_code != 200:
        return Response(
            json.dumps({"error": {"message": resp.text}}),
            status=resp.status_code,
            content_type='application/json'
        )

    # ---- Anthropic → OpenAI 格式转换 ----
    result = resp.json()
    content = ""
    for block in result.get("content", []):
        if block.get("type") == "text":
            content += block.get("text", "")

    chat_id = f"chatcmpl-{uuid.uuid4().hex[:8]}"
    created = int(time.time())
    model_name = anthropic_req["model"]

    if is_stream:
        # 流式响应:拆分成 OpenAI SSE 格式
        def generate():
            # 第一个 chunk:角色信息
            first_chunk = {
                "id": chat_id, "object": "chat.completion.chunk",
                "created": created, "model": model_name,
                "choices": [{"index": 0, "delta": {
                    "role": "assistant", "content": ""
                }, "finish_reason": None}]
            }
            yield f"data: {json.dumps(first_chunk)}\n\n"

            # 内容 chunk:每 20 个字符一个 chunk
            for i in range(0, len(content), 20):
                text_chunk = content[i:i + 20]
                content_chunk = {
                    "id": chat_id, "object": "chat.completion.chunk",
                    "created": created, "model": model_name,
                    "choices": [{"index": 0, "delta": {
                        "content": text_chunk
                    }, "finish_reason": None}]
                }
                yield f"data: {json.dumps(content_chunk)}\n\n"

            # 结束 chunk
            done_chunk = {
                "id": chat_id, "object": "chat.completion.chunk",
                "created": created, "model": model_name,
                "choices": [{"index": 0, "delta": {},
                             "finish_reason": "stop"}]
            }
            yield f"data: {json.dumps(done_chunk)}\n\n"
            yield "data: [DONE]\n\n"

        return Response(
            generate(),
            content_type='text/event-stream',
            headers={'Cache-Control': 'no-cache', 'Connection': 'keep-alive'}
        )
    else:
        # 非流式响应
        openai_resp = {
            "id": chat_id,
            "object": "chat.completion",
            "created": created,
            "model": model_name,
            "choices": [{
                "index": 0,
                "message": {"role": "assistant", "content": content},
                "finish_reason": "stop"
            }],
            "usage": {
                "prompt_tokens": result.get("usage", {}).get(
                    "input_tokens", 0
                ),
                "completion_tokens": result.get("usage", {}).get(
                    "output_tokens", 0
                ),
                "total_tokens": (
                    result.get("usage", {}).get("input_tokens", 0)
                    + result.get("usage", {}).get("output_tokens", 0)
                )
            }
        }
        return Response(
            json.dumps(openai_resp),
            status=200,
            content_type='application/json'
        )


@app.route('/v1/models', methods=['GET'])
def list_models():
    """返回可用模型列表(Dify 验证凭据时会调用)"""
    models = {
        "object": "list",
        "data": [{
            "id": "glm-5.2",
            "object": "model",
            "created": int(time.time()),
            "owned_by": "zhipu"
        }]
    }
    return Response(
        json.dumps(models), status=200, content_type='application/json'
    )


if __name__ == '__main__':
    print("=" * 50)
    print("  GLM 代理服务已启动")
    print(f"  代理地址: http://0.0.0.0:{PORT}")
    print(f"  Dify URL: http://127.0.0.1:{PORT}/v1")
    print("=" * 50)
    app.run(host='0.0.0.0', port=PORT, debug=False)

四、部署方式

方式一:Docker 部署(推荐,Dify 也是 Docker 的话)

4.1.1 创建 Dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY glm_proxy.py .
RUN pip install flask requests -q
CMD ["python", "glm_proxy.py"]
4.1.2 构建并运行
# 构建镜像
docker build -t glm-proxy .

# 运行容器,传入你的 API Key
docker run -d --name glm-proxy -p 5050:5050 \
  -e ZHIPU_API_KEY="你的智谱APIKey" \
  glm-proxy

# 把代理加入 Dify 的 Docker 网络(关键!)
docker network connect docker_default glm-proxy
docker network connect docker_ssrf_proxy_network glm-proxy
4.1.3 修改 Dify 的 SSRF 防护配置

编辑 Dify 的 .env 文件,找到:

SSRF_PROXY_ALLOW_PRIVATE_IPS=

改为:

SSRF_PROXY_ALLOW_PRIVATE_IPS=true

然后重启 Dify:

docker compose up -d
4.1.4 Dify 中配置模型

打开 Dify → 集成 → 模型供应商 → 找到 OpenAI-API-compatible → 添加模型

配置项填入内容
模型名称glm-5.2
模型类型LLM
凭据名称随意,如 智谱编码套餐
显示名称GLM-5.2
API Keysk-proxy(随便填,代理不验证)
API Base URLhttp://glm-proxy:5050/v1
endpoint model nameglm-5.2

点击「添加」,显示「修改成功」即配置完成。


方式二:Python 直接运行(无 Docker 环境)

适合 Dify 不是用 Docker 部署的,或者同事电脑上没有 Docker 的情况。

4.2.1 安装 Python 依赖
pip install flask requests
4.2.2 修改 API Key

打开 glm_proxy.py,找到这一行,改成你的 key:

API_KEY = os.environ.get("ZHIPU_API_KEY", "你的智谱APIKey")
4.2.3 启动代理
python glm_proxy.py

看到以下输出说明启动成功:

==================================================
  GLM 代理服务已启动
  代理地址: http://0.0.0.0:5050
  Dify URL: http://127.0.0.1:5050/v1
==================================================
4.2.4 Dify 中配置

跟方式一一样,只是 API Base URL 不同:

场景API Base URL
Dify 和代理在同一台机器http://127.0.0.1:5050/v1
Dify 用 Docker,代理在本机http://host.docker.internal:5050/v1
代理在另一台机器http://192.168.x.x:5050/v1

五、验证测试

5.1 直接测试代理

# 非流式测试
curl http://127.0.0.1:5050/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer test" \
  -d '{
    "model": "glm-5.2",
    "max_tokens": 100,
    "messages": [{"role": "user", "content": "你好"}]
  }'

正常返回示例:

{
  "id": "chatcmpl-xxxxxxxx",
  "object": "chat.completion",
  "model": "glm-5.2",
  "choices": [{
    "index": 0,
    "message": {"role": "assistant", "content": "你好!很高兴与你交流..."},
    "finish_reason": "stop"
  }]
}

5.2 在 Dify 中测试

  1. 进入应用编排页面
  2. 模型选择 → 选 GLM-5.2
  3. 点击「发布」
  4. 打开聊天页面,发一条消息
  5. 能正常回复就说明全部配置成功

六、Docker 网络原理(扩展阅读)

如果你好奇为什么 Docker 方式需要那么多额外操作,这里简单解释:

┌─────── Docker 网络 docker_default ───────┐
│                                          │
│  docker-api-1(Dify后端)                  │
│  docker-worker-1(Dify异步任务)           │
│  docker-nginx-1(Dify网关)               │
│  glm-proxy(代理服务)  ← 需要加入这个网络  │
│                                          │
│  容器之间用「容器名」互访(Docker内置DNS)   │
└──────────────────────────────────────────┘

三个关键操作:

操作命令作用
加入网络docker network connect docker_default glm-proxy让 Dify 容器能访问代理
加入SSRF网络docker network connect docker_ssrf_proxy_network glm-proxy让 SSRF 代理能转发请求
开启私有IPSSRF_PROXY_ALLOW_PRIVATE_IPS=true让 Dify 允许访问内网地址

Dify 默认有 SSRF(服务器端请求伪造)防护,禁止后端访问内网 IP。因为我们的代理在内网,所以需要放开这个限制。


七、常见问题

Q1:报错 “余额不足”?

检查你的 API Key 是不是编码套餐的 key。编码套餐的 key 和普通平台的 key 是通用的,但额度是分开的。

确认方法:去智谱开放平台 → 编码套餐 → 查看剩余额度。

Q2:报错 “Connection refused”?

代理服务没启动,或者 Docker 网络没配置好。

检查方法:

# 看代理是否在运行
docker ps | grep glm-proxy

# 从 Dify 容器内测试连通性
docker exec docker-api-1 curl http://glm-proxy:5050/v1/models

Q3:Dify 重启后模型消失了?

Docker 容器重启不会丢失 Dify 的配置(存在数据库里),但如果代理容器没设为开机自启,需要手动启动:

docker start glm-proxy

Q4:想给团队多人使用?

方案一:代理部署在服务器上,所有人 Dify 配同一个地址
方案二:打包脚本发给同事,各自本地跑(见方式二)

Q5:支持其他智谱模型吗?

支持。把 Dify 里的模型名和代理脚本的默认模型改成你想用的即可:

glm-5.2 / glm-5.1 / glm-4.7 / glm-4.6 ...

八、总结

问题解决方案
编码套餐只能用 Anthropic 端点代理自动走 Anthropic 端点
Dify 不传 max_tokens代理自动补全
Dify 传了 stop 参数报错代理用 OpenAI 格式,不涉及此参数
Docker 网络隔离代理加入 Dify 网络
SSRF 防护拦截开启 ALLOW_PRIVATE_IPS

核心就一句话:在 Dify 和智谱之间放一个格式转换代理,所有兼容性问题一次性解决。


完整文件清单

项目结构:
├── glm_proxy.py          # 代理脚本(核心)
├── Dockerfile            # Docker 构建文件(可选)
└── .env                  # Dify 配置修改(SSRF)

作者的话: 这套方案是在实际项目中调试出来的,经过多轮测试验证可用。如果你在配置过程中遇到问题,欢迎在评论区留言交流。

如果觉得有帮助,点个赞支持一下,谢谢!

本文为原创内容,转载请注明出处。

📌 相关推荐:Docker 部署前后端分离项目完整教程

更多推荐