Dify_专题四_Agent问答报错_cursor.md
这是基于原文生成的 Cursor 版文档。
原文件未改动;本版只做三件事:统一阅读入口、修正已审出的明显问题、补充更适合快速排障的提示。
Cursor 版修订说明
- 修正示例中的明显笔误
stream=stre->stream=stream。 - 保留原排查主线,并强调这是问答期问题的综合排查文档。
阅读提示
- 若正文中的容器名、目录名、IP 与你的环境不一致,请以实际
docker ps、docker compose ps、目录结构为准。 - 若正文属于“本次实战记录”,应理解为环境实录,不等同于所有环境的唯一正确默认值。
运行环境:业务脚本均在 207 固定目录
/opt/workbuddy执行,Python 为/usr/bin/python3。 Windows 开发机仅保留_probe_*调试脚本;同步命令:python scripts_cursor/完整代码约定/00_部署_workbuddy到207.py。
原文主体(已纳入 Cursor 版纠错)
专题四:创建 Agent 后「尝试问答」报错的综合排查与解决
阅读导航(何时打开 · 报错往哪查)
本文是单点修复/排障专题,配合《安装配置手册》使用;下文实战步骤与代码全部保留,此处只做索引。
| 报错/现象 | 本文案例 | 还可能是 |
|---|---|---|
400 System message | 案例 A + chat.py | 专题八 future 顺序 |
| Connection / no node | 案例 B | 专题五 7 层 |
| tool_calls 解析异常 | 案例 C | vLLM parser qwen3_xml |
| 排查顺序 | §六 落地清单 | 专题十四 变慢 |
适用场景:模型已经接好、知识库也接好了,但真正在 Agent 里发消息问答时却报错。
本文按"先定位是哪一层的问题 → 怎么抓证据 → 怎么复现 → 怎么修 → 怎么验证"的思路展开,
所有可用的配置与代码都直接附在文中,可照抄执行。
一、先认识:问答期报错有哪几类
问答发生在请求已经离开 Dify、打到模型(vLLM/Qwen 或 Xinference/BGE-M3)之后,所以报错不可能来自"模型没接入",而几乎都来自下面三类:
| 类别 | 典型报错 | 出在哪一层 | 一句话根因 |
|---|---|---|---|
| A. 消息格式被模型拒绝 | 400 System message must be at the beginning | 模型端(Qwen chat template)拒绝 Dify 出站消息 | Dify 把多条 system 堆在前面,Qwen3 只接受一条 |
| B. 模型端点不可达 | Connection error / no available node / 超时 | 网络/容器未起 | vLLM 容器没起、端口不通、或插件未就绪 |
| C. 工具调用/推理解析异常 | 工具不触发、JSON 解析错、思考块乱入 | 解析器配置 | --tool-call-parser 选错(如误用 qwen3) |
本文主案例讲 A(最典型、最让人困惑),B 和 C 作为补充案例。
A 和 C 的"根因"都和模型架构强相关,单独部署教程见
Dify_Qwen安装配置手册_cursor.md,vLLM 启动参数见Dify_专题九_其它配置问题解决_cursor.md。
二、主案例 A:400 System message must be at the beginning
2.1 现象
在 Agent 应用里发一条普通问题(例如"描述考勤与请休假管理制度的目的"),页面/API 直接返回 400:
BadRequestError: System message must be at the beginning.只要走 Agent 类应用(带工具/知识检索) 就必现;普通「聊天助手」应用有时不报——因为 Agent 会额外往 system 区塞一堆提示。
2.2 排查思路(不要急着改,先分层)
问答报错
└─ 是不是模型端拒绝?(400 + "System message must be at the beginning")
├─ 是 → 看 Dify 实际发给模型的 messages 长什么样(抓出站日志)
│ └─ 若多条 system / 含空 system → 根因确认:Qwen3 只接受单条 system
└─ 否 → 看是不是端点不可达(B) 或 工具解析(C)关键判断:这条 400 的措辞是模型(Qwen3 的 chat template)自己抛的,说明请求已经到达 vLLM,所以:
- 不是网络问题(排除 B);
- 是"消息内容"不符合 Qwen3 的模板约束。
2.3 抓证据:看 Dify 实际出站的消息
Dify 的 OpenAI 插件在 plugin_daemon 容器内运行,出站 messages 在容器内 /tmp,宿主机的 /tmp 看不到。
先在插件 chat.py 里临时加一段调试日志(验证完要删掉,见 2.6):
文件:.../openai-1.0.3@<hash>/models/llm/chat.py,在 client.chat.completions.create(...) 调用之前插入:
完整程序:/opt/workbuddy/scripts_cursor/Dify_专题四_Agent问答报错/专题四_内联_01.py
运行/引用:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题四_Agent问答报错/专题四_内联_01.py
# DEBUG: dump messages before sending to vLLM/Qwen
try:
with open('/tmp/dify_openai_messages.log', 'a', encoding='utf-8') as _df:
_df.write('--- model=%s stream=%s ---\n' % (model, stream))
_df.write(json.dumps(
[message(item) for item in _ensure_system_first(prompt_messages)],
ensure_ascii=False, indent=2))
_df.write('\n\n')
except Exception as _e:
pass重启插件使日志生效:
完整脚本:scripts_bash_cursor/Dify_专题四_Agent问答报错/专题四_内联_01.sh
运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题四_Agent问答报错/专题四_内联_01.sh
#!/usr/bin/env bash
set -euo pipefail
cd /dify/dify-main/docker && docker compose restart plugin_daemon
# 清空旧日志
docker exec docker-plugin_daemon-1 sh -c 'rm -f /tmp/dify_openai_messages.log && touch /tmp/dify_openai_messages.log'再在 Dify 里发一次问题触发报错,然后读日志:
完整脚本:scripts_bash_cursor/Dify_专题四_Agent问答报错/专题四_内联_02.sh
运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题四_Agent问答报错/专题四_内联_02.sh
#!/usr/bin/env bash
set -euo pipefail
docker exec docker-plugin_daemon-1 cat /tmp/dify_openai_messages.log你会看到:出站消息是多条连续的 system,且其中夹着一条空 system,例如:
[
{"role": "system", "content": "你是企业内部知识助手..."}, // 知识库助手提示
{"role": "system", "content": "You can run commands in an isolated shell workspace..."}, // Agent shell 工具提示
{"role": "system", "content": ""}, // ← 一条空 system
{"role": "system", "content": "Current Agent config manifest..."},// Agent 配置
{"role": "system", "content": "Environment variables may contain..."}, // 环境变量提示
{"role": "user", "content": "描述考勤与请休假管理制度的目的"}
]顺序本身没问题(都在最前),但 Qwen3 的 chat template 拒绝"多条 system 且含空 system"。
2.4 复现确认根因(直连 vLLM)
不用 Dify,直接用 curl 打 vLLM,把三种情况都试一遍,确认根因:
完整脚本:scripts_bash_cursor/Dify_专题四_Agent问答报错/专题四_内联_03.sh
运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题四_Agent问答报错/专题四_内联_03.sh
#!/usr/bin/env bash
set -euo pipefail
BASE=http://116.62.10.183:8000/v1/chat/completions
AUTH='Authorization: Bearer 123456'
H='Content-Type: application/json'
# TEST1:两条 system → 必报错
curl -s -X POST $BASE -H "$H" -H "$AUTH" -d '{
"model":"qwen3.6-27b",
"messages":[{"role":"system","content":"A"},{"role":"system","content":"B"},{"role":"user","content":"hi"}]
}'
# TEST2:含空 system → 必报错
curl -s -X POST $BASE -H "$H" -H "$AUTH" -d '{
"model":"qwen3.6-27b",
"messages":[{"role":"system","content":"A"},{"role":"system","content":""},{"role":"system","content":"C"},{"role":"user","content":"hi"}]
}'
# TEST3:合并成单条 system → 成功
curl -s -X POST $BASE -H "$H" -H "$AUTH" -d '{
"model":"qwen3.6-27b",
"messages":[{"role":"system","content":"A\n\nB\n\nC"},{"role":"user","content":"hi"}]
}'结果:TEST1、TEST2 都返回 System message must be at the beginning;TEST3 正常。根因锁定:Qwen3(qwen3_5 架构)的 chat template 只允许"一条 system 消息且必须在最前"。
注意:早期补丁只把 system 消息"排序置顶",没"合并",所以没用——多条 system 依然被拒。
2.5 解决:把多条 system 合并成一条
改插件 chat.py 的 _ensure_system_first 函数(该函数必须放在 from __future__ import annotations 这一行之后,否则会触发 SyntaxError: from __future__ imports must occur at the beginning,导致整个 openai 插件加载失败、保存凭据时报 "no available node")。
完整函数(当前生效版本,直接替换原 def _ensure_system_first 即可):
完整程序:/opt/workbuddy/scripts_cursor/Dify_专题四_Agent问答报错/专题四_内联_02.py
运行/引用:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题四_Agent问答报错/专题四_内联_02.py
def _ensure_system_first(messages: list[PromptMessage]) -> list[PromptMessage]:
"""Qwen3/vLLM chat templates accept ONLY one system message, at the start.
Dify (esp. Agent apps) stacks several system/developer prompts: KB assistant
prompt + agent tool prompt + an empty system + config manifest... Merging them
into a single system message (joined by blank lines, empties dropped) keeps it
at the front; every other message keeps its original relative order.
"""
system_parts: list[str] = []
rest: list[PromptMessage] = []
for m in messages:
if isinstance(m, (SystemPromptMessage, DeveloperPromptMessage)):
text = _text_content(m)
if text.strip():
system_parts.append(text)
else:
rest.append(m)
if system_parts:
rest.insert(0, SystemPromptMessage(content="\n\n".join(system_parts)))
return rest其中 _text_content 是插件已有的辅助函数(无需新增):
完整程序:/opt/workbuddy/scripts_cursor/Dify_专题四_Agent问答报错/专题四_内联_03.py
运行/引用:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题四_Agent问答报错/专题四_内联_03.py
def _text_content(prompt: PromptMessage) -> str:
if isinstance(prompt.content, str):
return prompt.content
content = prompt.content or []
if any(item.type != PromptMessageContentType.TEXT for item in content):
raise InvokeBadRequestError(
f"{prompt.role.value} messages only support text content"
)
return "".join(cast(TextPromptMessageContent, item).data for item in content)调用处(已在原 generate_chat 里):
完整程序:/opt/workbuddy/scripts_cursor/Dify_专题四_Agent问答报错/专题四_内联_04.py
运行/引用:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题四_Agent问答报错/专题四_内联_04.py
response = client.chat.completions.create(
model=model,
messages=cast(Any, [message(item) for item in _ensure_system_first(prompt_messages)]),
stream=stream # 其余参数不变
...
)改完重启插件加载:
完整脚本:scripts_bash_cursor/Dify_专题四_Agent问答报错/专题四_内联_04.sh
运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题四_Agent问答报错/专题四_内联_04.sh
#!/usr/bin/env bash
set -euo pipefail
cd /dify/dify-main/docker && docker compose restart plugin_daemon2.6 验证(通过 Agent API 实测)
Agent 应用必须用 streaming 模式(blocking 模式会被拒绝),用官方 API key 直接打:
完整脚本:scripts_bash_cursor/Dify_专题四_Agent问答报错/专题四_内联_05.sh
运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题四_Agent问答报错/专题四_内联_05.sh
#!/usr/bin/env bash
set -euo pipefail
BASE=http://192.168.110.207/v1
APIK=app-v9yYVTQTrUoSFHNqAhufSGmh
curl -s -N -X POST $BASE/chat-messages \
-H "Authorization: Bearer $APIK" \
-H "Content-Type: application/json" \
-d '{
"inputs": {},
"query": "描述考勤与请休假管理制度的目的",
"response_mode": "streaming",
"user": "debug-workbuddy"
}'预期:
- 流式事件里没有
event: error; - 出现
event: agent_message的回答片段,且正确检索到「播城内部知识库」给出答案。
验证通过后,把 2.3 加的临时调试日志代码删掉(保持插件干净): 删掉那段 # DEBUG: dump messages... 到 except Exception as _e: pass 为止的块,再 docker compose restart plugin_daemon 一次。
三、补充案例 B:模型端点不可达(Connection error / no available node)
现象
问答时返回 InvokeConnectionError / Connection error / 保存模型凭据时 no available node。
思路
这是"请求没出 Dify 网关"或"出网关后连不上模型"。优先查 vLLM 是否真的在监听:
完整脚本:scripts_bash_cursor/Dify_专题四_Agent问答报错/专题四_内联_06.sh
运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题四_Agent问答报错/专题四_内联_06.sh
#!/usr/bin/env bash
set -euo pipefail
# 在 Dify 服务器上
docker ps --filter name=vllm-qwen3.6-27b # 容器是否 running
docker logs --tail 20 vllm-qwen3.6-27b # 是否 "Application startup complete"
curl -s http://116.62.10.183:8000/v1/models # 端口是否通、模型是否列出常见根因与处理
- vLLM 容器崩溃/没起:先
bash vllm_qwen_run.sh起重(脚本会先docker rm -f旧容器再起,见Dify_专题九_其它配置问题解决_cursor.md)。 - 端口不通(防火墙/跨机):确认
116.62.10.183:8000从 Dify 服务器可达。 no available node:常是插件本身没就绪(例如 2.5 提到的SyntaxError让 openai 插件整体加载失败)→ 查docker logs docker-plugin_daemon-1有无 Python 报错,修好后再 restart。
四、补充案例 C:工具调用 / 推理解析异常
现象
问答时模型"不调用工具"、或返回里混着思考块、或 JSON 解析报错。
思路
这是 vLLM 启动参数里解析器选错,与模型架构强绑定:
- Qwen3.6-27B 是标准 Qwen3 instruct(qwen3_5 架构),
--tool-call-parser必须用qwen3_xml; qwen3这个名字在 vLLM 任何版本都不存在(会KeyError: invalid tool call parser);qwen3_coder仅给 Qwen3-Coder;hermes是 Qwen3 MoE 默认,都不适用本模型;- 再加
--reasoning-parser qwen3(vLLM 0.25.1 内置),把思考块从工具调用里分离,更稳。
见 vllm_qwen_run.sh 当前生效参数:
完整脚本:scripts_bash_cursor/Dify_专题四_Agent问答报错/专题四_内联_07.sh
运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题四_Agent问答报错/专题四_内联_07.sh
#!/usr/bin/env bash
set -euo pipefail
docker.m.daocloud.io/vllm/vllm-openai:latest \
/model \
--trust-remote-code --dtype bfloat16 --tensor-parallel-size 1 \
--enable-auto-tool-choice --tool-call-parser qwen3_xml --reasoning-parser qwen3 \
--max-model-len 65536 --gpu-memory-utilization 0.75 --max-num-seqs 8 \
--served-model-name qwen3.6-27b --enable-prefix-caching --host 0.0.0.0 --port 8000改完重启 vLLM 容器即可(bash vllm_qwen_run.sh)。
五、通用调试手法小结(问答报错必看)
- 抓 Dify 出站消息:在
plugin_daemon容器内/tmp/dify_openai_messages.log,不是宿主机/tmp;改完要docker exec docker-plugin_daemon-1 cat ...。 - 直连模型复现:绕过 Dify,用 curl 直打 vLLM/Xinference,能快速区分"是 Dify 拼消息的锅"还是"模型本身的锅"。
- 看容器日志定位插件加载:
docker logs docker-plugin_daemon-1能发现SyntaxError/ import 失败等导致插件整体挂掉的问题。 - 改完必须重启:插件代码改动后都要
docker compose restart plugin_daemon;vLLM 参数改动要重启 vLLM 容器。 - Agent 应用必须用 streaming 测:用 blocking 模式调 Agent API 会直接被拒,不是业务报错。
六、落地清单(问答报错排查顺序)
- [ ] 看报错措辞:是
System message...(A)、Connection(B)还是工具/JSON(C)? - [ ] A 类:加临时调试日志 → 抓出站 messages → 确认多条/含空 system → 应用 2.5 合并补丁 → 重启 → streaming 验证 → 删调试日志。
- [ ] B 类:
docker ps/docker logs/curl /v1/models确认 vLLM 在跑且端口通。 - [ ] C 类:确认 vLLM
--tool-call-parser qwen3_xml+--reasoning-parser qwen3,不对则改vllm_qwen_run.sh起重。 - [ ] 验证统一用 Agent API
streaming模式,确认无event: error且能检索知识库。
风险提示(通用):本文 A、C 涉及的插件补丁与 vLLM 参数,插件升级/重装会覆盖
chat.py的补丁,升级后若再次报
System message must be at the beginning,按本文 2.3–2.5 重打一遍即可。已根治(2026-09-24 起):
model类插件的自动升级已关闭、langgenius/openai已加入排除名单,补丁不会再被夜间自动升级冲掉 → 专题十七 · §5 根治。
另注:openai 插件
1.0.5上游已原生实现单条 leading system 合并(_single_leading_system),该版本上本补丁已冗余,不要再打,以免和上游逻辑互相干扰。
完整从零部署见
Dify_Qwen安装配置手册_cursor.md;专题一/二/三分别覆盖"接入 Qwen""Xinference 跑 bge-m3""Agent 知识库选不到"。
七、实战记录(本次部署真实遇到的情况)
本次问答报错的 400,就是主案例 A 那一连串排查的真实过程。按统一维度拆解。
现象:vLLM 已起来,在 Agent 里发"描述考勤与请休假管理制度的目的",页面直接返回 400 System message must be at the beginning。
定位(抓出站消息):在 chat.py 临时加调试日志,抓 plugin_daemon 容器内 /tmp/dify_openai_messages.log,发现 Dify 出站是 5 条连续 system(知识库助手提示 + Agent shell 工具提示 + 一条空 system + 配置 + 环境变量),顺序没问题但 Qwen3 拒收多条 system。 完整脚本:scripts_bash_cursor/Dify_专题四_Agent问答报错/专题四_内联_08.sh
运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题四_Agent问答报错/专题四_内联_08.sh
#!/usr/bin/env bash
set -euo pipefail
# 加日志位置:client.chat.completions.create(...) 之前
# with open('/tmp/dify_openai_messages.log','a') as f: f.write(json.dumps([...]))
cd /dify/dify-main/docker && docker compose restart plugin_daemon
docker exec docker-plugin_daemon-1 sh -c 'rm -f /tmp/dify_openai_messages.log'
docker exec docker-plugin_daemon-1 cat /tmp/dify_openai_messages.log改了什么:把 _ensure_system_first 从早期"只排序"改成"合并"(去空、\n\n 连接成单条),见本文 2.5;并重申该函数必须写在 from __future__ import annotations 之后(否则触发专题八的 SyntaxError)。
怎么判断根因(直连复现):用 curl 直打 vLLM —— 两条 system、含空 system 都报同样 400,合并成单条则成功,确属 Qwen3(qwen3_5 架构)只接受一条 system。
怎么重启服务: 完整脚本:scripts_bash_cursor/Dify_专题四_Agent问答报错/专题四_内联_09.sh
运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题四_Agent问答报错/专题四_内联_09.sh
#!/usr/bin/env bash
set -euo pipefail
cd /dify/dify-main/docker && docker compose restart plugin_daemon
sleep 8 && docker ps --filter name=plugin_daemon --format '{{.Names}} {{.Status}}'怎么看日志: 完整脚本:scripts_bash_cursor/Dify_专题四_Agent问答报错/专题四_内联_10.sh
运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题四_Agent问答报错/专题四_内联_10.sh
#!/usr/bin/env bash
set -euo pipefail
docker logs docker-plugin_daemon-1 2>&1 | grep -iE 'error|syntax|traceback' | tail -20 # 无报错即 OK怎么判断正常(验证):用 Agent API streaming 模式实测: 完整脚本:scripts_bash_cursor/Dify_专题四_Agent问答报错/专题四_内联_11.sh
运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_专题四_Agent问答报错/专题四_内联_11.sh
#!/usr/bin/env bash
set -euo pipefail
curl -s -N -X POST http://192.168.110.207/v1/chat-messages \
-H "Authorization: Bearer app-v9yYVTQTrUoSFHNqAhufSGmh" \
-H "Content-Type: application/json" \
-d '{"inputs":{},"query":"描述考勤与请休假管理制度的目的","response_mode":"streaming","user":"debug"}'流式事件里无 event: error、出现 event: agent_message 且正确检索「播城内部知识库」给出答案 → 通过。
收尾:验证通过后把临时调试日志代码删掉,docker compose restart plugin_daemon 一次,插件恢复干净。
一句话对照:专题四前面是"分层方法与代码",本节是"本次那个真实的 400 是怎么被定位并修掉的"。
本专题 Python 程序一览
| 文件 | 用途 | |---|---|| 专题四_01_加出站调试日志.py | 专题四 · 步骤 1:在 chat.py 注入临时调试日志(验证后请运行删日志脚本)。 | | 专题四_02_读出站消息日志.py | 专题四 · 步骤 2:读取 plugin_daemon 容器内出站 messages 调试日志。 | | 专题四_03_vLLM复现system消息.py | 专题四 · 步骤 3:直连 vLLM 复现多条/空 system 与合并后成功。 | | 专题四_04_合并system补丁_修改后.py | 专题四 · 步骤 4 · 修改后:替换 chat.py 的 _ensure_system_first 为合并逻辑。 | | 专题四_05_删调试日志_修改后.py | 专题四 · 步骤 5 · 修改后:删除 chat.py 临时调试日志块。 | | 专题四_06_Agent流式验收.py | 专题四 · 步骤 6:Agent 公开 API streaming 模式验收(无 event: error)。 |
专题四_01_加出站调试日志.py
运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题四_Agent问答报错/专题四_01_加出站调试日志.py
完整补丁:patches_cursor/Dify_专题四_Agent问答报错/专题四_内联_01.py
运行/引用:见 patches_cursor/Dify_专题四_Agent问答报错/专题四_内联_01.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""专题四 · 步骤 1:在 chat.py 注入临时调试日志(验证后请运行删日志脚本)。"""
from __future__ import annotations
import sys
import _bootstrap # noqa: F401
import _config as C
import _plugin_patch as P
from dify_api import Dify
def main() -> int:
d = Dify(C.DIFY_EMAIL, C.DIFY_PASSWORD)
try:
d.login()
chat_path = P.find_plugin_file(d, "chat.py")
print("chat.py:", chat_path)
content = P.read_remote_file(d, chat_path)
new_content = P.inject_debug_log(content)
P.deploy_plugin_file(d, chat_path, new_content)
d.sh(
"docker exec %s sh -c 'rm -f /tmp/dify_openai_messages.log && "
"touch /tmp/dify_openai_messages.log'" % C.PLUGIN_CONTAINER
)
print("已重启 plugin_daemon 并清空 /tmp/dify_openai_messages.log")
return 0
finally:
d.close()
if __name__ == "__main__":
sys.exit(main())专题四_02_读出站消息日志.py
运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题四_Agent问答报错/专题四_02_读出站消息日志.py
完整补丁:patches_cursor/Dify_专题四_Agent问答报错/专题四_内联_02.py
运行/引用:见 patches_cursor/Dify_专题四_Agent问答报错/专题四_内联_02.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""专题四 · 步骤 2:读取 plugin_daemon 容器内出站 messages 调试日志。"""
from __future__ import annotations
import sys
import _bootstrap # noqa: F401
import _config as C
from dify_api import Dify
def main() -> int:
d = Dify(C.DIFY_EMAIL, C.DIFY_PASSWORD)
try:
d.login()
out, err = d.sh(
"docker exec %s cat /tmp/dify_openai_messages.log" % C.PLUGIN_CONTAINER
)
text = out or err
if not text.strip():
print("[提示] 日志为空。请先在 Agent 里发一条问题触发报错,再运行本脚本。")
return 1
print(text)
return 0
finally:
d.close()
if __name__ == "__main__":
sys.exit(main())专题四_03_vLLM复现system消息.py
运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题四_Agent问答报错/专题四_03_vLLM复现system消息.py
完整补丁:patches_cursor/Dify_专题四_Agent问答报错/专题四_内联_03.py
运行/引用:见 patches_cursor/Dify_专题四_Agent问答报错/专题四_内联_03.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""专题四 · 步骤 3:直连 vLLM 复现多条/空 system 与合并后成功。"""
from __future__ import annotations
import json
import sys
import _bootstrap # noqa: F401
import _config as C
from dify_api import Dify
def curl_chat(d: Dify, messages: list[dict], label: str) -> None:
body = json.dumps({"model": C.LLM_MODEL, "messages": messages})
remote = "/tmp/vllm_sys_test.json"
with d.sftp.open(remote, "w") as f:
f.write(body)
cmd = (
"curl -s -X POST %s/v1/chat/completions "
"-H 'Content-Type: application/json' "
"-H 'Authorization: Bearer %s' "
"-d @%s" % (C.VLLM_URL, C.VLLM_KEY, remote)
)
out, err = d.sh(cmd, t=120)
print("\n=== %s ===" % label)
print(out or err)
try:
j = json.loads(out)
err_msg = j.get("error", {}).get("message") or j.get("message")
if err_msg:
print("[error]", err_msg)
except Exception:
pass
def main() -> int:
d = Dify(C.DIFY_EMAIL, C.DIFY_PASSWORD)
try:
d.login()
curl_chat(
d,
[
{"role": "system", "content": "A"},
{"role": "system", "content": "B"},
{"role": "user", "content": "hi"},
],
"TEST1 两条 system(期望 400)",
)
curl_chat(
d,
[
{"role": "system", "content": "A"},
{"role": "system", "content": ""},
{"role": "system", "content": "C"},
{"role": "user", "content": "hi"},
],
"TEST2 含空 system(期望 400)",
)
curl_chat(
d,
[
{"role": "system", "content": "A\n\nB\n\nC"},
{"role": "user", "content": "hi"},
],
"TEST3 合并单条 system(期望成功)",
)
return 0
finally:
d.close()
if __name__ == "__main__":
sys.exit(main())专题四_04_合并system补丁_修改后.py
运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题四_Agent问答报错/专题四_04_合并system补丁_修改后.py
完整补丁:patches_cursor/Dify_专题四_Agent问答报错/专题四_内联_04.py
运行/引用:见 patches_cursor/Dify_专题四_Agent问答报错/专题四_内联_04.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""专题四 · 步骤 4 · 修改后:替换 chat.py 的 _ensure_system_first 为合并逻辑。"""
from __future__ import annotations
import sys
import _bootstrap # noqa: F401
import _config as C
import _plugin_patch as P
from dify_api import Dify
def main() -> int:
d = Dify(C.DIFY_EMAIL, C.DIFY_PASSWORD)
try:
d.login()
chat_path = P.find_plugin_file(d, "chat.py")
print("chat.py:", chat_path)
content = P.read_remote_file(d, chat_path)
new_content = P.replace_function(content, "_ensure_system_first", P.ENSURE_SYSTEM_FIRST)
P.deploy_plugin_file(d, chat_path, new_content)
print("已应用合并 system 补丁并重启 plugin_daemon")
return 0
finally:
d.close()
if __name__ == "__main__":
sys.exit(main())专题四_05_删调试日志_修改后.py
运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题四_Agent问答报错/专题四_05_删调试日志_修改后.py
完整补丁:patches_cursor/Dify_专题四_Agent问答报错/专题四_内联_05.py
运行/引用:见 patches_cursor/Dify_专题四_Agent问答报错/专题四_内联_05.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""专题四 · 步骤 5 · 修改后:删除 chat.py 临时调试日志块。"""
from __future__ import annotations
import sys
import _bootstrap # noqa: F401
import _config as C
import _plugin_patch as P
from dify_api import Dify
def main() -> int:
d = Dify(C.DIFY_EMAIL, C.DIFY_PASSWORD)
try:
d.login()
chat_path = P.find_plugin_file(d, "chat.py")
content = P.read_remote_file(d, chat_path)
new_content = P.remove_debug_log(content)
P.deploy_plugin_file(d, chat_path, new_content)
print("已删除调试日志并重启 plugin_daemon")
return 0
finally:
d.close()
if __name__ == "__main__":
sys.exit(main())专题四_06_Agent流式验收.py
运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_专题四_Agent问答报错/专题四_06_Agent流式验收.py
完整补丁:patches_cursor/Dify_专题四_Agent问答报错/专题四_内联_06.py
运行/引用:见 patches_cursor/Dify_专题四_Agent问答报错/专题四_内联_06.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""专题四 · 步骤 6:Agent 公开 API streaming 模式验收(无 event: error)。"""
from __future__ import annotations
import json
import sys
import _bootstrap # noqa: F401
import _config as C
from dify_api import Dify
def main() -> int:
d = Dify(C.DIFY_EMAIL, C.DIFY_PASSWORD)
try:
d.login()
body = {
"inputs": {},
"query": C.AGENT_TEST_QUERY,
"response_mode": "streaming",
"user": "debug-workbuddy",
}
remote = "/tmp/agent_stream_test.json"
with d.sftp.open(remote, "w") as f:
f.write(json.dumps(body, ensure_ascii=False))
cmd = (
"curl -s -N -m 420 -X POST %s/v1/chat-messages "
"-H 'Authorization: Bearer %s' "
"-H 'Content-Type: application/json' "
"-d @%s" % (C.DIFY_BASE, C.APP_API_KEY, remote)
)
out, err = d.sh(cmd, t=450)
text = out or err
print(text[-2000:] if len(text) > 2000 else text)
if "event: error" in text:
print("\n[FAIL] 流式响应含 event: error")
return 1
if "agent_message" in text or "message" in text:
print("\n[OK] 流式响应正常")
return 0
print("\n[WARN] 未检测到 agent_message,请人工核对上方输出")
return 1
finally:
d.close()
if __name__ == "__main__":
sys.exit(main())