741 lines
36 KiB
Python
741 lines
36 KiB
Python
import google.generativeai as genai
|
|
import PIL.Image
|
|
import numpy as np
|
|
import time
|
|
import random
|
|
import os
|
|
import torch
|
|
import base64
|
|
import json
|
|
from io import BytesIO
|
|
import tempfile
|
|
|
|
class GeminiProNode:
|
|
def __init__(self):
|
|
self.api_key = None
|
|
self.model = None
|
|
self.config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'config.json')
|
|
self.load_config()
|
|
|
|
def load_config(self):
|
|
"""加载配置文件"""
|
|
try:
|
|
if os.path.exists(self.config_path):
|
|
with open(self.config_path, 'r', encoding='utf-8') as f:
|
|
config = json.load(f)
|
|
self.api_key = config.get('api_key', '')
|
|
if self.api_key:
|
|
genai.configure(api_key=self.api_key)
|
|
print("[Gemini Pro] 成功加载配置文件中的 API Key")
|
|
except Exception as e:
|
|
print(f"[Gemini Pro] 加载配置文件失败: {str(e)}")
|
|
|
|
def save_config(self, api_key):
|
|
"""保存配置文件"""
|
|
try:
|
|
config = {'api_key': api_key}
|
|
with open(self.config_path, 'w', encoding='utf-8') as f:
|
|
json.dump(config, f, indent=4)
|
|
print("[Gemini Pro] 成功保存 API Key 到配置文件")
|
|
except Exception as e:
|
|
print(f"[Gemini Pro] 保存配置文件失败: {str(e)}")
|
|
|
|
def add_random_variation(self, prompt, seed=0):
|
|
"""
|
|
在提示词末尾添加隐藏的随机标识
|
|
确保每次运行都能得到不同结果
|
|
"""
|
|
if seed == 0:
|
|
random_id = random.randint(10000, 99999)
|
|
else:
|
|
rng = random.Random(seed)
|
|
random_id = rng.randint(10000, 99999)
|
|
|
|
return f"{prompt} [variation-{random_id}]"
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"prompt": ("STRING", {"default": "Analyze the situation in details.", "multiline": True}),
|
|
"system_prompt": ("STRING", {"default": "You are a helpful AI assistant.", "multiline": True}),
|
|
"input_type": (["text", "image", "video", "audio"], {"default": "text"}),
|
|
"model": (["gemini-3-pro-preview","gemini-2.5-flash-preview-09-2025","gemini-2.5-flash-preview-05-20"], {"default": "gemini-2.5-flash-preview-09-2025"}),
|
|
"api_key": ("STRING", {"default": ""}),
|
|
"proxy": ("STRING", {"default": ""}),
|
|
"delay_time": (["0", "1", "2", "3", "5", "10"], {"default": "0"}),
|
|
"seed": ("INT", {
|
|
"default": 0,
|
|
"min": 0,
|
|
"max": 0xffffffffffffffff,
|
|
"tooltip": "随机种子,改变此值会强制重新生成内容"
|
|
}),
|
|
},
|
|
"optional": {
|
|
"image1": ("IMAGE",),
|
|
"image2": ("IMAGE",),
|
|
"image3": ("IMAGE",),
|
|
"image4": ("IMAGE",),
|
|
"video": ("VIDEO",),
|
|
"audio": ("AUDIO",),
|
|
"max_output_tokens": ("INT", {"default": 15360, "min": 1, "max": 65536}),
|
|
"temperature": ("FLOAT", {"default": 0.6, "min": 0.0, "max": 1.0, "step": 0.1}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("STRING",)
|
|
FUNCTION = "generate"
|
|
CATEGORY = "Gemini Pro"
|
|
|
|
def generate(self, prompt, system_prompt, input_type, model, api_key, proxy,
|
|
delay_time="0", seed=0, image1=None, image2=None, image3=None, image4=None, video=None, audio=None,
|
|
max_output_tokens=1000, temperature=0.6):
|
|
try:
|
|
# 在 API 调用前添加强制延迟
|
|
delay_seconds = int(delay_time)
|
|
if delay_seconds > 0:
|
|
print(f"[Gemini Pro] 等待 {delay_seconds} 秒...")
|
|
time.sleep(delay_seconds)
|
|
|
|
# 处理 API Key
|
|
if api_key.strip(): # 如果用户输入了新的 API Key
|
|
if api_key != self.api_key: # 如果与当前的不同
|
|
self.api_key = api_key
|
|
genai.configure(api_key=api_key)
|
|
self.save_config(api_key) # 保存到配置文件
|
|
elif not self.api_key: # 如果没有输入且没有保存的 API Key
|
|
return ("错误: 请先输入 API Key",)
|
|
|
|
# 处理代理设置 - 改为智能自适应
|
|
if proxy and proxy.strip():
|
|
# 格式化代理地址,确保包含协议前缀
|
|
proxy = proxy.strip()
|
|
if not (proxy.startswith('http://') or proxy.startswith('https://')):
|
|
proxy = f"http://{proxy}"
|
|
print(f"[Gemini Pro] 已自动添加http://前缀到代理地址")
|
|
|
|
# 设置环境变量
|
|
os.environ['http_proxy'] = proxy
|
|
os.environ['https_proxy'] = proxy
|
|
print(f"[Gemini Pro] 使用代理: {proxy}")
|
|
else:
|
|
# 如果未提供代理,确保清除之前可能设置的环境变量
|
|
if 'http_proxy' in os.environ:
|
|
del os.environ['http_proxy']
|
|
if 'https_proxy' in os.environ:
|
|
del os.environ['https_proxy']
|
|
print("[Gemini Pro] 不使用代理")
|
|
|
|
print(f"\n[Gemini Pro] 正在调用 API...")
|
|
print(f"[Gemini Pro] 模型: {model}")
|
|
print(f"[Gemini Pro] 输入类型: {input_type}")
|
|
print(f"[Gemini Pro] 系统引导词: {system_prompt}")
|
|
print(f"[Gemini Pro] 提示词: {prompt}")
|
|
print(f"[Gemini Pro] 随机种子: {seed}")
|
|
|
|
start_time = time.time()
|
|
|
|
# 添加随机变化因子到提示词
|
|
varied_prompt = self.add_random_variation(prompt, seed)
|
|
|
|
# 准备输入内容
|
|
content = [varied_prompt] if varied_prompt else []
|
|
|
|
# 根据输入类型处理
|
|
media_content = None
|
|
media_contents = []
|
|
if input_type == "text":
|
|
if any([image1, image2, image3, image4, video, audio]):
|
|
return ("错误: 文本模式下不能提供多媒体输入",)
|
|
elif input_type == "image":
|
|
try:
|
|
imgs = [img for img in [image1, image2, image3, image4] if img is not None]
|
|
if not imgs:
|
|
return ("错误: 图像模式下至少提供一张图片",)
|
|
|
|
converted_images = []
|
|
for idx, img in enumerate(imgs, start=1):
|
|
try:
|
|
original_img = img
|
|
# 转换图片格式
|
|
if isinstance(img, torch.Tensor):
|
|
# 确保图像是在 CPU 上
|
|
img = img.cpu()
|
|
# 如果是 4D tensor,去掉第一个维度
|
|
if len(img.shape) == 4:
|
|
img = img.squeeze(0)
|
|
# 如果是 3D tensor,调整维度顺序
|
|
if len(img.shape) == 3:
|
|
# 确保通道顺序是 (H, W, C)
|
|
if img.shape[0] == 3:
|
|
img = img.permute(1, 2, 0)
|
|
# 转换为 uint8 格式
|
|
img = (img * 255).round().clamp(0, 255)
|
|
# 转换为 numpy array
|
|
img = img.numpy().astype(np.uint8)
|
|
|
|
# 如果是 numpy array
|
|
if isinstance(img, np.ndarray):
|
|
# 确保形状正确
|
|
if len(img.shape) == 3:
|
|
# 确保是 RGB 格式
|
|
if img.shape[-1] == 4: # RGBA
|
|
img = img[..., :3] # 只保留 RGB
|
|
elif img.shape[-1] != 3:
|
|
raise ValueError(f"Unexpected image shape: {img.shape}")
|
|
else:
|
|
raise ValueError(f"Unexpected image dimensions: {len(img.shape)}")
|
|
|
|
# 转换为 PIL Image
|
|
pil_img = PIL.Image.fromarray(img)
|
|
converted_images.append(pil_img)
|
|
print(f"[Gemini Pro] 图片{idx}处理成功: {pil_img.size}")
|
|
else:
|
|
raise ValueError("Image must be tensor or numpy array")
|
|
except Exception as e_img:
|
|
print(f"[Gemini Pro] 第{idx}张图片处理错误: {str(e_img)}")
|
|
img_shape = original_img.shape if hasattr(original_img, 'shape') else 'unknown'
|
|
print(f"[Gemini Pro] 图片{idx}类型: {type(original_img)}")
|
|
print(f"[Gemini Pro] 图片{idx}形状: {img_shape}")
|
|
return (f"错误: 第{idx}张图片处理失败 - {str(e_img)}",)
|
|
|
|
# 如果有媒体内容,将其与提示词组合
|
|
if converted_images:
|
|
media_contents.extend(converted_images)
|
|
content = [varied_prompt] + media_contents if varied_prompt else media_contents
|
|
|
|
except Exception as e:
|
|
print(f"[Gemini Pro] 图片处理错误: {str(e)}")
|
|
return (f"错误: 图片处理失败 - {str(e)}",)
|
|
elif input_type == "video" and video is not None:
|
|
try:
|
|
# 处理视频文件
|
|
if isinstance(video, str): # 视频文件路径
|
|
# 使用GeminiFileUpload类上传视频文件
|
|
file_upload = GeminiFileUpload()
|
|
uploaded_file = file_upload.file_upload(video)
|
|
|
|
if isinstance(uploaded_file, tuple) and uploaded_file[0].startswith("错误"):
|
|
return uploaded_file
|
|
|
|
# 构建视频分析提示
|
|
video_prompt = f"这是一个视频文件。请分析视频内容,注意视频中的动作、场景变化和关键事件:\n{varied_prompt}"
|
|
content = [video_prompt, uploaded_file[0]]
|
|
|
|
else:
|
|
return ("错误: 视频输入必须是文件路径",)
|
|
|
|
print(f"[Gemini Pro] 视频处理成功")
|
|
except Exception as e:
|
|
print(f"[Gemini Pro] 视频处理错误: {str(e)}")
|
|
return (f"错误: 视频处理失败 - {str(e)}",)
|
|
elif input_type == "audio" and audio is not None:
|
|
media_content = audio
|
|
content = [varied_prompt, media_content] if varied_prompt else [media_content]
|
|
else:
|
|
return (f"错误: {input_type} 类型需要提供对应的输入",)
|
|
|
|
# 创建模型实例并生成内容
|
|
model_instance = genai.GenerativeModel(model)
|
|
|
|
# 添加系统提示词(如果有)
|
|
if system_prompt:
|
|
print(f"[Gemini Pro] 使用系统提示词: {system_prompt}")
|
|
content.insert(0, f"System: {system_prompt}\nUser: ")
|
|
|
|
# 添加基础延迟和重试逻辑
|
|
base_delay = 3 # 增加基础延迟到 3 秒
|
|
max_retries = 5 # 增加最大重试次数到 5 次
|
|
|
|
# 每次 API 调用前添加基础延迟
|
|
time.sleep(base_delay)
|
|
|
|
response = None
|
|
try:
|
|
for attempt in range(max_retries):
|
|
try:
|
|
if attempt > 0:
|
|
delay = base_delay * (2 ** attempt) # 增加退避时间
|
|
print(f"[Gemini Pro] 配额限制,等待 {delay} 秒后重试...")
|
|
time.sleep(delay)
|
|
|
|
response = model_instance.generate_content(
|
|
content,
|
|
generation_config=genai.types.GenerationConfig(
|
|
temperature=temperature,
|
|
max_output_tokens=max_output_tokens,
|
|
candidate_count=1
|
|
)
|
|
)
|
|
break # 如果成功,跳出重试循环
|
|
except Exception as e:
|
|
if ("429" in str(e) or "Resource has been exhausted" in str(e)) and attempt < max_retries - 1:
|
|
print(f"[Gemini Pro] 配额限制,正在重试 ({attempt + 1}/{max_retries})")
|
|
continue
|
|
raise # 如果是其他错误或已达到最大重试次数,则抛出异常
|
|
|
|
# 修改重试逻辑,添加总体超时控制
|
|
max_retries = 3
|
|
base_delay = 2
|
|
start_time = time.time()
|
|
timeout = 30 # 设置30秒超时
|
|
|
|
if not response: # 如果前面的尝试没有成功获取响应
|
|
for attempt in range(max_retries):
|
|
try:
|
|
current_time = time.time()
|
|
if current_time - start_time > timeout:
|
|
raise TimeoutError("API 调用超时(超过30秒)")
|
|
if attempt > 0:
|
|
delay = min(base_delay * (2 ** (attempt - 1)), timeout - (current_time - start_time))
|
|
if delay <= 0:
|
|
raise TimeoutError("API 调用超时(超过30秒)")
|
|
print(f"[Gemini Pro] 等待 {delay:.1f} 秒后重试...")
|
|
time.sleep(delay)
|
|
|
|
response = model_instance.generate_content(
|
|
content,
|
|
generation_config=genai.types.GenerationConfig(
|
|
temperature=temperature,
|
|
max_output_tokens=max_output_tokens,
|
|
candidate_count=1
|
|
)
|
|
)
|
|
break # 如果成功,跳出重试循环
|
|
except TimeoutError as te:
|
|
print(f"[Gemini Pro] {str(te)}")
|
|
return (f"错误: {str(te)}",)
|
|
except Exception as e:
|
|
# 重试机制
|
|
max_retries = 2 # 修改为总共重复2次
|
|
base_delay = 30 # 修改为30秒延迟
|
|
|
|
# 基础延迟
|
|
time.sleep(base_delay)
|
|
|
|
for attempt in range(max_retries):
|
|
try:
|
|
error_str = str(e)
|
|
print(f"[Gemini Pro] API调用错误: {error_str}")
|
|
|
|
# 检查是否是配额限制错误
|
|
if ("429" in str(e) or "Resource has been exhausted" in str(e)) and attempt < max_retries - 1:
|
|
print(f"[Gemini Pro] 配额限制,正在重试 ({attempt + 1}/{max_retries})")
|
|
time.sleep(30) # 30秒后重试
|
|
continue
|
|
else:
|
|
raise e
|
|
except Exception as e:
|
|
error_str = str(e)
|
|
print(f"[Gemini Pro] 生成内容时出错: {error_str}")
|
|
|
|
if "ResourceExhausted" in str(e) and attempt < max_retries - 1:
|
|
print(f"[Gemini Pro] 资源不足,正在重试 ({attempt + 1}/{max_retries})")
|
|
time.sleep(30) # 30秒后重试
|
|
continue
|
|
else:
|
|
raise e
|
|
|
|
end_time = time.time()
|
|
print(f"[Gemini Pro] API 调用耗时: {end_time - start_time:.2f} 秒")
|
|
|
|
# 安全获取响应文本,避免直接访问 response.text 导致异常
|
|
safe_text = ""
|
|
try:
|
|
text_candidate = getattr(response, "text", None)
|
|
if text_candidate:
|
|
safe_text = text_candidate
|
|
except Exception as e:
|
|
print(f"[Gemini Pro] 注意: 直接访问 response.text 失败: {e}")
|
|
# 从 candidates 中兜底提取
|
|
if not safe_text and hasattr(response, "candidates"):
|
|
try:
|
|
parts_texts = []
|
|
for cand in (getattr(response, "candidates", []) or []):
|
|
fr = getattr(cand, "finish_reason", None)
|
|
if fr is not None:
|
|
print(f"[Gemini Pro] 候选 finish_reason: {fr}")
|
|
content_obj = getattr(cand, "content", None)
|
|
if content_obj is not None:
|
|
parts = getattr(content_obj, "parts", None)
|
|
if parts:
|
|
for p in parts:
|
|
t = getattr(p, "text", None)
|
|
if t:
|
|
parts_texts.append(t)
|
|
if parts_texts:
|
|
safe_text = "\n".join(parts_texts)
|
|
except Exception as e:
|
|
print(f"[Gemini Pro] 从 candidates 提取文本失败: {e}")
|
|
|
|
if safe_text:
|
|
print(f"[Gemini Pro] 生成完成,长度: {len(safe_text)} 字符")
|
|
return (safe_text,)
|
|
else:
|
|
# 输出更多诊断信息(例如安全拦截原因)
|
|
try:
|
|
pf = getattr(response, "prompt_feedback", None)
|
|
block_reason = getattr(pf, "block_reason", None) if pf is not None else None
|
|
if block_reason:
|
|
print(f"[Gemini Pro] 警告: 输出被安全策略拦截,block_reason={block_reason}")
|
|
except Exception:
|
|
pass
|
|
print("[Gemini Pro] 错误: API 返回为空或被安全策略拦截")
|
|
return ("错误: API 未返回有效输出,可能被安全策略拦截或输入无效。",)
|
|
finally:
|
|
# 确保资源被正确释放
|
|
# 清理大型对象引用
|
|
if media_content and hasattr(media_content, 'close'):
|
|
try:
|
|
media_content.close()
|
|
except:
|
|
pass
|
|
|
|
# 关闭多张图片资源
|
|
if media_contents:
|
|
for im in media_contents:
|
|
if hasattr(im, 'close'):
|
|
try:
|
|
im.close()
|
|
except:
|
|
pass
|
|
|
|
# 清理内容列表中的大型对象
|
|
if content:
|
|
for i in range(len(content)):
|
|
if isinstance(content[i], PIL.Image.Image):
|
|
try:
|
|
content[i].close()
|
|
except:
|
|
pass
|
|
|
|
# 尝试显式地进行一次垃圾收集
|
|
import gc
|
|
gc.collect()
|
|
|
|
# 重置会话相关变量
|
|
model_instance = None
|
|
|
|
except Exception as e:
|
|
print(f"[Gemini Pro] 错误: {str(e)}")
|
|
return (f"错误: {str(e)}",)
|
|
|
|
class GeminiFileUpload:
|
|
def __init__(self):
|
|
self.api_key = None
|
|
self.config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'config.json')
|
|
self.temp_dir = os.path.join("I:", "ComfyUI_windows_portable", "ComfyUI", "temp")
|
|
self.load_config()
|
|
|
|
# 确保临时目录存在
|
|
if not os.path.exists(self.temp_dir):
|
|
try:
|
|
os.makedirs(self.temp_dir, exist_ok=True)
|
|
print(f"[Gemini文件上传] 创建临时目录: {self.temp_dir}")
|
|
except Exception as e:
|
|
print(f"[Gemini文件上传] 创建临时目录失败: {str(e)}")
|
|
return ("错误: 无法创建临时目录",)
|
|
|
|
def load_config(self):
|
|
"""加载配置文件"""
|
|
try:
|
|
if os.path.exists(self.config_path):
|
|
with open(self.config_path, 'r', encoding='utf-8') as f:
|
|
config = json.load(f)
|
|
self.api_key = config.get('api_key', '')
|
|
if self.api_key:
|
|
genai.configure(api_key=self.api_key)
|
|
print("[Gemini文件上传] 成功加载配置文件中的 API Key")
|
|
else:
|
|
print("[Gemini文件上传] 警告: 配置文件中没有找到有效的 API Key")
|
|
else:
|
|
print(f"[Gemini文件上传] 警告: 配置文件不存在 - {self.config_path}")
|
|
except Exception as e:
|
|
print(f"[Gemini文件上传] 加载配置文件失败: {str(e)}")
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"file_path": ("STRING", {"default": "./sample.mp3", "multiline": False}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("GEMINI_FILE",)
|
|
RETURN_NAMES = ("file",)
|
|
FUNCTION = "file_upload"
|
|
|
|
CATEGORY = "Gemini Pro"
|
|
|
|
def file_upload(self, file_path):
|
|
try:
|
|
if not self.api_key:
|
|
return ("错误: 未在配置文件中找到有效的 API Key,请先在 config.json 中配置",)
|
|
|
|
print(f"[Gemini文件上传] 正在处理文件: {file_path}")
|
|
|
|
# 检查原始文件是否存在
|
|
if not os.path.exists(file_path):
|
|
print(f"[Gemini文件上传] 错误: 文件不存在 - {file_path}")
|
|
return ("错误: 文件不存在",)
|
|
|
|
# 生成随机文件名
|
|
original_filename = os.path.basename(file_path)
|
|
file_ext = os.path.splitext(original_filename)[1]
|
|
random_filename = f"{int(time.time())}_{random.randint(1000, 9999)}{file_ext}"
|
|
temp_file_path = os.path.join(self.temp_dir, random_filename)
|
|
|
|
print(f"[Gemini文件上传] 创建临时文件: {temp_file_path}")
|
|
|
|
# 复制文件到临时目录
|
|
try:
|
|
import shutil
|
|
shutil.copy2(file_path, temp_file_path)
|
|
except Exception as e:
|
|
print(f"[Gemini文件上传] 复制文件到临时目录失败: {str(e)}")
|
|
return (f"错误: 复制文件失败 - {str(e)}",)
|
|
|
|
# 上传临时文件
|
|
try:
|
|
print(f"[Gemini文件上传] 上传临时文件: {temp_file_path}")
|
|
uploaded_file = genai.upload_file(temp_file_path)
|
|
print(f"[Gemini文件上传] 文件上传成功,ID: {uploaded_file.id if hasattr(uploaded_file, 'id') else '未知'}")
|
|
|
|
# 保存原始文件路径信息(可选,如果后续需要)
|
|
uploaded_file.original_filepath = file_path
|
|
|
|
# 删除临时文件
|
|
try:
|
|
os.remove(temp_file_path)
|
|
print(f"[Gemini文件上传] 临时文件已删除: {temp_file_path}")
|
|
except Exception as e:
|
|
print(f"[Gemini文件上传] 警告: 删除临时文件失败 - {str(e)}")
|
|
|
|
return (uploaded_file,)
|
|
except Exception as e:
|
|
# 上传失败,也尝试删除临时文件
|
|
try:
|
|
if os.path.exists(temp_file_path):
|
|
os.remove(temp_file_path)
|
|
print(f"[Gemini文件上传] 上传失败,临时文件已删除: {temp_file_path}")
|
|
except:
|
|
pass
|
|
|
|
print(f"[Gemini文件上传] 错误: 上传文件失败 - {str(e)}")
|
|
return (f"错误: 上传文件失败 - {str(e)}",)
|
|
|
|
except Exception as e:
|
|
print(f"[Gemini文件上传] 错误: {str(e)}")
|
|
return (f"错误: {str(e)}",)
|
|
|
|
|
|
class GeminiFileProcessing:
|
|
def __init__(self):
|
|
self.api_key = None
|
|
self.config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'config.json')
|
|
self.load_config()
|
|
|
|
def load_config(self):
|
|
"""加载配置文件"""
|
|
try:
|
|
if os.path.exists(self.config_path):
|
|
with open(self.config_path, 'r', encoding='utf-8') as f:
|
|
config = json.load(f)
|
|
self.api_key = config.get('api_key', '')
|
|
if self.api_key:
|
|
genai.configure(api_key=self.api_key)
|
|
print("[Gemini文件处理] 成功加载配置文件中的 API Key")
|
|
else:
|
|
print("[Gemini文件处理] 警告: 配置文件中没有找到有效的 API Key")
|
|
else:
|
|
print(f"[Gemini文件处理] 警告: 配置文件不存在 - {self.config_path}")
|
|
except Exception as e:
|
|
print(f"[Gemini文件处理] 加载配置文件失败: {str(e)}")
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"file": ("GEMINI_FILE",),
|
|
"prompt": ("STRING", {"default": "分析这个文件内容并提供摘要。", "multiline": True}),
|
|
"user_prompt": ("STRING", {"default": "你是一个专业的文件分析助手,请以专业、清晰的方式分析文件内容。", "multiline": True}),
|
|
"model": (["gemini-2.5-flash-preview-09-2025","gemini-3-pro-preview", "gemini-2.0-flash-exp","gemini-2.5-flash-preview-05-20"], {"default": "gemini-2.5-flash-preview-09-2025"}),
|
|
"stream": ("BOOLEAN", {"default": False}),
|
|
"max_output_tokens": ("INT", {"default": 65536, "min": 1, "max": 65536}),
|
|
"temperature": ("FLOAT", {"default": 0.6, "min": 0.0, "max": 1.0, "step": 0.1}),
|
|
"proxy": ("STRING", {"default": ""}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("STRING",)
|
|
RETURN_NAMES = ("response",)
|
|
FUNCTION = "generate_content"
|
|
OUTPUT_NODE = True
|
|
CATEGORY = "Gemini Pro"
|
|
|
|
def generate_content(self, file, prompt, user_prompt, model, stream, max_output_tokens=65536, temperature=0.6, proxy=""):
|
|
try:
|
|
if not self.api_key:
|
|
return ("错误: 未在配置文件中找到有效的 API Key,请先在 config.json 中配置",)
|
|
|
|
print(f"[Gemini文件处理] 正在处理文件")
|
|
print(f"[Gemini文件处理] 系统提示词: {user_prompt}")
|
|
print(f"[Gemini文件处理] 提示词: {prompt}")
|
|
print(f"[Gemini文件处理] 模型: {model}")
|
|
|
|
# 处理代理设置 - 智能自适应
|
|
if proxy and proxy.strip():
|
|
# 格式化代理地址,确保包含协议前缀
|
|
proxy = proxy.strip()
|
|
if not (proxy.startswith('http://') or proxy.startswith('https://')):
|
|
proxy = f"http://{proxy}"
|
|
print(f"[Gemini文件处理] 已自动添加http://前缀到代理地址")
|
|
|
|
# 设置环境变量
|
|
os.environ['http_proxy'] = proxy
|
|
os.environ['https_proxy'] = proxy
|
|
print(f"[Gemini文件处理] 使用代理: {proxy}")
|
|
else:
|
|
# 如果未提供代理,确保清除之前可能设置的环境变量
|
|
if 'http_proxy' in os.environ:
|
|
del os.environ['http_proxy']
|
|
if 'https_proxy' in os.environ:
|
|
del os.environ['https_proxy']
|
|
print("[Gemini文件处理] 不使用代理")
|
|
|
|
# 创建模型实例
|
|
model_instance = genai.GenerativeModel(model)
|
|
|
|
# 添加基础延迟和重试逻辑
|
|
base_delay = 3 # 基础延迟3秒
|
|
max_retries = 5 # 最大重试次数5次
|
|
|
|
# 调用API前的延迟
|
|
time.sleep(base_delay)
|
|
|
|
# 合并用户提示词和提示词
|
|
combined_prompt = f"System: {user_prompt}\nUser: {prompt}"
|
|
|
|
# 发送请求并处理流式/非流式响应
|
|
response = None
|
|
textoutput = ""
|
|
|
|
try:
|
|
for attempt in range(max_retries):
|
|
try:
|
|
if attempt > 0:
|
|
delay = base_delay * (2 ** attempt) # 指数退避
|
|
print(f"[Gemini文件处理] 配额限制,等待 {delay} 秒后重试...")
|
|
time.sleep(delay)
|
|
|
|
start_time = time.time()
|
|
|
|
if stream:
|
|
print("[Gemini文件处理] 使用流式输出模式")
|
|
response = model_instance.generate_content(
|
|
[combined_prompt, file],
|
|
stream=True,
|
|
generation_config=genai.types.GenerationConfig(
|
|
temperature=temperature,
|
|
max_output_tokens=max_output_tokens
|
|
)
|
|
)
|
|
# 使用列表推导式收集响应,确保迭代完成
|
|
chunks = [chunk for chunk in response]
|
|
textoutput = "\n".join([chunk.text for chunk in chunks if hasattr(chunk, 'text')])
|
|
else:
|
|
print("[Gemini文件处理] 使用标准输出模式")
|
|
response = model_instance.generate_content(
|
|
[combined_prompt, file],
|
|
generation_config=genai.types.GenerationConfig(
|
|
temperature=temperature,
|
|
max_output_tokens=max_output_tokens
|
|
)
|
|
)
|
|
try:
|
|
text_candidate = getattr(response, "text", None)
|
|
if text_candidate:
|
|
textoutput = text_candidate
|
|
except Exception as e:
|
|
print(f"[Gemini文件处理] 注意: 直接访问 response.text 失败: {e}")
|
|
try:
|
|
candidates = getattr(response, "candidates", []) or []
|
|
texts = []
|
|
for cand in candidates:
|
|
fr = getattr(cand, "finish_reason", None)
|
|
if fr is not None:
|
|
print(f"[Gemini文件处理] 候选 finish_reason: {fr}")
|
|
content_obj = getattr(cand, "content", None)
|
|
if content_obj is not None:
|
|
parts = getattr(content_obj, "parts", None)
|
|
if parts:
|
|
for p in parts:
|
|
t = getattr(p, "text", None)
|
|
if t:
|
|
texts.append(t)
|
|
if texts:
|
|
textoutput = "\n".join(texts)
|
|
except Exception as e2:
|
|
print(f"[Gemini文件处理] 从 candidates 提取失败: {e2}")
|
|
|
|
# 如果成功获取到文本内容,退出重试循环
|
|
if textoutput:
|
|
break
|
|
|
|
except Exception as e:
|
|
error_str = str(e)
|
|
if "ResourceExhausted" in error_str and attempt < max_retries - 1:
|
|
print(f"[Gemini文件处理] 资源不足,正在重试 ({attempt + 1}/{max_retries})")
|
|
continue
|
|
elif attempt < max_retries - 1:
|
|
print(f"[Gemini文件处理] 错误: {error_str}, 正在重试 ({attempt + 1}/{max_retries})")
|
|
continue
|
|
raise # 其他错误或已达到最大重试次数,抛出异常
|
|
|
|
end_time = time.time()
|
|
print(f"[Gemini文件处理] API 调用耗时: {end_time - start_time:.2f} 秒")
|
|
|
|
if textoutput:
|
|
print(f"[Gemini文件处理] 生成完成,长度: {len(textoutput)} 字符")
|
|
return (textoutput,)
|
|
else:
|
|
try:
|
|
pf = getattr(response, "prompt_feedback", None)
|
|
block_reason = getattr(pf, "block_reason", None) if pf is not None else None
|
|
if block_reason:
|
|
print(f"[Gemini文件处理] 警告: 输出被安全策略拦截,block_reason={block_reason}")
|
|
except Exception:
|
|
pass
|
|
print("[Gemini文件处理] 警告: 未获取到有效输出")
|
|
return ("API未返回有效输出。请检查文件格式和提示词,然后重试。",)
|
|
|
|
finally:
|
|
# 确保资源被正确释放
|
|
# 清理模型实例
|
|
model_instance = None
|
|
|
|
# 清理文件引用
|
|
file = None
|
|
|
|
# 清理响应对象
|
|
response = None
|
|
|
|
# 显式进行垃圾回收
|
|
import gc
|
|
gc.collect()
|
|
|
|
print("[Gemini文件处理] 资源清理完成")
|
|
|
|
except Exception as e:
|
|
error_msg = str(e)
|
|
print(f"[Gemini文件处理] 错误: {error_msg}")
|
|
|
|
# 显式进行垃圾回收
|
|
import gc
|
|
gc.collect()
|
|
|
|
# 如果出现文件状态错误,给用户更清晰的提示
|
|
if "not in an ACTIVE state" in error_msg:
|
|
return ("错误: 文件状态无效。这可能是由于文件已被使用过,请重新上传文件。",)
|
|
|
|
return (f"错误: {error_msg}",) |