Dify + Gemini 2.0 Flash Exp 实战:5分钟搭建AI作图工作流(附完整代码)
从零到一:基于Dify与Gemini 2.0 Flash Exp构建企业级AI图像生成平台
你是否曾想过,将前沿的多模态AI图像生成能力,像搭积木一样快速集成到自己的应用里?面对Gemini 2.0 Flash Exp这类强大的模型,很多开发者手握API密钥却不知如何将其转化为稳定、可用的服务。今天,我将分享一套经过实战验证的解决方案,利用Dify的低代码平台,在半小时内搭建起一个完整的AI作图工作流,不仅支持文字生图,还能实现智能改图,让创意落地变得前所未有的简单。
这个方案特别适合中小型开发团队、独立开发者以及希望快速验证AI图像应用场景的产品经理。我们不需要从零编写复杂的后端服务,而是通过Dify的可视化工作流设计,结合自定义工具开发,构建一个兼具灵活性与稳定性的图像生成平台。下面,我将从环境准备开始,一步步带你完成整个系统的搭建、调试与部署。
1. 核心架构设计与环境准备
在动手敲代码之前,理清整个系统的技术栈和协作方式是成功的关键。我们的目标不是复制一个简单的Demo,而是构建一个可扩展、易维护、具备生产环境潜力的AI图像服务中间层。
1.1 技术选型与角色分工
整个系统涉及三个核心部分:Dify工作流引擎、自定义API服务以及Gemini模型。它们各自承担着不同的职责:
- Dify:作为大脑和调度中心。它负责接收用户请求,进行意图识别(比如判断用户是想生图还是聊天),调用不同的模型或工具,并管理多轮对话的上下文(会话变量)。其低代码特性让我们能通过拖拽节点快速构建复杂的业务逻辑。
- 自定义API服务:作为双手和桥梁。这是一个我们用Python(FastAPI)编写的独立服务,它封装了对Gemini 2.0 Flash Exp API的调用,处理图像生成、编辑、本地保存、上传至云存储等“脏活累活”,并向Dify提供干净、标准的接口。
- Gemini 2.0 Flash Exp:作为创意引擎。它接收经过我们优化和处理的提示词,执行最核心的图像生成与编辑任务。
为什么选择这样的架构?直接让Dify调用Gemini API不行吗?理论上可以,但在实际生产中会遇到几个棘手问题:网络稳定性、结果后处理(如图片存储)、安全性过滤以及复杂的提示词工程。我们的自定义服务正是为了解决这些问题而生。
1.2 开发环境与账号配置
工欲善其事,必先利其器。请确保你已准备好以下资源:
1. 基础开发环境:
- Python 3.9+:这是我们的自定义服务语言。建议使用虚拟环境(如
venv或conda)管理依赖。 - 代码编辑器:VS Code、PyCharm等任选。
- Git:用于版本控制和获取示例代码。
2. 关键平台账号与密钥:
- Dify:访问Dify官网,注册账号并创建一个新的应用。我们将在应用内使用“工作流”模式。记下你的Dify API密钥(在设置中查看),后续自定义工具鉴权会用到。
- Gemini API:前往Google AI Studio,在API设置中创建API密钥。Gemini 2.0 Flash Exp模型通常有免费额度,足够用于开发和测试。
- 云存储服务(可选但推荐):生成的图片需要有个地方存放并对外提供访问链接。你可以选择腾讯云COS、阿里云OSS、AWS S3等任何支持API上传的对象存储服务。本文将使用腾讯云COS为例,你需要获取其
SecretId、SecretKey、Bucket名称和所属地域(Region)。
为了方便管理这些敏感的配置信息,我们不会将它们硬编码在代码中。接下来,我们将创建项目并初始化配置文件。
# 创建项目目录并初始化
mkdir dify-gemini-image-platform && cd dify-gemini-image-platform
python -m venv venv # 创建虚拟环境
# 激活虚拟环境 (Windows)
venv\Scripts\activate
# 激活虚拟环境 (MacOS/Linux)
source venv/bin/activate
# 创建必要的目录和文件
mkdir -p server config
touch server/main.py server/utils.py config/config.ini requirements.txt
现在,在config/config.ini文件中,填入你的密钥信息:
[google]
api_key = YOUR_GEMINI_API_KEY_HERE
model = gemini-2.0-flash-exp-image-generation
output_path = ./generated_images
[cos]
region = ap-shanghai # 替换为你的COS地域
secret_id = YOUR_TENCENT_COS_SECRET_ID
secret_key = YOUR_TENCENT_COS_SECRET_KEY
bucket = your-bucket-name-1250000000 # 替换为你的存储桶名称
[dify]
api_key = YOUR_DIFY_APP_API_KEY # 用于服务端向Dify回调等高级功能,基础工作流可暂不配置
注意:请务必将
config.ini文件添加到.gitignore中,避免将密钥提交到公开仓库,造成安全风险。
2. 构建图像生成与处理API服务
有了清晰的设计和配置,我们现在开始打造系统的核心——自定义API服务。这个服务将扮演一个“万能适配器”的角色。
2.1 使用FastAPI搭建服务骨架
FastAPI以其高性能和自动生成API文档的特性,成为构建此类中间层服务的绝佳选择。我们先安装依赖并搭建基础框架。
# 安装核心依赖
pip install fastapi uvicorn google-generativeai pillow requests qcloud-cos-python python-multipart
pip freeze > requirements.txt # 保存依赖列表
接下来,我们编写server/main.py的主干部分。为了代码清晰,我们将COS上传和图片下载功能抽离到utils.py中。
# server/main.py
from fastapi import FastAPI, HTTPException, UploadFile, File
from pydantic import BaseModel
from typing import Optional
import google.generativeai as genai
from .utils import download_image_from_url, upload_image_to_cos, generate_unique_filename
import configparser
import os
import logging
from PIL import Image
import io
import base64
# 配置日志和读取配置
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
config = configparser.ConfigParser()
config.read('config/config.ini', encoding='utf-8')
# 初始化Gemini客户端
GEMINI_API_KEY = config.get('google', 'api_key')
genai.configure(api_key=GEMINI_API_KEY)
MODEL_NAME = config.get('google', 'model', fallback='gemini-2.0-flash-exp-image-generation')
app = FastAPI(title="Gemini Image Service", description="为Dify工作流提供图像生成与编辑的API")
# 定义请求体模型
class TextToImageRequest(BaseModel):
prompt: str
negative_prompt: Optional[str] = None # 可选的反向提示词,用于排除不想要的内容
size: Optional[str] = "1024x1024" # 生成图片尺寸
class ImageEditRequest(BaseModel):
prompt: str
image_url: str # 待修改图片的URL
mask_prompt: Optional[str] = None # 可选,指定修改区域
@app.get("/health")
async def health_check():
"""健康检查端点"""
return {"status": "healthy", "service": "gemini-image-api"}
@app.post("/v1/generate")
async def generate_image(request: TextToImageRequest):
"""核心文生图接口"""
logger.info(f"收到文生图请求,提示词: {request.prompt[:50]}...")
try:
# 调用Gemini模型
model = genai.GenerativeModel(MODEL_NAME)
# 注意:Gemini 2.0 Flash Exp的图像生成调用方式可能与文本模型略有不同
# 此处为示例,实际调用需参考最新官方文档
response = model.generate_content([request.prompt])
# 假设响应中包含图像数据(具体解析方式需根据实际API响应调整)
# 这里是一个通用处理逻辑示例
image_data = None
for part in response.parts:
if hasattr(part, 'inline_data') and part.inline_data.mime_type.startswith('image/'):
image_data = part.inline_data.data
break
if not image_data:
raise HTTPException(status_code=500, detail="模型未返回图像数据")
# 解码并保存图片
image_bytes = base64.b64decode(image_data)
image = Image.open(io.BytesIO(image_bytes))
# 生成唯一文件名并保存本地
filename = generate_unique_filename('png')
local_path = os.path.join(config.get('google', 'output_path'), filename)
os.makedirs(os.path.dirname(local_path), exist_ok=True)
image.save(local_path)
logger.info(f"图片已保存至本地: {local_path}")
# 上传至云存储
cos_url = upload_image_to_cos(local_path, filename)
if not cos_url:
# 如果上传失败,可以返回本地可访问的URL(需配置静态文件服务),或抛出异常
raise HTTPException(status_code=500, detail="图片上传至云存储失败")
return {
"success": True,
"data": {
"url": cos_url,
"local_path": local_path,
"prompt_used": request.prompt
}
}
except Exception as e:
logger.error(f"文生图处理失败: {str(e)}", exc_info=True)
raise HTTPException(status_code=500, detail=f"图像生成过程出错: {str(e)}")
@app.post("/v1/edit")
async def edit_image(request: ImageEditRequest):
"""图生图(编辑)接口"""
logger.info(f"收到图生图请求,提示词: {request.prompt[:50]}...,原图URL: {request.image_url[:80]}...")
try:
# 1. 从URL下载原图
original_image = download_image_from_url(request.image_url)
if original_image is None:
raise HTTPException(status_code=400, detail="无法下载指定的原始图片")
# 2. 准备输入(图片+文本提示词)
model = genai.GenerativeModel(MODEL_NAME)
# 具体调用方式需参考Gemini多模态输入文档
# 示例:将图片和文本组合成内容列表
response = model.generate_content([request.prompt, original_image])
# 3. 处理响应(与文生图类似,解析图像数据)
# ... (解析和保存图像的逻辑与generate_image接口类似)
# 4. 返回新图片URL
return {
"success": True,
"data": {
"new_image_url": "https://your-cos-url/edited_image.png", # 替换为实际URL
"edit_prompt": request.prompt
}
}
except HTTPException:
raise
except Exception as e:
logger.error(f"图生图处理失败: {str(e)}", exc_info=True)
raise HTTPException(status_code=500, detail=f"图像编辑过程出错: {str(e)}")
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8080, reload=True)
2.2 实现工具函数与健壮性处理
上面的主框架调用了一些工具函数,它们在server/utils.py中实现,负责具体的文件操作和云服务交互,这是保证服务稳定性的关键。
# server/utils.py
import os
import datetime
import random
import requests
from PIL import Image, UnidentifiedImageError
from io import BytesIO
import configparser
from qcloud_cos import CosConfig, CosS3Client, CosServiceError
import logging
logger = logging.getLogger(__name__)
# 读取配置
config = configparser.ConfigParser()
config.read('config/config.ini', encoding='utf-8')
def generate_unique_filename(extension='png'):
"""生成基于时间戳和随机数的唯一文件名"""
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
random_suffix = random.randint(1000, 9999)
return f"gen_{timestamp}_{random_suffix}.{extension}"
def download_image_from_url(url: str, timeout=10):
"""从给定的URL下载图片,返回PIL.Image对象"""
try:
response = requests.get(url, timeout=timeout)
response.raise_for_status() # 检查HTTP错误
image = Image.open(BytesIO(response.content))
# 可选:转换为RGB模式,确保兼容性
if image.mode in ('RGBA', 'LA', 'P'):
rgb_image = Image.new('RGB', image.size, (255, 255, 255))
rgb_image.paste(image, mask=image.split()[-1] if image.mode == 'RGBA' else None)
image = rgb_image
elif image.mode != 'RGB':
image = image.convert('RGB')
logger.info(f"成功从URL下载图片: {url}")
return image
except requests.exceptions.RequestException as e:
logger.error(f"下载图片请求失败: {url}, 错误: {e}")
return None
except UnidentifiedImageError as e:
logger.error(f"下载的内容不是有效图片: {url}, 错误: {e}")
return None
except Exception as e:
logger.error(f"下载图片时发生未知错误: {url}, 错误: {e}")
return None
def upload_image_to_cos(local_path: str, filename: str):
"""将本地图片上传至腾讯云COS,返回公网访问URL"""
try:
region = config.get('cos', 'region')
secret_id = config.get('cos', 'secret_id')
secret_key = config.get('cos', 'secret_key')
bucket = config.get('cos', 'bucket')
cos_config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key)
cos_client = CosS3Client(cos_config)
with open(local_path, 'rb') as fp:
response = cos_client.put_object(
Bucket=bucket,
Body=fp,
Key=filename,
StorageClass='STANDARD',
EnableMD5=False
)
# 构建公网访问URL (根据COS配置,可能需开启公有读或使用临时密钥)
# 这里假设存储桶是公有读,或已配置CDN域名
cos_url = f"https://{bucket}.cos.{region}.myqcloud.com/{filename}"
logger.info(f"图片成功上传至COS: {cos_url}")
return cos_url
except CosServiceError as e:
logger.error(f"腾讯云COS服务错误: {e.get_error_code()}, {e.get_error_msg()}")
return None
except FileNotFoundError:
logger.error(f"要上传的本地文件不存在: {local_path}")
return None
except Exception as e:
logger.error(f"上传图片至COS时发生未知错误: {e}")
return None
这个服务架构的优势在于解耦和可测试性。你可以独立运行这个FastAPI服务,并使用Postman或curl工具直接测试/v1/generate和/v1/edit接口,确保其功能正常、返回格式符合预期,然后再将其接入Dify。这比在Dify工作流中直接调试第三方API要高效和可靠得多。
3. 在Dify中设计与编排智能工作流
API服务准备就绪后,我们就可以在Dify这个“可视化编程”平台上,构建业务逻辑了。Dify工作流的核心思想是将复杂的过程拆解为一个个节点,并通过连线定义数据流。
3.1 创建应用与初始化工作流
首先,在Dify控制台创建一个新的“工作流”应用。我们将构建一个能理解用户意图,并智能路由到生图或改图流程的对话机器人。
工作流的第一步通常是**“开始”节点**,它接收用户的最初输入。接下来,我们需要一个**“问题分类器”**节点。这是实现智能交互的关键:系统需要判断用户是想生成新图片,还是修改上一张图片,或者只是进行普通对话。
在Dify中配置“问题分类器”时,我们需要为其提供清晰的分类指令。以下是一个优化的系统提示词示例,用于引导大模型进行准确分类:
你是一个智能对话路由助手。请根据用户的当前输入,判断其意图属于以下三类中的哪一类:
1. **生成新图片**:当用户描述一个全新的场景、物体或概念,并明显希望得到一张对应的新图片时。例如:“画一只在月球上跳芭蕾的猫”、“生成一张赛博朋克风格的城市夜景”。
2. **修改现有图片**:当用户的请求基于之前对话中已生成的图片,并希望对其进行调整、添加元素、改变风格等时。通常包含“修改”、“调整”、“把...改成...”、“在刚才的图上加一个...”等关键词。例如:“把背景换成星空”、“让它的表情更开心点”。
3. **普通对话**:与图片生成和修改无关的其他任何问题或聊天。例如:“你好”、“你是谁”、“今天天气怎么样”。
请只输出分类编号(1, 2, 3),不要输出任何其他解释性文字。
用户输入:{{#sys.query#}}
将这个提示词配置到分类器节点,并连接一个合适的LLM(如GPT-4o、Claude或国内可访问的DeepSeek、通义千问等)。节点的输出将是一个简单的数字,这便于后续的条件判断。
3.2 实现条件分支与多轮对话记忆
分类之后,我们需要用**“条件判断”**节点来分流。根据分类器的输出(1, 2, 3),我们将请求导向三个不同的处理分支。
这里有一个精妙的设计点:如何让系统记住上一轮生成的图片,以便在用户说“修改它”时知道“它”指的是什么?这就需要用到Dify的**“变量”功能,特别是“会话变量”**。
- 在“生成新图片”分支的末尾,当成功生成图片并获得URL后,我们使用一个**“变量赋值”**节点,将这个图片URL保存到一个会话变量中,例如命名为
last_generated_image_url。会话变量的特点是它在同一次对话会话中持续有效。 - 在“修改现有图片”分支的开头,我们从同一个会话变量
last_generated_image_url中读取值,作为图生图API的image_url参数。
这样,就实现了一个简单的“对话记忆”。用户在第一轮说“画一只猫”,系统生成猫的图片并记住URL;用户在第二轮说“给它加顶帽子”,系统就能自动取出上一轮的图片URL,发送给编辑接口。这个模式非常强大,可以扩展到记住更多上下文信息。
3.3 集成自定义工具与提示词优化
接下来是核心操作节点:调用我们自建的图像API。在Dify中,这通过**“自定义工具”**节点完成。
- 创建自定义工具:在Dify的“工具”模块中,选择“创建自定义工具”。我们需要提供API的OpenAPI Schema(Swagger规范)。根据之前编写的FastAPI服务,我们可以生成如下简化的
openapi.yaml描述文件:
openapi: 3.0.0
info:
title: Gemini Image Service
version: 1.0.0
servers:
- url: http://localhost:8080 # 你的API服务地址
paths:
/v1/generate:
post:
summary: 根据文本生成图片
operationId: generateImage
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/TextToImageRequest'
responses:
'200':
description: 成功
/v1/edit:
post:
summary: 根据文本和原图编辑图片
operationId: editImage
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ImageEditRequest'
responses:
'200':
description: 成功
components:
schemas:
TextToImageRequest:
type: object
required:
- prompt
properties:
prompt:
type: string
description: 生成图片的详细描述
size:
type: string
description: 图片尺寸,如1024x1024
default: 1024x1024
ImageEditRequest:
type: object
required:
- prompt
- image_url
properties:
prompt:
type: string
description: 描述如何修改图片
image_url:
type: string
description: 待修改图片的公开URL
将这份YAML内容粘贴到Dify自定义工具的Schema定义框中。Dify会自动解析出接口和参数。你还需要在“认证”部分配置API Key(如果需要),以及设置正确的服务器地址。
-
在工作流中使用工具:回到工作流编辑界面,从节点库中拖入两个“自定义工具”节点,分别选择我们刚创建的“生成图片”和“编辑图片”工具。
- 对于“生成图片”工具,其
prompt参数可以连接到一个**“LLM”节点的输出。这个LLM节点的作用是进行提示词优化**。用户输入可能是“一只可爱的猫”,而优化的提示词可能是“A photorealistic close-up portrait of an extremely cute fluffy orange tabby cat, with bright green eyes looking curiously at the viewer, soft natural lighting, detailed fur, cinematic depth of field, on a cozy windowsill with a plant in the background”。通过一个专门的LLM来润色和扩展用户输入,能极大提升出图质量和成功率。 - 对于“编辑图片”工具,其
image_url参数则连接到我们之前设置的会话变量last_generated_image_url。
- 对于“生成图片”工具,其
-
处理与返回结果:自定义工具节点执行后,会返回一个结构化的JSON响应。我们需要用一个**“代码”节点来解析这个响应,提取出图片的URL,并将其格式化为Markdown图片链接(如
),最后通过一个“回复”**节点发送给用户。同时,在这个“代码”节点中,我们也要执行将会话变量last_generated_image_url更新为最新图片URL的操作。
整个工作流的逻辑链条至此就清晰了:用户输入 -> 意图分类 -> 条件分支 -> (优化提示词) -> 调用对应API -> 解析结果并更新记忆 -> 回复用户。
4. 部署、调试与进阶优化策略
将各个节点像拼图一样连接起来后,一个可视化的工作流就诞生了。点击右上角的“预览”按钮,你可以在Dify提供的聊天界面中进行实时测试。输入“画一个骑着自行车的熊猫”,观察工作流如何一步步执行,并最终返回一张图片。
4.1 服务部署与网络考量
本地测试通过后,需要考虑部署,让服务能在任何地方被访问。
- API服务部署:你的FastAPI服务需要部署在一台有公网IP的服务器上。推荐使用Docker进行容器化部署,这能保证环境一致性。编写一个简单的
Dockerfile和docker-compose.yml文件,可以轻松地在云服务器上启动服务。别忘了在云服务商的安全组中开放你API服务监听的端口(如8080)。 - Dify工作流部署:Dify本身也支持多种部署方式。对于个人或小团队,使用Dify Cloud是最简单的;如果需要数据私有化,可以按照官方文档部署Dify开源版。
- 网络问题:这是整合海外AI服务(如Gemini)时最常见的挑战。你的自建API服务所在服务器必须能够稳定访问
generativelanguage.googleapis.com(Gemini API域名)。你需要确保服务器网络环境符合要求。绝对不要在应用前端或客户端层面处理任何网络连通性问题,所有与Gemini API的通信都应封装在后端服务中。
4.2 性能监控与错误处理
一个健壮的系统离不开完善的监控和错误处理。
- 日志记录:我们在FastAPI服务中已经使用了Python的
logging模块。确保将日志输出到文件,并设置合理的日志级别(INFO, ERROR)。这对于排查“为什么这次生成失败了”至关重要。 - API限流与重试:Gemini API有调用频率限制。在你的自定义服务中,应考虑加入简单的限流逻辑(如使用
time.sleep)和失败重试机制(如使用tenacity库),以提高服务的鲁棒性。 - Dify工作流调试:Dify工作流编辑器提供了每个节点的执行详情。如果工作流执行失败,可以点击每个节点查看其输入、输出和错误信息,这是定位问题最直接的方法。
4.3 扩展可能性探讨
这个基础框架就像一棵树的树干,你可以在此基础上生长出许多枝丫:
- 多模型支持:你的自定义API服务可以不止调用Gemini。你可以增加对Stable Diffusion、DALL-E 3、Midjourney(通过第三方服务)等模型的支持,并在Dify工作流中让用户选择或根据场景自动选择最优模型。
- 高级图像处理:在API服务中,生成图片后、上传之前,可以加入后处理环节,如使用Python的PIL库进行缩放、添加水印、格式转换、压缩等。
- 成本与用量统计:在API服务层记录每次调用的模型、Token消耗(如果适用)、图片尺寸等信息,便于后续进行成本分析和用量控制。
- 队列与异步处理:对于耗时的图像生成任务,可以引入消息队列(如Redis, RabbitMQ),将请求放入队列异步处理,并通过WebSocket或轮询通知Dify前端任务完成,提升用户体验。
搭建这样一个平台,最大的收获不是最终那个能生图的聊天窗口,而是在这个过程中,你掌握了一种将尖端AI能力产品化的方法论:用低代码平台快速构建业务逻辑,用自建服务解决定制化、稳定性和安全性的问题。当Gemini 3.0发布,或者下一个更强大的图像模型出现时,你只需要更新你的API服务层,前端的Dify工作流可能无需大改,就能快速接入新的能力。这种架构的灵活性,才是应对AI领域日新月异变化的最佳策略。
更多推荐


所有评论(0)