dify案例分享-开源模型加持,打字就能轻松 P 图的工作流来了

235 阅读13分钟

1.前言

HiDream-E1-Full 是全球首款 对话式交互编辑模型,专注于解决传统图像生成工具中难以精准控制输出细节的痛点。其核心功能包括:

  • 自然语言指令编辑:用户可通过自然语言对话(如“调整机械臂的金属光泽”“增加星空粒子效果”)对 HiDream-I1 生成的图像进行实时修改,无需手动调整参数,极大降低创作门槛。
  • 多维度编辑能力:支持对图像风格、局部细节(如光影、材质)、构图元素(如添加或移除对象)等进行动态调整,形成“生成-反馈-优化”的闭环流程。
  • 与 HiDream-I1 的协同性:作为 HiDream-I1 的配套工具,E1-Full 能够直接解析生成模型的中间结果,提升编辑效率与一致性。

一张图给大家展示一下效果

image-20250510163346565

前期也给大家介绍过google Gemini 2.0 Flash Exp 文字一键生图改图工作流,本次给大家介绍的是一块开源的一键生图模型。

目前这个开源的模型已经在gitee平台上上架了,我也算是第一时间体验了这块模型。体验地址

ai.gitee.com/serverless-…

image-20250510164039100

目前gitee平台每天送100次模型体验,感兴趣小伙伴可以去平台注册和使用。下面我们重点看下dify平台整合这个模型的工作流是什么样子的

image-20250510164227195

上面是整体工作流的截图。我们给大家看一下效果。

image-20250510164614722

image-20250510164837209

话不多说下面介绍一下这个工作流是如何制作的。

2.工作流的制作

这个工作流用到一个叫做图片转base64的第三方组件,我们需要在插件市场上把它安装。

image-20250510165940066

关于这个组件的安装,这里就不做详细展开。这个组件安装后有地方需要设置否则会报错,关于这个组件的设置和注意事项。大家可以到文章末尾会介绍。

安装完成后我们就可以看到图片转base64这个插件了。

image-20250510170116278

接下来我们把工作流制作需要的重点几个给大家介绍一下。

这个工作流由以下几个部分组成:1 开始节点、2条件分支、3图片转base64 、4代码执行、5 自定义HiDream E1图像生成_forbase64工具、6 代码执行 2、7 直接回复、8 变量赋值、9自定义HiDream E1图像生成_forimageurl 等组成。

开始

这个开始节点有2个参数,第一个参数是提示词prompt,第二个参数用户上传的图片

image-20250510170551984

条件分支

这个地方主要是控制第一次和后面几次做区分的。 主要的判断依据就是picture_url 是否为空。picture_url 是一个会话变量。

条件分支配置如下截图

image-20250510170924439

判断不为空就走上面的,为空就走下面,第一次就会走下面的流程。

会话变量

下面流程中用到了条件分支,这个条件分支的判断是生成图片的URL连接是否存在作为判断依据,所以我们这里增加一个会话变量。

这个会话变量只有在chatflow才会有的。顾名思义chat聊天才产生多伦对话。

image-20250320171753817

我们需要添加一个picture_url 会话变量,主要是存储后面文生图产生的图片URL链接使用。

image-20250320171858328

图片转Base64

这个就用到我们前面安装的第三方组件。

image-20250510171332265

这个组件有好几个功能,我们这里用到了图片转base64,它的参数这里就一个文件。

image-20250510171514334

代码出来

上面的组件返回的值 不是一个标准base64值,所以我们这里用代码处理一下

import json
def main(json_input_string: str) -> dict:
    result_value = None
    error_message = None

    try:
        text_field_value=json_input_string
        if not text_field_value:
            error_message = "错误:JSON 数据中未找到 'text' 字段。"
        elif not isinstance(text_field_value, str):
            error_message = "错误:'text' 字段的值不是字符串类型。"
        elif "base64," not in text_field_value:
            error_message = "错误:'text' 字段的值不包含 'base64,' 标记,格式不符合预期。"
        else:
            parts = text_field_value.split("base64,", 1)
            # 确保 "base64," 后面确实有内容
            if len(parts) > 1 and parts[1]: 
                result_value = parts[1]
            else:
                error_message = "错误:'text' 字段在 'base64,' 标记后没有数据。"
    
    except json.JSONDecodeError:
        error_message = "错误:无法解析输入的 JSON 字符串。"
    except Exception as e:
        error_message = f"处理过程中发生未知错误: {str(e)}"

    # 确保始终返回包含 "result" 和 "error" 键的字典
    # Dify 平台可能会专门查找 "result" 键。
    # 如果发生错误,"result" 的值为 None,错误信息记录在 "error" 键中。
    return {"result": result_value, "error": error_message}

image-20250510171814719

自定义HiDream E1图像生成

这里我们需要先编写一个服务端代码,代码的部分主要是参考gitee api 接口然后我们使用fastapi实现接口的转发,程序部署在服务器上面。由于这个工作流涉及知识点较多,这里就不把详细代码展开细说。我们贴一下核心代码(关于详细代码我会上传到我开源项目里面,文章末尾会有项目合集,大家可以去上面找源码)

部分代码片段

@router.post("/difyforgitee/generate-HiDream-E1/")
async def generate_hidream_e1(request: HiDreamE1Request): # 修改函数签名以接收新的 Pydantic 模型
    # --- 代理配置示例 ---
    # 如果您的服务器需要通过代理访问外部网络,请取消注释以下行并配置您的代理服务器地址
    # proxies = {
    # "http://": "http://your_proxy_username:your_proxy_password@your_proxy_address:port",
    # "https://": "https://your_proxy_username:your_proxy_password@your_proxy_address:port",
    # }
    # http_client_with_proxy = httpx.Client(proxies=proxies)
    # --- ---

    client = OpenAI(
        base_url="https://ai.gitee.com/v1",
        api_key=api_key, # 使用从配置文件读取的api_key
        timeout=300.0,  # <-- 修改: 设置请求超时时间为300秒
        max_retries=0  # <-- 新增: 禁用重试
        # http_client=http_client_with_proxy # <-- 新增: 如果使用代理,取消注释此行
    )
    logger.info(f"Received request with prompt: {request.prompt}, imageurl: {request.imageurl}, has_image_base64: {request.image_base64 is not None}")

    base64_encoded_content_to_process: Optional[str] = None

    if request.imageurl:
        logger.info(f"Attempting to process image from URL: {request.imageurl}")
        try:
            async with httpx.AsyncClient(timeout=30.0) as http_client: # 使用 httpx 进行异步 GET 请求
                response = await http_client.get(request.imageurl)
                response.raise_for_status() # 如果状态码是 4xx 或 5xx,则引发 HTTPStatusError
                image_bytes_from_url = response.content
                base64_encoded_content_to_process = base64.b64encode(image_bytes_from_url).decode('utf-8')
                logger.info(f"Successfully downloaded and base64 encoded image from URL: {request.imageurl}")
        except httpx.HTTPStatusError as e:
            logger.error(f"HTTP error downloading image from {request.imageurl}: {e.response.status_code} - {e.response.text}")
            raise HTTPException(status_code=e.response.status_code, detail=f"下载图片URL '{request.imageurl}' 时出错: {e.response.status_code} - {e.response.text}")
        except httpx.RequestError as e: # 处理网络错误、DNS 失败等
            logger.error(f"Request error downloading image from {request.imageurl}: {str(e)}")
            raise HTTPException(status_code=503, detail=f"连接图片URL '{request.imageurl}' 时出错: {str(e)}") # 503 Service Unavailable
        except Exception as e: # 捕获其他意外错误
            logger.error(f"Unexpected error processing image URL {request.imageurl}: {str(e)}")
            raise HTTPException(status_code=500, detail=f"处理图片URL '{request.imageurl}' 时发生意外错误: {str(e)}")
    elif request.image_base64:
        logger.info("Using provided image_base64 from request body.")
        base64_encoded_content_to_process = request.image_base64
    
    if not base64_encoded_content_to_process:
        logger.warning("No image data provided in request (neither imageurl nor image_base64).")
        raise HTTPException(
            status_code=400, 
            detail="未提供图像数据。请提供 'imageurl' 或 'image_base64'。"
        )

    logger.info(f"Proceeding with image processing. Base64 content length (approx): {len(base64_encoded_content_to_process) if base64_encoded_content_to_process else 0}")

    try:
        # 从请求体中获取 prompt (这部分逻辑不变)
        prompt_text = request.prompt
        
        # 将Base64字符串解码为字节
        # 使用 base64_encoded_content_to_process 替代原来的 request.image_base64
        try:
            image_bytes = base64.b64decode(base64_encoded_content_to_process.encode('utf-8'))
        except Exception as e:
            logger.error(f"解码提供的Base64图片数据时出错: {str(e)}") # 增加此处的日志记录
            raise HTTPException(status_code=400, detail=f"无法解码提供的Base64图片数据: {str(e)}")
        logger.info(f"Base64图片数据已成功解码。 Image byte length: {len(image_bytes)}")
        response = client.images.edit(
            model="HiDream-E1-Full",
            image=image_bytes, # 直接传递解码后的图片字节
            prompt=prompt_text, # 使用从请求体中获取的 prompt
            response_format="b64_json",
            extra_body={
                "steps": 28,
                "instruction_following_strength": 5,
                "image_preservation_strength": 3,
                "refinement_strength": 0.3,
                "seed": -1,
            }
        )
        logger.info(f"Received response from Gitee API: {response}")
        if not response.data or not response.data[0].b64_json:
            raise HTTPException(status_code=500, detail="从Gitee API响应中获取base64数据失败。格式不符合预期。")

        result_b64 = response.data[0].b64_json

        filename, output_path_local = base64_to_image(result_b64, output_path)
        logger.info(f"Image saved to {output_path_local}")
        etag = upload_cos(region, secret_id, secret_key, bucket, filename, output_path)
        logger.info(f"Image uploaded to COS with etag: {etag}")
        if not etag:
            raise HTTPException(status_code=500, detail="上传图片到COS失败。")

        return {
            "filename": filename,
            "output_path": output_path_local, # 保持字段名一致性或明确区分
            "etag": etag
        }
    except APIConnectionError as e: # <-- 新增: 更具体地捕获连接错误
        logger.error(f"Gitee API Connection Error: {e}")
        error_message = (
            f"无法连接到 Gitee API (请求URL: {e.request.url if hasattr(e, 'request') and e.request else 'N/A'}): {str(e)}. "
            "这通常是由于服务器的网络配置问题(例如防火墙、DNS解析、代理服务器设置)或目标服务暂时不可达。 "
            "请检查服务器的网络连通性,并确认是否需要为应用程序配置代理服务器。"
        )
        raise HTTPException(status_code=503, detail=f"Gitee API 连接错误: {error_message}") # 503 Service Unavailable
    except APIError as e:
        logger.error(f"Gitee API Error: {e}")
        error_detail = str(e)
        if hasattr(e, 'message') and e.message:
            error_detail = e.message
        elif hasattr(e, 'response') and e.response is not None:
            try:
                # 尝试解析JSON响应体中的错误信息
                error_content = e.response.json()
                error_detail = error_content.get("error", {}).get("message", str(e.response.content))
            except json.JSONDecodeError: # 如果响应不是有效的JSON
                error_detail = e.response.text # 使用原始文本响应
            except Exception: # 其他解析错误
                 error_detail = str(e.response.content) # Fallback to raw content as string
        # Gitee API 可能会在 status_code 为 4xx/5xx 时返回非 JSON 错误,例如纯文本或 HTML
        # 因此,直接使用 e.response.text 可能更稳妥,如果 JSON 解析失败
        status_code_to_return = e.status_code if hasattr(e, 'status_code') and e.status_code else 500
        raise HTTPException(status_code=status_code_to_return, detail=f"Gitee API 错误: {error_detail}")
    except HTTPException as e:
        logger.error(f"HTTPException: {e}")
        raise e
    except Exception as e:
        logger.error(f"Unexpected error: {e}")
        # 对于其他未预料到的错误,记录日志并返回通用错误信息
        # import traceback; traceback.print_exc(); # 可选:在服务器端打印详细堆栈信息
        raise HTTPException(status_code=500, detail=f"发生意外错误: {type(e).__name__} - {str(e)}")

​ 这个服务端代码发布后我们需要写2个curl 命令来实现 测试,后面把这个curl命令转换成openapi 3.1.0 版本的json schema,我们一般测试可以先测试代码,也可以使用postman来实现测试

image-20250510172408526

下面讲一下这个curl 命令如何转成openapi 3.1.0 版本的json schema。关于这块知识大家可以看我早起的文章

零基础代码,也能在dify中创建自定义工具?手把手教你!

有的小伙伴看我上面的文章可能觉的还是有点麻烦,我这边也做了一个小工具帮助大家来生成json schema。

curl命令

这里我们有2段curl 命令代码如下

curl --location --request POST 'http://14.103.204.132:8080/difyforgitee/generate-HiDream-E1/' \
--header 'Content-Type: application/json' \
--data-raw '{"prompt": " 请将图片转成吉卜力风格", "image_base64": "/9j/4AAQSkZJRgAB...}'
curl --location --request POST 'http://14.103.204.132:8080/difyforgitee/generate-HiDream-E1/' \
--header 'Content-Type: application/json' \
--data-raw '{"prompt": " 请将图片转成吉卜力风格", "imageurl": "http://example.com/your_image.jpg"}'

使用工作流创建json schema

我们可以使用这个项目difyhs.duckcloud.fun/chat/71yzWK… 命令复制到工作流里面

image-20250510173259045

工作流就会返回我们要的openapi 3.1.0 版本的json schema

image-20250510173344430

上面json schema 代码我们就可以到dify 自定义工具中创建。

制作自定义工具

把生成的代码在自定义工具添加。

image-20250510173527579

点击创建自定义工具,把上面的JSON 代码复制到shema里面

image-20250320215029527

自定义接口可以增加一个鉴权,增加安全性。

image-20250320215130545

image-20250320215222776

以上设置完成后我们,我们点击保存完成自定工具创建

自定义工具在dify使用

我们回到dify工作流界面中。添加节点-工具-HiDream E1 图像生成_forbase64

image-20250510174044498

这个自定义工具有2个参数,第一个是 prompt 第二是前面base64工具转化后的base64值

image-20250510174147280

代码运行

这个代码运行和我之前文生的代码类似,主要功能就是通过服务端代码生成的图片上传到我的腾讯COS图床上,返回我要的图片的URL地址我这边用代码解析返回的URL .

代码如下

def main(arg1: str) -> str:
    import json
    data = json.loads(arg1)
    filename=data['filename']
    url=data['etag']
    markdown_result = f"![{filename}]({url})"
    return {"result": markdown_result,"url": url}

输入参数 arg1

返回参数 result,url

image-20250510174447088

直接回复

这个直接回复就比较简单了,把上面代码执行的信息返回

image-20250510174537976

变量赋值

这个地方主要的目的是为了通过全局变量保存会话产生的图片的url 方便后面第二轮,第三轮对话时候用到上一次对话的图片地址。

image-20250510174700878

这个地方只是赋值就可以了,这个地方需要结合前面的会话变量设置一起用。简单来说就是前面配置好,这个地方来使用。

好了,上面第一轮对话的工作流就已经初步制作完成了。接下来我们将第二个分支

image-20250510174923158

这个分支和上面的东西几乎是一样的,差异就是自定义工具。

自定义HiDream E1图像生成(图片url)

我们在上面条件分支if语句中拉一个自定义HiDream E1图像生成_forimageurl,这个自定义工具和上面步骤6一行的。

它有2个参数,第一个参数是 prompt,这里我们接受系统提示词。

image-20250510175203444

另外一个imageurl 这个地方我们就从上面变量赋值获取picture_url

image-20250510175257599

后面的流程和前面的都一样,这里我们就不做详细展开了。

通过以上我们就完成了工作流的制作。

3.工作流的分享和使用

接下来我们就可以把工作流分享出去了。

体验地址difyhs.duckcloud.fun/chat/GIcuzi… 备用地址(http://14.103.204.132/chat/GIcuziInjH61FLUx)

相关资料和文档可以看我开源的项目 github.com/wwwzhouhui/…

4.问题记录

前面我们提到工作流在使用到图片转base64的第三方组件 安装访问遇到的问题

image-20250510175719615

如果默认安装小伙伴大概率会遇到这个问题。

出现上述错误如何解决?

需要修改2个地方。

1.env 文件中查找FILES_URL

​ 默认的FILES_URL是空的,我们需要修改使用 http://:5001 或 http://api:5001,在此情况下,确保外部可以访问端口 5001

image-20250510003900660

2.docker-compose.yaml 对应的FILES_URL修改

image-20250510004029749

此外dify-api容器镜像端口开放出来(默认情况是不开放的),增加如下代码

  ports:

   - '5001:5001'

image-20250510004232125

我们也可以从docker容器看到端口开放情况(默认是不开启的)

image-20250510004416081

另外我们在运行调用自定义接口 可能会返回超时,主要原因是请求接口的重试机制和超时导致的额,这里大家多试一试。

image-20250510175959973

5.总结

今天主要带大家了解并实现了基于 HiDream - E1 - Full 模型在 Dify 平台的图像编辑工作流方案。详细介绍了在 Dify 平台整合此模型的工作流制作过程。该工作流由开始节点、条件分支、图片转 base64、代码执行、自定义 HiDream E1 图像生成工具等多个部分组成。在制作过程中,涉及第三方组件的安装与设置、服务端代码的编写、curl 命令转成 OpenAPI 3.1.0 版本的 json schema、自定义工具的创建与使用等多个关键步骤,还针对流程中使用的会话变量、代码处理等细节进行了说明。总体来说,这个方案相对较为全面地整合了图像编辑和生成的功能,为用户提供了一种便捷、高效的图像创作方式。感兴趣的小伙伴可以按照本文步骤去尝试。今天的分享就到这里结束了,我们下一篇文章见。