Dify工作流节点详细配置(方案B最终版)
本文档详细描述了Dify工作流中方案B的14个节点配置。工作流从初始化会话开始,进入主学习循环,包含获取幻灯片内容、显示题目、用户作答、评分反馈等环节。系统根据用户得分(0-3题正确)自动切换学习路径(A速通/B正常/C刷题),循环处理每个幻灯片直到课程结束,最后生成学习报告。文档采用Mermaid流程图可视化展示节点关系,并详细说明了每个节点的输入输出变量、代码逻辑和条件判断规则,实现了完整的自适应学习流程。
3题全对
答对2题
0-1题
未结束
已结束
继续循环
开始节点
代码执行: 初始化会话
调用API获取第一个slide
循环节点开始
代码执行: 获取当前slide
调用GET /api/slides/slide_id
LLM: 显示Slide内容
包含标题、内容、图片
代码执行: 获取测试题
从slide数据中提取quiz
LLM: 显示3道测试题
要求用户作答
LLM: 等待用户输入答案
格式: A,B,C
代码执行: 评分
计算正确题数
代码执行: 提交分数
POST /api/quiz/submit
获取下一个slide_id和路径
条件分支: 判断分数
变量赋值: 路径=A
message=切换到速通
变量赋值: 路径=B
message=继续正常
变量赋值: 路径=C
message=切换到刷题
LLM: 显示反馈
告知用户分数和路径
代码执行: 更新变量
current_slide_id=next_slide_id
条件分支: 是否结束
next_slide_id是否为null
循环终止条件检查
代码执行: 获取学习报告
GET /api/session/user_id/report
LLM: 显示学习报告
路径、成绩、建议
结束节点
节点总数:14个
- 1个开始节点
- 5个代码执行节点
- 5个LLM节点
- 1个条件分支节点
- 1个循环节点
- 1个结束节点
节点1: 开始节点
节点类型: 开始
输入变量:
yaml
course_id:
类型: String
描述: 课程ID
必填: 是
示例: "1"
user_id:
类型: String
描述: 用户ID
必填: 是
示例: "user_123"
节点2: 代码执行 - 初始化会话
节点名称: 初始化会话
输入变量 : course_id, user_id
代码:
python
def main(course_id, user_id):
import requests
import json
# 调用外部API开始课程
api_url = "http://localhost:8000/api/courses/start"
try:
response = requests.post(
api_url,
json={
"course_id": int(course_id),
"user_id": user_id
},
headers={"Content-Type": "application/json"},
timeout=10
)
if response.status_code != 200:
return {
"session_id": None,
"current_slide_id": None,
"current_path": "B",
"error": f"API error: {response.status_code}"
}
data = response.json()
return {
"session_id": data.get("session_id"),
"current_slide_id": data.get("first_slide_id"),
"current_path": "B",
"error": None
}
except Exception as e:
return {
"session_id": None,
"current_slide_id": None,
"current_path": "B",
"error": str(e)
}
输出变量:
yaml
session_id:
类型: String
描述: 会话ID
current_slide_id:
类型: String
描述: 第一个slide的ID
current_path:
类型: String
描述: 初始路径(默认B)
error:
类型: String
描述: 错误信息(如果有)
节点3: 循环节点
节点名称: 学习循环
循环变量:
yaml
current_slide_id:
类型: String
初始值: {{初始化会话.current_slide_id}}
说明: 当前正在学习的slide ID
session_id:
类型: String
初始值: {{初始化会话.session_id}}
说明: 用户会话ID(在整个循环中保持不变)
current_path:
类型: String
初始值: {{初始化会话.current_path}}
说明: 当前学习路径(A/B/C)
循环终止条件:
yaml
添加条件:
变量: 循环 {{x}} current_slide_id
运算符: 为空
值: (留空)
说明: 当current_slide_id为None/null时,表示课程结束
最大循环次数: 50
循环变量更新规则:
yaml
每次循环结束后,将以下节点的输出更新到循环变量:
current_slide_id: {{提交并获取下一步.next_slide_id}}
current_path: {{提交并获取下一步.new_path}}
session_id: {{初始化会话.session_id}} (保持不变)
节点4: 代码执行 - 获取当前Slide
节点名称: 获取Slide数据
输入变量:
current_slide_id(来自循环变量)session_id(来自循环变量)
代码:
python
def main(current_slide_id, session_id):
import requests
import json
if not current_slide_id or current_slide_id == "None":
return {
"slide_id": None,
"slide_title": "",
"content_text": "",
"images_text": "",
"quiz_json": "{}",
"slide_type": "",
"error": "No slide_id"
}
api_url = f"http://localhost:8000/api/slides/{current_slide_id}"
try:
response = requests.get(
api_url,
params={"session_id": session_id},
headers={"Content-Type": "application/json"},
timeout=10
)
if response.status_code != 200:
return {
"slide_id": current_slide_id,
"slide_title": "Error",
"content_text": f"Failed to load slide: {response.status_code}",
"images_text": "",
"quiz_json": "{}",
"slide_type": "",
"error": f"API error: {response.status_code}"
}
data = response.json()
# 格式化slide内容
content = data.get("content", {})
headings = content.get("headings", [])
paragraphs = content.get("paragraphs", [])
images = content.get("images", [])
# 组装内容文本
content_text = ""
for heading in headings:
content_text += f"## {heading}\n\n"
for para in paragraphs:
content_text += f"{para}\n\n"
# 组装图片文本
images_text = ""
for img in images:
images_text += f"\n[图片: {img.get('caption', '无标题')}]"
# 将quiz转为JSON字符串
quiz_json = json.dumps(data.get("quiz", {}), ensure_ascii=False)
return {
"slide_id": data.get("id"),
"slide_title": data.get("title", ""),
"content_text": content_text.strip(),
"images_text": images_text,
"quiz_json": quiz_json,
"slide_type": data.get("type", "X"),
"error": None
}
except Exception as e:
return {
"slide_id": current_slide_id,
"slide_title": "Error",
"content_text": f"Exception: {str(e)}",
"images_text": "",
"quiz_json": "{}",
"slide_type": "",
"error": str(e)
}
输出变量:
yaml
slide_id:
类型: String
slide_title:
类型: String
content_text:
类型: String
images_text:
类型: String
quiz_json:
类型: String
slide_type:
类型: String
error:
类型: String
节点5: LLM - 显示Slide内容
节点名称: 展示Slide
模型: deepseek-ai/DeepSeek-V3 (或其他可用模型)
SYSTEM:
你是一个专业的课程讲解助手。你的任务是清晰地展示课程内容,帮助学生理解知识点。
请用友好、鼓励的语气展示内容,让学生感到学习是愉快的。
USER:
【Slide类型: {{获取Slide数据.slide_type}}】
# {{获取Slide数据.slide_title}}
{{获取Slide数据.content_text}}
{{获取Slide数据.images_text}}
---
请仔细阅读以上内容。理解后,输入"继续"或"下一步"开始测试。
输出设置:
yaml
结构化输出: 关闭
输出变量:
yaml
text:
类型: String
描述: LLM生成的展示内容
usage:
类型: Object
描述: Token使用情况
节点6: 代码执行 - 解析测试题
节点名称: 准备测试题
输入变量 : quiz_json (来自节点4)
代码:
python
def main(quiz_json):
import json
try:
quiz = json.loads(quiz_json)
except:
return {
"quiz_text": "本slide没有测试题,请继续下一个slide。",
"question1": "",
"question2": "",
"question3": "",
"correct1": "",
"correct2": "",
"correct3": "",
"has_quiz": False
}
questions = quiz.get("questions", [])
if len(questions) < 3:
return {
"quiz_text": "测试题数量不足,请联系管理员。",
"question1": "",
"question2": "",
"question3": "",
"correct1": "",
"correct2": "",
"correct3": "",
"has_quiz": False
}
# 格式化测试题文本
quiz_text = "请回答以下3道测试题:\n\n"
for i, q in enumerate(questions[:3], 1):
quiz_text += f"**题目 {i}**: {q.get('question')}\n"
for key, value in sorted(q.get("options", {}).items()):
quiz_text += f" {key}. {value}\n"
quiz_text += "\n"
quiz_text += "\n请按照格式输入答案,例如: A,B,C 或 A B C"
return {
"quiz_text": quiz_text,
"question1": questions[0].get("question", ""),
"question2": questions[1].get("question", ""),
"question3": questions[2].get("question", ""),
"correct1": questions[0].get("correct", ""),
"correct2": questions[1].get("correct", ""),
"correct3": questions[2].get("correct", ""),
"has_quiz": True
}
输出变量:
yaml
quiz_text:
类型: String
描述: 格式化后的测试题文本
question1:
类型: String
描述: 第1题题目
question2:
类型: String
描述: 第2题题目
question3:
类型: String
描述: 第3题题目
correct1:
类型: String
描述: 第1题正确答案
correct2:
类型: String
描述: 第2题正确答案
correct3:
类型: String
描述: 第3题正确答案
has_quiz:
类型: Boolean
描述: 是否有有效的测试题
节点7: LLM - 显示测试题并获取答案
节点名称: 测试交互
模型: deepseek-ai/DeepSeek-V3
SYSTEM:
你是一个测试助手。
你的任务:
1. 展示测试题目
2. 等待用户输入答案
3. 识别用户的3个答案选项(只提取A、B、C、D这些字母)
4. 按照"答案1,答案2,答案3"的格式返回
用户可能的输入格式:
- A,B,C
- A B C
- ABC
- "我选A第二题B第三题C"
你需要提取出3个字母,然后只返回格式化后的答案,例如"A,B,C"
重要:只返回格式化后的答案,不要有任何其他文字。
USER:
{{准备测试题.quiz_text}}
请输入你的答案:
输出变量:
yaml
text:
类型: String
描述: 格式化后的答案,如"A,B,C"
usage:
类型: Object
节点8: 代码执行 - 评分
节点名称: 计算分数
输入变量:
text(来自节点7,用户答案)correct1,correct2,correct3(来自节点6)
代码:
python
def main(text, correct1, correct2, correct3):
import re
# 提取答案中的字母
user_answers_raw = text.strip().upper()
answers = re.findall(r'[A-D]', user_answers_raw)
if len(answers) != 3:
return {
"correct_count": 0,
"answer1": "",
"answer2": "",
"answer3": "",
"is_correct1": False,
"is_correct2": False,
"is_correct3": False,
"error": f"答案格式错误,提取到{len(answers)}个答案,需要3个"
}
correct_answers = [correct1, correct2, correct3]
correct_count = 0
is_correct = [False, False, False]
for i in range(3):
if answers[i] == correct_answers[i]:
correct_count += 1
is_correct[i] = True
return {
"correct_count": correct_count,
"answer1": answers[0],
"answer2": answers[1],
"answer3": answers[2],
"is_correct1": is_correct[0],
"is_correct2": is_correct[1],
"is_correct3": is_correct[2],
"error": None
}
输出变量:
yaml
correct_count:
类型: Number
描述: 答对的题数
answer1:
类型: String
answer2:
类型: String
answer3:
类型: String
is_correct1:
类型: Boolean
is_correct2:
类型: Boolean
is_correct3:
类型: Boolean
error:
类型: String
节点9: 代码执行 - 提交分数
节点名称: 提交并获取下一步
输入变量:
current_slide_id(循环变量)correct_count(来自节点8)session_id(循环变量)
代码:
python
def main(current_slide_id, correct_count, session_id):
import requests
import json
api_url = "http://localhost:8000/api/quiz/submit"
try:
response = requests.post(
api_url,
json={
"session_id": session_id,
"slide_id": current_slide_id,
"correct_count": int(correct_count)
},
headers={"Content-Type": "application/json"},
timeout=10
)
if response.status_code != 200:
return {
"next_slide_id": None,
"new_path": "B",
"path_changed": False,
"message": f"提交失败: {response.status_code}",
"error": f"API error: {response.status_code}"
}
data = response.json()
return {
"next_slide_id": data.get("next_slide_id"),
"new_path": data.get("new_path", "B"),
"path_changed": data.get("path_changed", False),
"message": data.get("message", ""),
"error": None
}
except Exception as e:
return {
"next_slide_id": None,
"new_path": "B",
"path_changed": False,
"message": f"提交失败: {str(e)}",
"error": str(e)
}
输出变量:
yaml
next_slide_id:
类型: String
描述: 下一个slide的ID(如果为None表示课程结束)
new_path:
类型: String
描述: 新的学习路径(A/B/C)
path_changed:
类型: Boolean
描述: 路径是否发生了变化
message:
类型: String
描述: 反馈信息
error:
类型: String
描述: 错误信息(如果有)
节点10: LLM - 显示反馈
节点名称: 展示反馈
模型: deepseek-ai/DeepSeek-V3
SYSTEM:
你是一个学习反馈助手。向学生展示他们的测试结果和下一步学习路径。
请用鼓励的语气给予反馈,即使学生答错了,也要给予积极的建议。
USER:
测试完成!
你答对了 {{计算分数.correct_count}} 道题(共3题)。
详细结果:
- 题目1: 你的答案 {{计算分数.answer1}} | 正确答案 {{准备测试题.correct1}} | {{计算分数.is_correct1}}
- 题目2: 你的答案 {{计算分数.answer2}} | 正确答案 {{准备测试题.correct2}} | {{计算分数.is_correct2}}
- 题目3: 你的答案 {{计算分数.answer3}} | 正确答案 {{准备测试题.correct3}} | {{计算分数.is_correct3}}
---
路径更新: {{提交并获取下一步.message}}
当前学习路径: {{提交并获取下一步.new_path}}路径
准备进入下一个Slide...
输出变量:
yaml
text:
类型: String
usage:
类型: Object
节点11: 条件分支 - 检查是否结束
节点名称: 判断课程结束
条件配置:
yaml
IF条件:
变量: {{提交并获取下一步.next_slide_id}}
运算符: 不为空
值: (留空)
下一步:
IF分支: 返回到循环节点(继续学习)
ELSE分支: 进入"生成报告"节点
说明:
- 如果
next_slide_id不为空,说明还有下一个slide,继续循环 - 如果
next_slide_id为空/None,说明课程结束,生成报告
节点12: 代码执行 - 获取学习报告
节点名称: 生成报告
输入变量:
session_id(循环变量)user_id(开始节点)
代码:
python
def main(session_id, user_id):
import requests
api_url = f"http://localhost:8000/api/session/{user_id}/report"
try:
response = requests.get(
api_url,
params={"session_id": session_id},
headers={"Content-Type": "application/json"},
timeout=10
)
if response.status_code != 200:
return {
"report_text": f"生成报告失败: {response.status_code}",
"total_slides": 0,
"accuracy": 0,
"path_a_count": 0,
"path_b_count": 0,
"path_c_count": 0,
"error": f"API error: {response.status_code}"
}
data = response.json()
# 格式化报告
path_history = " → ".join(data.get("path_history", []))
report_text = f"""
# 学习报告
## 学习路径
{path_history}
## 统计数据
- 完成slides数: {data.get('total_slides_completed', 0)}
- 总答题数: {data.get('total_questions', 0)}
- 正确率: {data.get('accuracy', 0)}%
- 学习时长: {data.get('duration', 0)}分钟
## 路径分布
- A路径(速通): {data.get('path_distribution', {}).get('A', 0)} 次
- B路径(正常): {data.get('path_distribution', {}).get('B', 0)} 次
- C路径(刷题): {data.get('path_distribution', {}).get('C', 0)} 次
## 知识点掌握情况
{data.get('knowledge_summary', '无数据')}
## 学习建议
{data.get('recommendations', '继续保持!')}
"""
return {
"report_text": report_text,
"total_slides": data.get('total_slides_completed', 0),
"accuracy": data.get('accuracy', 0),
"path_a_count": data.get('path_distribution', {}).get('A', 0),
"path_b_count": data.get('path_distribution', {}).get('B', 0),
"path_c_count": data.get('path_distribution', {}).get('C', 0),
"error": None
}
except Exception as e:
return {
"report_text": f"生成报告时出错: {str(e)}",
"total_slides": 0,
"accuracy": 0,
"path_a_count": 0,
"path_b_count": 0,
"path_c_count": 0,
"error": str(e)
}
输出变量:
yaml
report_text:
类型: String
描述: 格式化的报告文本
total_slides:
类型: Number
描述: 完成的slide总数
accuracy:
类型: Number
描述: 正确率(百分比)
path_a_count:
类型: Number
path_b_count:
类型: Number
path_c_count:
类型: Number
error:
类型: String
节点13: LLM - 显示学习报告
节点名称: 展示报告
模型: deepseek-ai/DeepSeek-V3
SYSTEM:
你是一个学习总结助手。向学生展示他们的学习成果和建议。
请用鼓励和积极的语气总结学生的学习情况,给予具体的改进建议。
USER:
恭喜你完成本次学习!
{{生成报告.report_text}}
---
感谢使用自适应学习系统!
希望这次学习对你有所帮助。继续保持学习热情!
输出变量:
yaml
text:
类型: String
usage:
类型: Object
节点14: 结束节点
节点类型: 结束
输出变量:
yaml
将以下变量作为最终输出:
final_report:
类型: String
值: {{展示报告.text}}
total_slides:
类型: Number
值: {{生成报告.total_slides}}
accuracy:
类型: Number
值: {{生成报告.accuracy}}
关键配置注意事项
1. 循环节点的变量更新
循环变量更新规则(重要):
在循环节点配置界面中:
yaml
循环开始时的初始值:
current_slide_id: {{初始化会话.current_slide_id}}
session_id: {{初始化会话.session_id}}
current_path: {{初始化会话.current_path}}
每次循环后更新为:
current_slide_id: {{提交并获取下一步.next_slide_id}}
current_path: {{提交并获取下一步.new_path}}
session_id: {{初始化会话.session_id}} (保持初始值不变)
2. API地址配置
所有代码节点中的API地址需要根据实际情况修改:
python
# 开发环境(FastAPI在本地运行)
api_url = "http://localhost:8000/..."
# 生产环境(FastAPI部署在服务器)
api_url = "http://your-server-ip:8000/..."
# 使用域名(推荐)
api_url = "https://your-domain.com/..."
3. 循环终止条件
正确的配置:
yaml
变量: 循环 {{x}} current_slide_id
运算符: 为空
值: (留空,不填任何内容)
说明 : 当FastAPI返回 "next_slide_id": null 时,这个条件会触发,循环终止。
4. 错误处理
每个代码节点都包含了 try-except 错误处理,返回的数据中都有 error 字段。
可选:添加条件分支来处理错误情况,例如在节点4后面添加:
yaml
IF {{获取Slide数据.error}} 不为空:
→ 显示错误信息,终止流程
ELSE:
→ 继续正常流程
5. 测试建议
逐步测试:
- 先测试节点2(初始化会话),确保能成功调用FastAPI
- 手动设置循环变量,测试节点4(获取Slide)
- 测试节点6-9(完整的答题流程)
- 最后测试完整的循环
测试数据:
yaml
course_id: "1"
user_id: "test_user"
6. 常见问题
问题1: 循环变量无法更新
- 检查循环节点的"变量更新规则"配置
- 确保引用的节点名称和变量名完全正确
问题2: API调用失败
- 检查FastAPI服务是否正在运行(
http://localhost:8000/docs) - 检查API地址是否正确
- 查看FastAPI终端的日志
问题3: 循环不终止
- 检查FastAPI的
submit_quiz函数是否正确返回None - 检查循环终止条件的配置
完整的节点连接顺序
开始
↓
初始化会话
↓
循环开始 ←─────────────┐
↓ │
获取Slide数据 │
↓ │
展示Slide │
↓ │
准备测试题 │
↓ │
测试交互 │
↓ │
计算分数 │
↓ │
提交并获取下一步 │
↓ │
展示反馈 │
↓ │
判断课程结束 │
↓ │
IF 未结束 ──────────────┘
│
│ ELSE (已结束)
↓
生成报告
↓
展示报告
↓
结束
这就是完整的14个节点配置。按照这个配置搭建,配合FastAPI后端,就能实现完整的自适应学习系统。