刘立冬 il y a 2 semaines
Parent
commit
858ffecc37
1 fichiers modifiés avec 95 ajouts et 46 suppressions
  1. 95 46
      post_evaluator_v4_langgraph.py

+ 95 - 46
post_evaluator_v4_langgraph.py

@@ -24,7 +24,8 @@ from PIL import Image
 from langchain_google_genai import ChatGoogleGenerativeAI
 from langchain_core.messages import HumanMessage, SystemMessage
 from langgraph.graph import StateGraph, END
-# import google.generativeai as genai  # 暂时禁用,版本冲突
+# 视频上传相关
+import mimetypes
 
 # ============================================================================
 # 常量配置
@@ -42,6 +43,10 @@ MAX_RETRIES = 2
 RETRY_WAIT_SECONDS = 3
 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
 CACHE_DIR = ".evaluation_cache"
@@ -1178,11 +1183,16 @@ class GeminiClient:
         if media_files:
             print(f"      🔍 传递给Gemini: {len(media_files)}个媒体文件")
             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:
-                    print(f"         🎥 视频[{i}]: {type(media).__name__}")
+                    print(f"         ⚠️  媒体[{i}]: 未知类型 {type(media).__name__}")
         else:
             print(f"      ⚠️  无媒体文件传递给Gemini(仅文本)")
 
@@ -1236,18 +1246,19 @@ class GeminiClient:
 # ============================================================================
 
 class VideoUploader:
-    """视频上传处理器"""
+    """视频上传处理器 - 使用 google.generativeai"""
 
     @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:
             video_url: 视频URL
 
         Returns:
-            (video_file, video_uri, temp_path)
+            (media_dict, file_uri, temp_path)
+            media_dict格式: {"type": "media", "file_uri": ..., "mime_type": ...}
         """
         import requests
 
@@ -1256,7 +1267,7 @@ class VideoUploader:
         os.close(temp_fd)
 
         try:
-            print(f"      📥 下载视频: {video_url[:60]}...")
+            print(f"      📥 下载视频: {video_url[:100]}...")
 
             # 下载
             loop = asyncio.get_event_loop()
@@ -1274,54 +1285,86 @@ class VideoUploader:
             file_size_mb = os.path.getsize(temp_path) / (1024 * 1024)
             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:
                 return None, None, temp_path
 
             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:
             print(f"      ❌ 视频上传失败: {str(e)[:100]}")
             return None, None, temp_path
 
     @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()
-        current_file = uploaded_file
 
         loop = asyncio.get_event_loop()
 
-        while current_file.state.name == "PROCESSING":
+        while True:
             elapsed = time.time() - start_time
             if elapsed > FILE_PROCESS_TIMEOUT:
-                print(f"      ❌ 视频处理超时: {current_file.name}")
+                print(f"      ❌ 视频处理超时")
                 return None
 
-            print(f"      ⏳ 等待Gemini处理视频...{elapsed:.0f}s")
-            await asyncio.sleep(RETRY_WAIT_SECONDS)
-
+            # 获取文件状态
             current_file = await loop.run_in_executor(
                 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 "",
         }
 
-        # 媒体描述
-        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个视频"
         else:
             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:
         # 创建并运行图