Dify 接入智谱 GLM 编码套餐完整教程(附代理脚本,彻底解决 max_tokens 报错)
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 接入方式:
| 端点 | 地址 | 协议格式 | 编码套餐能用吗 |
|---|---|---|---|
| 原生 API | open.bigmodel.cn/api/paas/v4 | 智谱原生(类OpenAI) | ❌ 查不到套餐额度 |
| Anthropic 兼容 | open.bigmodel.cn/api/anthropic | Anthropic Messages | ✅ 正常使用 |
| OpenAI 兼容 | open.bigmodel.cn/api/paas/v4 | OpenAI Chat Completions | ❌ 查不到套餐额度 |
关键结论: 编码套餐的额度 只能通过 Anthropic 端点使用。
2.2 Dify 的三个智谱相关供应商
Dify 的模型供应商市场里有几个跟智谱相关的选项:
| 供应商 | 版本 | 协议 | 问题 |
|---|---|---|---|
| 智谱(Anthropic) | 0.3.26 | Anthropic | ❌ 调用时不传 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端点)
代理做三件事:
- 接收 Dify 发来的 OpenAI 格式请求
- 转换成 Anthropic 格式,发给智谱编码套餐端点
- 把智谱的 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 Key | sk-proxy(随便填,代理不验证) |
| API Base URL | http://glm-proxy:5050/v1 |
| endpoint model name | glm-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 中测试
- 进入应用编排页面
- 模型选择 → 选
GLM-5.2 - 点击「发布」
- 打开聊天页面,发一条消息
- 能正常回复就说明全部配置成功
六、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 代理能转发请求 |
| 开启私有IP | SSRF_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 部署前后端分离项目完整教程
更多推荐
所有评论(0)