12 KiB
Dify 工作流配置文档:laser_card_variants_v1
文件路径:
docs/specs/2026-06-02-dify-laser-card-workflow.md本文档是 Dify 平台上
laser_card_variants_v1工作流的完整配置指南。
工作流概述
- 类型: Workflow(API 触发)
- 名称:
laser_card_variants_v1 - 用途: 接收用户图片 → 抠图 → MiniMax AI 生成背景+装饰 → laser-compositor 合成 → 返回 5 张镭射卡 URL
- 预计耗时: 30-60 秒
开始节点 — 输入变量
Dify 平台限制: Dify 的开始节点不支持
array类型。preset_codes和render_configs使用 文本 (string) 类型,传入 JSON 字符串,在代码节点中json.loads解析。
| 变量 | Dify 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
source_image_url |
文本 (string) | 是 | — | 用户原图 OSS signed URL |
use_cutout |
复选框 (boolean) | 是 | — | 是否人像抠图 |
preset_codes |
文本 (string) | 否 | ["dream","classic","holoFull","ice","sunset"] |
JSON 数组字符串,例如 "[\"dream\",\"classic\"]" |
render_configs |
文本 (string) | 是 | — | JSON 数组字符串,包含每个 variant 的 grating_config、bg_prompt、overlay_prompt |
Dify 平台操作步骤:
- 添加
preset_codes:类型 文本,变量名preset_codes,不勾选必填,默认值填["dream","classic","holoFull","ice","sunset"] - 添加
render_configs:类型 文本,变量名render_configs,勾选必填,默认值留空 - 确认
source_image_url(文本,必填)和use_cutout(复选框,必填)正确 - 保存开始节点
节点链路
flowchart TB
START[开始节点] --> NORM[代码节点1: 参数展开]
NORM --> BRANCH{条件: use_cutout?}
BRANCH -->|true| SEG[HTTP节点: Gateway /segment 抠图]
BRANCH -->|false| LOOP
SEG -->|成功| LOOP[循环节点: preset_codes x5]
SEG -->|失败| WARN1[添加 warning]
WARN1 --> LOOP
subgraph variant_loop[循环内: 每个 variant]
BG[HTTP节点: MiniMax 生成背景图]
DECO[HTTP节点: MiniMax 生成装饰图]
COMP[HTTP节点: laser-compositor /compose]
BG --> DECO --> COMP
end
LOOP --> variant_loop
COMP --> AGG[代码节点2: 聚合输出]
AGG --> END_NODE[结束节点]
各节点详细配置
节点 1:代码节点 — 参数展开
输入: preset_codes, render_configs, use_cutout
代码(Python):
import json
def main(preset_codes: str, render_configs: str, use_cutout: bool) -> dict:
"""
解析 JSON 字符串并展开 variants 列表
因为 Dify 开始节点不支持 array 类型,preset_codes 和 render_configs 都以 string 传入
"""
# 解析 preset_codes(字符串 → 列表)
if preset_codes and preset_codes.strip():
try:
preset_list = json.loads(preset_codes)
except:
preset_list = []
else:
preset_list = []
if not preset_list:
preset_list = ["dream", "classic", "holoFull", "ice", "sunset"]
# 解析 render_configs(字符串 → 列表)
if render_configs and render_configs.strip():
try:
configs = json.loads(render_configs)
except:
configs = []
else:
configs = []
# 构建 preset_id → config 映射
config_map = {rc.get("preset_id"): rc for rc in configs if "preset_id" in rc}
variants = []
for pc in preset_list:
if pc in config_map:
cfg = config_map[pc]
variants.append({
"preset_id": pc,
"grating_config": cfg.get("grating_config", {}),
"bg_prompt": cfg.get("bg_prompt", ""),
"overlay_prompt": cfg.get("overlay_prompt", ""),
})
return {
"variants": json.dumps(variants),
"variant_count": len(variants),
"use_cutout": use_cutout,
}
节点 2:条件分支 — 抠图判断
条件: {{#代码节点1.use_cutout#}} == true
- true 分支 → HTTP 节点调用抠图 API
- false 分支 → 直接进入循环
节点 3:HTTP 节点 — 抠图(Segment)
方法: POST
URL: http://{GATEWAY_HOST}/api/v1/segment
Headers:
Content-Type: multipart/form-data
Body (form-data):
image: {{文件上传引用}}
scene: portrait
Authorization: Bearer {{#env.GATEWAY_JWT_TOKEN#}}
注意: Dify HTTP 节点的 multipart 能力有限。如果 Dify 不支持 multipart form-data,可以用代码节点构造 HTTP 请求,或改为让 Gateway 提供 JSON 接口(传入 image_url)。
替代方案(推荐):Gateway 扩展 /api/v1/segment 支持 JSON body
POST http://{GATEWAY_HOST}/api/v1/segment/json
Body (JSON):
{
"image_url": "{{#开始节点.source_image_url#}}",
"scene": "portrait"
}
返回:
{
"success": true,
"cutout_oss_key": "laser-card/.../xxx_cutout.png",
"cutout_url_signed": "https://oss.example.com/..."
}
如果失败:设置 cutout_url = "",记 warning。
节点 4:循环节点 — variant 遍历
循环对象: {{#代码节点1.variants#}}(JSON array)
循环变量名: variant_item
循环内包含 3 个子节点(串行执行):
子节点 4a:MiniMax 生成背景图
方法: POST
URL: https://api.minimaxi.com/v1/image_generation
Headers:
Authorization: Bearer {{#env.MINIMAX_API_KEY#}}
Content-Type: application/json
Body (JSON):
{
"model": "image-01",
"prompt": "{{#variant_item.bg_prompt#}}",
"aspect_ratio": "3:4",
"n": 1,
"prompt_optimizer": true,
"response_format": "url"
}
超时: 30 秒
输出提取:
bg_url={{#输出.body.data[0].url#}}
如果失败,设置 bg_url = "",记 warning。
子节点 4b:MiniMax 生成装饰图
方法: POST
URL: https://api.minimaxi.com/v1/image_generation
Headers:
Authorization: Bearer {{#env.MINIMAX_API_KEY#}}
Content-Type: application/json
Body (JSON):
{
"model": "image-01",
"prompt": "{{#variant_item.overlay_prompt#}}",
"aspect_ratio": "3:4",
"n": 1,
"prompt_optimizer": true,
"response_format": "url"
}
超时: 30 秒
输出提取:
overlay_url={{#输出.body.data[0].url#}}
子节点 4c:laser-compositor 合成
方法: POST
URL: http://{LASER_COMPOSITOR_HOST}/compose
Headers:
Content-Type: application/json
Body (JSON):
{
"background_url": "{{#子节点4a.bg_url#}}",
"cutout_url": "{{#代码节点3.cutout_url_signed#}}",
"overlay_url": "{{#子节点4b.overlay_url#}}",
"grating_config": {{#variant_item.grating_config#}},
"export_width": 450,
"export_height": 600,
"variant_index": {{#循环索引#}},
"output_oss_key": "laser-card/{date}/{uuid}_variant_{{循环索引}}"
}
超时: 30 秒
返回:
{
"status": "succeeded",
"variant_index": 0,
"width": 450,
"height": 600,
"oss_key": "laser-card/.../variant_0.png",
"signed_url": "https://oss.example.com/..."
}
节点 5:代码节点 — 聚合输出
目的: 将循环中 5 个 variant 的结果聚合为统一 JSON
import json
def main(variant_results: list, cutout_url: str, warnings: list) -> dict:
"""
聚合循环结果
variant_results: 从循环节点收集的结果数组
"""
variants = []
output_warnings = warnings.copy() if warnings else []
for i, vr in enumerate(variant_results):
if vr and vr.get("status") == "succeeded":
variants.append({
"preset_id": vr.get("preset_id", f"variant_{i}"),
"oss_key": vr.get("oss_key", ""),
"signed_url": vr.get("signed_url", ""),
"width": vr.get("width", 450),
"height": vr.get("height", 600),
})
else:
output_warnings.append(f"variant_{i} failed")
return {
"status": "succeeded" if len(variants) > 0 else "failed",
"variants": variants,
"cutout_oss_key": cutout_url or "",
"warnings": output_warnings,
}
结束节点 — 输出格式
{
"status": "succeeded",
"variants": [
{
"preset_id": "dream",
"oss_key": "laser-card/xxxx/variant_0.png",
"signed_url": "https://oss.example.com/...",
"width": 450,
"height": 600
}
],
"cutout_oss_key": "laser-card/xxxx/cutout.png",
"warnings": []
}
环境变量(在 Dify 工作流中配置)
| 变量 | 说明 |
|---|---|
GATEWAY_HOST |
Gateway 地址,如 http://192.168.1.100:8080 |
MINIMAX_API_KEY |
MiniMax API 密钥 |
LASER_COMPOSITOR_HOST |
laser-compositor 地址,如 http://127.0.0.1:7000 |
鉴权说明
Dify 工作流调用 Gateway /api/v1/segment(需要 JWT)时,需要在 Dify 的 HTTP 节点中传入 Bearer Token。有两种方式:
- 在 Dify 环境变量中配置一个长期有效的 JWT token(
GATEWAY_JWT_TOKEN)— 简单但需要定期维护 - Gateway 扩展
/api/v1/segment/json— 不强制 JWT 鉴权,仅限内网调用(Dify→Gateway 走内网)
推荐方案 2,减少 Token 维护成本。
装饰图 prompt 模板
每个 preset 的 bg_prompt 和 overlay_prompt 由前端 laserPresets.js 的 buildRenderConfigs() 动态生成并传入。Dify 不需要维护 prompt 数据。
5 个 preset 的参考 prompt(维护在前端 laserPresets.js):
| Preset | bg_prompt | overlay_prompt |
|---|---|---|
| dream | 梦幻柔光渐变背景,淡紫色调和粉色,柔和泡泡和星光光斑,虚化景深效果,3:4竖版比例 | 精致银色星点散布,细边框装饰线,透明背景,SVG风格手绘装饰元素 |
| classic | 经典复古胶片风格背景,暖色调,浅香槟色渐变,艺术纹理和柔和光影,3:4竖版比例 | 经典复古边框花纹装饰,暖金色调和银色细线,透明背景,精致几何图案 |
| holoFull | 全息科技感背景,银色金属质感,未来主义光影和几何线条,珍珠光泽渐变,3:4竖版比例 | 全息科技感装饰边框,霓虹色细线光晕,透明背景,电路板几何图案和星形闪光 |
| ice | 冰雪冷色调背景,浅蓝灰渐变,冰晶纹理和雪花光斑,清爽透明感,3:4竖版比例 | 冰晶装饰边框,浅蓝色和银色星点,透明背景,雪花图案和细线装饰 |
| sunset | 日落暖色调背景,玫瑰金渐变,暖橙色和粉色光影,温馨氛围,3:4竖版比例 | 日落暖色调装饰边框,玫瑰金和琥珀色星点,透明背景,柔和花卉和光线元素 |
测试方法
- 在 Dify 平台创建 Workflow,按本文档配置各节点(特别注意:
preset_codes和render_configs选文本类型) - 使用 Dify 的"运行"功能测试,传入示例 input:
关键:
preset_codes和render_configs的值必须是 JSON 字符串(带转义引号),不是 JSON 对象。
{
"source_image_url": "https://oss.example.com/test/user_photo.jpg",
"use_cutout": true,
"preset_codes": "[\"dream\"]",
"render_configs": "[{\"preset_id\":\"dream\",\"grating_config\":{\"sheen_band_angle\":135,\"sheen_intensity\":0.40,\"sheen_speed\":0.35,\"foil_coverage\":0.75,\"backdrop_tone\":\"#A8ACB2\"},\"bg_prompt\":\"梦幻柔光渐变背景,淡紫色调\",\"overlay_prompt\":\"精致银色星点装饰,透明背景\"}]"
}
外部 API 调用示例(通过 Gateway 触发):
{
"source_image_url": "https://oss.example.com/photo.jpg",
"use_cutout": true,
"preset_codes": "[\"dream\",\"classic\"]",
"render_configs": "[{\"preset_id\":\"dream\",\"grating_config\":{\"sheen_band_angle\":135,\"sheen_intensity\":0.4},\"bg_prompt\":\"...\",\"overlay_prompt\":\"...\"}]"
}
- 单个 preset 测试成功后,改为 5 个 preset 全量测试