|
@@ -24,7 +24,8 @@ from PIL import Image
|
|
|
from langchain_google_genai import ChatGoogleGenerativeAI
|
|
from langchain_google_genai import ChatGoogleGenerativeAI
|
|
|
from langchain_core.messages import HumanMessage, SystemMessage
|
|
from langchain_core.messages import HumanMessage, SystemMessage
|
|
|
from langgraph.graph import StateGraph, END
|
|
from langgraph.graph import StateGraph, END
|
|
|
-# import google.generativeai as genai # 暂时禁用,版本冲突
|
|
|
|
|
|
|
+# 视频上传相关
|
|
|
|
|
+import mimetypes
|
|
|
|
|
|
|
|
# ============================================================================
|
|
# ============================================================================
|
|
|
# 常量配置
|
|
# 常量配置
|
|
@@ -42,6 +43,10 @@ MAX_RETRIES = 2
|
|
|
RETRY_WAIT_SECONDS = 3
|
|
RETRY_WAIT_SECONDS = 3
|
|
|
FILE_PROCESS_TIMEOUT = 180
|
|
FILE_PROCESS_TIMEOUT = 180
|
|
|
|
|
|
|
|
|
|
+# 代理配置(用于访问 Google File API)
|
|
|
|
|
+HTTP_PROXY = "http://127.0.0.1:29758"
|
|
|
|
|
+HTTPS_PROXY = "https://127.0.0.1:29758"
|
|
|
|
|
+
|
|
|
# 缓存配置
|
|
# 缓存配置
|
|
|
ENABLE_CACHE = False
|
|
ENABLE_CACHE = False
|
|
|
CACHE_DIR = ".evaluation_cache"
|
|
CACHE_DIR = ".evaluation_cache"
|
|
@@ -1178,11 +1183,16 @@ class GeminiClient:
|
|
|
if media_files:
|
|
if media_files:
|
|
|
print(f" 🔍 传递给Gemini: {len(media_files)}个媒体文件")
|
|
print(f" 🔍 传递给Gemini: {len(media_files)}个媒体文件")
|
|
|
for i, media in enumerate(media_files[:3]):
|
|
for i, media in enumerate(media_files[:3]):
|
|
|
- if isinstance(media, dict) and media.get("type") == "image_url":
|
|
|
|
|
- data_url = media.get("image_url", {}).get("url", "")
|
|
|
|
|
- print(f" 📸 图片[{i}]: Base64 data URL ({len(data_url)}字符)")
|
|
|
|
|
|
|
+ if isinstance(media, dict):
|
|
|
|
|
+ if media.get("type") == "image_url":
|
|
|
|
|
+ data_url = media.get("image_url", {}).get("url", "")
|
|
|
|
|
+ print(f" 📸 图片[{i}]: Base64 data URL ({len(data_url)}字符)")
|
|
|
|
|
+ elif media.get("type") == "media":
|
|
|
|
|
+ file_uri = media.get("file_uri", "")
|
|
|
|
|
+ mime_type = media.get("mime_type", "")
|
|
|
|
|
+ print(f" 🎥 视频[{i}]: file_uri ({mime_type})")
|
|
|
else:
|
|
else:
|
|
|
- print(f" 🎥 视频[{i}]: {type(media).__name__}")
|
|
|
|
|
|
|
+ print(f" ⚠️ 媒体[{i}]: 未知类型 {type(media).__name__}")
|
|
|
else:
|
|
else:
|
|
|
print(f" ⚠️ 无媒体文件传递给Gemini(仅文本)")
|
|
print(f" ⚠️ 无媒体文件传递给Gemini(仅文本)")
|
|
|
|
|
|
|
@@ -1236,18 +1246,19 @@ class GeminiClient:
|
|
|
# ============================================================================
|
|
# ============================================================================
|
|
|
|
|
|
|
|
class VideoUploader:
|
|
class VideoUploader:
|
|
|
- """视频上传处理器"""
|
|
|
|
|
|
|
+ """视频上传处理器 - 使用 google.generativeai"""
|
|
|
|
|
|
|
|
@staticmethod
|
|
@staticmethod
|
|
|
- async def upload_video(video_url: str) -> tuple[Optional[Any], Optional[str], Optional[str]]:
|
|
|
|
|
|
|
+ async def upload_video(video_url: str) -> tuple[Optional[Dict], Optional[str], Optional[str]]:
|
|
|
"""
|
|
"""
|
|
|
- 上传视频到Gemini
|
|
|
|
|
|
|
+ 上传视频到Google服务器并获取file_uri
|
|
|
|
|
|
|
|
Args:
|
|
Args:
|
|
|
video_url: 视频URL
|
|
video_url: 视频URL
|
|
|
|
|
|
|
|
Returns:
|
|
Returns:
|
|
|
- (video_file, video_uri, temp_path)
|
|
|
|
|
|
|
+ (media_dict, file_uri, temp_path)
|
|
|
|
|
+ media_dict格式: {"type": "media", "file_uri": ..., "mime_type": ...}
|
|
|
"""
|
|
"""
|
|
|
import requests
|
|
import requests
|
|
|
|
|
|
|
@@ -1256,7 +1267,7 @@ class VideoUploader:
|
|
|
os.close(temp_fd)
|
|
os.close(temp_fd)
|
|
|
|
|
|
|
|
try:
|
|
try:
|
|
|
- print(f" 📥 下载视频: {video_url[:60]}...")
|
|
|
|
|
|
|
+ print(f" 📥 下载视频: {video_url[:100]}...")
|
|
|
|
|
|
|
|
# 下载
|
|
# 下载
|
|
|
loop = asyncio.get_event_loop()
|
|
loop = asyncio.get_event_loop()
|
|
@@ -1274,54 +1285,86 @@ class VideoUploader:
|
|
|
file_size_mb = os.path.getsize(temp_path) / (1024 * 1024)
|
|
file_size_mb = os.path.getsize(temp_path) / (1024 * 1024)
|
|
|
print(f" 📦 视频下载完成,大小: {file_size_mb:.2f}MB")
|
|
print(f" 📦 视频下载完成,大小: {file_size_mb:.2f}MB")
|
|
|
|
|
|
|
|
- # 上传到Gemini
|
|
|
|
|
- print(f" ☁️ 上传到Gemini...")
|
|
|
|
|
- # 暂时禁用视频上传功能(genai版本冲突)
|
|
|
|
|
- raise NotImplementedError("视频上传暂时禁用,等待修复版本冲突")
|
|
|
|
|
- # uploaded_file = await loop.run_in_executor(
|
|
|
|
|
- # None,
|
|
|
|
|
- # lambda: genai.upload_file(temp_path)
|
|
|
|
|
- # )
|
|
|
|
|
|
|
+ # 上传到Google File API
|
|
|
|
|
+ print(f" ☁️ 上传到Google File API...")
|
|
|
|
|
+
|
|
|
|
|
+ # 动态导入 google.generativeai(避免模块级别冲突)
|
|
|
|
|
+ import google.generativeai as genai
|
|
|
|
|
+
|
|
|
|
|
+ # 配置代理环境变量(让底层 HTTP 库使用代理)
|
|
|
|
|
+ # 设置大写和小写版本,确保 httplib2 能正确识别
|
|
|
|
|
+ os.environ['HTTP_PROXY'] = HTTP_PROXY
|
|
|
|
|
+ os.environ['HTTPS_PROXY'] = HTTPS_PROXY
|
|
|
|
|
+ os.environ['http_proxy'] = HTTP_PROXY
|
|
|
|
|
+ os.environ['https_proxy'] = HTTPS_PROXY
|
|
|
|
|
+ print(f" 🔧 使用代理: {HTTPS_PROXY}")
|
|
|
|
|
+
|
|
|
|
|
+ # 配置 API key
|
|
|
|
|
+ genai.configure(api_key=GEMINI_API_KEY)
|
|
|
|
|
+
|
|
|
|
|
+ # 上传文件
|
|
|
|
|
+ uploaded_file = await loop.run_in_executor(
|
|
|
|
|
+ None,
|
|
|
|
|
+ lambda: genai.upload_file(temp_path)
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ print(f" 📤 文件已上传: {uploaded_file.name}")
|
|
|
|
|
|
|
|
# 等待处理
|
|
# 等待处理
|
|
|
- processed_file = await VideoUploader._wait_for_processing(uploaded_file)
|
|
|
|
|
|
|
+ processed_file = await VideoUploader._wait_for_processing(uploaded_file.name)
|
|
|
if not processed_file:
|
|
if not processed_file:
|
|
|
return None, None, temp_path
|
|
return None, None, temp_path
|
|
|
|
|
|
|
|
print(f" ✅ 视频上传成功: {processed_file.uri}")
|
|
print(f" ✅ 视频上传成功: {processed_file.uri}")
|
|
|
- return processed_file, processed_file.uri, temp_path
|
|
|
|
|
|
|
+
|
|
|
|
|
+ # 检测MIME类型
|
|
|
|
|
+ mime_type = mimetypes.guess_type(temp_path)[0] or "video/mp4"
|
|
|
|
|
+
|
|
|
|
|
+ # 返回media字典格式
|
|
|
|
|
+ media_dict = {
|
|
|
|
|
+ "type": "media",
|
|
|
|
|
+ "file_uri": processed_file.uri,
|
|
|
|
|
+ "mime_type": mime_type
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return media_dict, processed_file.uri, temp_path
|
|
|
|
|
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
|
print(f" ❌ 视频上传失败: {str(e)[:100]}")
|
|
print(f" ❌ 视频上传失败: {str(e)[:100]}")
|
|
|
return None, None, temp_path
|
|
return None, None, temp_path
|
|
|
|
|
|
|
|
@staticmethod
|
|
@staticmethod
|
|
|
- async def _wait_for_processing(uploaded_file: Any) -> Optional[Any]:
|
|
|
|
|
- """等待Gemini处理视频文件"""
|
|
|
|
|
|
|
+ async def _wait_for_processing(file_name: str) -> Optional[any]:
|
|
|
|
|
+ """等待Google处理视频文件"""
|
|
|
|
|
+ # 动态导入 google.generativeai
|
|
|
|
|
+ import google.generativeai as genai
|
|
|
|
|
+
|
|
|
start_time = time.time()
|
|
start_time = time.time()
|
|
|
- current_file = uploaded_file
|
|
|
|
|
|
|
|
|
|
loop = asyncio.get_event_loop()
|
|
loop = asyncio.get_event_loop()
|
|
|
|
|
|
|
|
- while current_file.state.name == "PROCESSING":
|
|
|
|
|
|
|
+ while True:
|
|
|
elapsed = time.time() - start_time
|
|
elapsed = time.time() - start_time
|
|
|
if elapsed > FILE_PROCESS_TIMEOUT:
|
|
if elapsed > FILE_PROCESS_TIMEOUT:
|
|
|
- print(f" ❌ 视频处理超时: {current_file.name}")
|
|
|
|
|
|
|
+ print(f" ❌ 视频处理超时")
|
|
|
return None
|
|
return None
|
|
|
|
|
|
|
|
- print(f" ⏳ 等待Gemini处理视频...{elapsed:.0f}s")
|
|
|
|
|
- await asyncio.sleep(RETRY_WAIT_SECONDS)
|
|
|
|
|
-
|
|
|
|
|
|
|
+ # 获取文件状态
|
|
|
current_file = await loop.run_in_executor(
|
|
current_file = await loop.run_in_executor(
|
|
|
None,
|
|
None,
|
|
|
- lambda: genai.get_file(current_file.name)
|
|
|
|
|
|
|
+ lambda: genai.get_file(name=file_name)
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
- if current_file.state.name == "FAILED":
|
|
|
|
|
- print(f" ❌ 视频处理失败: {current_file.state}")
|
|
|
|
|
- return None
|
|
|
|
|
-
|
|
|
|
|
- return current_file
|
|
|
|
|
|
|
+ # 检查状态
|
|
|
|
|
+ if current_file.state.name == "ACTIVE":
|
|
|
|
|
+ print(f" ✅ 视频处理完成")
|
|
|
|
|
+ return current_file
|
|
|
|
|
+ elif current_file.state.name == "PROCESSING":
|
|
|
|
|
+ print(f" ⏳ 视频处理中... ({elapsed:.1f}s)")
|
|
|
|
|
+ await asyncio.sleep(RETRY_WAIT_SECONDS)
|
|
|
|
|
+ else:
|
|
|
|
|
+ print(f" ❌ 视频处理失败: {current_file.state.name}")
|
|
|
|
|
+ return None
|
|
|
|
|
|
|
|
|
|
|
|
|
# ============================================================================
|
|
# ============================================================================
|
|
@@ -1438,8 +1481,10 @@ class PromptAdapter:
|
|
|
"body_text": post.body_text or "",
|
|
"body_text": post.body_text or "",
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- # 媒体描述
|
|
|
|
|
- if post.type == "video":
|
|
|
|
|
|
|
+ # 媒体描述(检查是否真的有视频文件)
|
|
|
|
|
+ # 如果video_file存在于kwargs中且为None,说明是降级策略
|
|
|
|
|
+ has_video_file = kwargs.get("video_file") if "video_file" in kwargs else (post.type == "video")
|
|
|
|
|
+ if post.type == "video" and has_video_file:
|
|
|
params["num_images"] = "1个视频"
|
|
params["num_images"] = "1个视频"
|
|
|
else:
|
|
else:
|
|
|
num_images = len(post.images) if post.images else 0
|
|
num_images = len(post.images) if post.images else 0
|
|
@@ -1999,16 +2044,20 @@ async def evaluate_post_v4(
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
# 处理视频
|
|
# 处理视频
|
|
|
- if post.type == "video" and post.images and len(post.images) > 0:
|
|
|
|
|
- video_url = post.images[0] # 视频URL通常在images[0]
|
|
|
|
|
- video_file, video_uri, temp_path = await VideoUploader.upload_video(video_url)
|
|
|
|
|
- initial_state["video_file"] = video_file
|
|
|
|
|
- initial_state["video_uri"] = video_uri
|
|
|
|
|
- initial_state["temp_video_path"] = temp_path
|
|
|
|
|
-
|
|
|
|
|
- if not video_file:
|
|
|
|
|
- print(f" ❌ 视频上传失败,停止评估")
|
|
|
|
|
- return (None, None, None, None, None, None)
|
|
|
|
|
|
|
+ if post.type == "video" and post.video:
|
|
|
|
|
+ print(f" 📹 检测到视频帖子,准备上传视频...")
|
|
|
|
|
+ media_dict, video_uri, temp_path = await VideoUploader.upload_video(post.video)
|
|
|
|
|
+
|
|
|
|
|
+ if media_dict:
|
|
|
|
|
+ # 视频上传成功
|
|
|
|
|
+ initial_state["video_file"] = media_dict # 存储media字典
|
|
|
|
|
+ initial_state["video_uri"] = video_uri
|
|
|
|
|
+ initial_state["temp_video_path"] = temp_path
|
|
|
|
|
+ print(f" ✅ 视频已准备好用于评估")
|
|
|
|
|
+ else:
|
|
|
|
|
+ # 降级策略:使用封面图片
|
|
|
|
|
+ print(f" ⚠️ 视频上传失败,降级使用封面图片+文本进行评估")
|
|
|
|
|
+ # 继续评估,使用封面图片
|
|
|
|
|
|
|
|
try:
|
|
try:
|
|
|
# 创建并运行图
|
|
# 创建并运行图
|