|
|
@@ -9,6 +9,7 @@ from __future__ import annotations
|
|
|
|
|
|
import json
|
|
|
import os
|
|
|
+import time
|
|
|
from typing import Any, Callable, Mapping
|
|
|
|
|
|
import httpx
|
|
|
@@ -57,6 +58,10 @@ def _with_media_update(result: dict[str, Any], update: dict[str, Any] | None) ->
|
|
|
return result
|
|
|
|
|
|
|
|
|
+def _with_timing(result: dict[str, Any], timing: dict[str, Any]) -> dict[str, Any]:
|
|
|
+ return {**result, "timing_metrics": timing}
|
|
|
+
|
|
|
+
|
|
|
def _clamp_score(value: Any) -> float:
|
|
|
try:
|
|
|
number = float(value)
|
|
|
@@ -157,16 +162,24 @@ class GeminiVideoClient:
|
|
|
play_url = media.get("play_url")
|
|
|
if not play_url:
|
|
|
return _with_media_update(
|
|
|
- _fail("no_play_url", query_text=query_text),
|
|
|
+ _with_timing(_fail("no_play_url", query_text=query_text), {"video_fetch": {"skipped": "no_play_url"}}),
|
|
|
_media_unavailable_update("no_play_url"),
|
|
|
)
|
|
|
+ timing_metrics: dict[str, Any] = {}
|
|
|
try:
|
|
|
- data_url = self.fetch_fn(play_url, content.get("platform", "douyin"))
|
|
|
+ data_url, fetch_metrics = self._fetch_video(play_url, content.get("platform", "douyin"))
|
|
|
+ timing_metrics["video_fetch"] = fetch_metrics
|
|
|
except Exception as exc:
|
|
|
- return _fail(
|
|
|
- "video_fetch_failed",
|
|
|
- query_text=query_text,
|
|
|
- exception_type=type(exc).__name__,
|
|
|
+ fetch_metrics = getattr(exc, "metrics", {}) or {}
|
|
|
+ if fetch_metrics:
|
|
|
+ timing_metrics["video_fetch"] = fetch_metrics
|
|
|
+ return _with_timing(
|
|
|
+ _fail(
|
|
|
+ "video_fetch_failed",
|
|
|
+ query_text=query_text,
|
|
|
+ exception_type=type(exc).__name__,
|
|
|
+ ),
|
|
|
+ timing_metrics,
|
|
|
)
|
|
|
|
|
|
messages = [
|
|
|
@@ -180,8 +193,11 @@ class GeminiVideoClient:
|
|
|
},
|
|
|
]
|
|
|
last_failure: dict[str, Any] | None = None
|
|
|
+ request_attempts: list[dict[str, Any]] = []
|
|
|
+ request_started_at = time.monotonic()
|
|
|
for attempt in range(2):
|
|
|
retry_count = attempt + 1
|
|
|
+ attempt_started_at = time.monotonic()
|
|
|
try:
|
|
|
response = self.http_post(
|
|
|
f"{self.base_url}/chat/completions",
|
|
|
@@ -190,9 +206,35 @@ class GeminiVideoClient:
|
|
|
timeout=self.timeout_seconds,
|
|
|
)
|
|
|
response.raise_for_status()
|
|
|
- return _parse(response.json(), query_text)
|
|
|
+ request_attempts.append(
|
|
|
+ {
|
|
|
+ "attempt": retry_count,
|
|
|
+ "duration_ms": _elapsed_ms(attempt_started_at, time.monotonic()),
|
|
|
+ "http_status_code": getattr(response, "status_code", None),
|
|
|
+ "status": "ok",
|
|
|
+ }
|
|
|
+ )
|
|
|
+ timing_metrics["gemini_request"] = {
|
|
|
+ "total_duration_ms": _elapsed_ms(request_started_at, time.monotonic()),
|
|
|
+ "attempts": request_attempts,
|
|
|
+ }
|
|
|
+ return _with_timing(_parse(response.json(), query_text), timing_metrics)
|
|
|
except httpx.HTTPError as exc:
|
|
|
failure_type = "gemini_client_timeout" if isinstance(exc, httpx.TimeoutException) else "gemini_http_error"
|
|
|
+ request_attempts.append(
|
|
|
+ {
|
|
|
+ "attempt": retry_count,
|
|
|
+ "duration_ms": _elapsed_ms(attempt_started_at, time.monotonic()),
|
|
|
+ "http_status_code": _http_status(exc),
|
|
|
+ "status": "failed",
|
|
|
+ "failure_type": failure_type,
|
|
|
+ "exception_type": type(exc).__name__,
|
|
|
+ }
|
|
|
+ )
|
|
|
+ timing_metrics["gemini_request"] = {
|
|
|
+ "total_duration_ms": _elapsed_ms(request_started_at, time.monotonic()),
|
|
|
+ "attempts": request_attempts,
|
|
|
+ }
|
|
|
last_failure = _fail(
|
|
|
failure_type,
|
|
|
query_text=query_text,
|
|
|
@@ -202,8 +244,21 @@ class GeminiVideoClient:
|
|
|
)
|
|
|
if attempt == 0 and _retryable_http(exc):
|
|
|
continue
|
|
|
- return last_failure
|
|
|
+ return _with_timing(last_failure, timing_metrics)
|
|
|
except (KeyError, IndexError, TypeError, ValueError, json.JSONDecodeError) as exc:
|
|
|
+ request_attempts.append(
|
|
|
+ {
|
|
|
+ "attempt": retry_count,
|
|
|
+ "duration_ms": _elapsed_ms(attempt_started_at, time.monotonic()),
|
|
|
+ "status": "failed",
|
|
|
+ "failure_type": "gemini_response_invalid",
|
|
|
+ "exception_type": type(exc).__name__,
|
|
|
+ }
|
|
|
+ )
|
|
|
+ timing_metrics["gemini_request"] = {
|
|
|
+ "total_duration_ms": _elapsed_ms(request_started_at, time.monotonic()),
|
|
|
+ "attempts": request_attempts,
|
|
|
+ }
|
|
|
last_failure = _fail(
|
|
|
"gemini_response_invalid",
|
|
|
query_text=query_text,
|
|
|
@@ -212,8 +267,17 @@ class GeminiVideoClient:
|
|
|
)
|
|
|
if attempt == 0:
|
|
|
continue
|
|
|
- return last_failure
|
|
|
- return last_failure or _fail("gemini_unknown_error", query_text=query_text)
|
|
|
+ return _with_timing(last_failure, timing_metrics)
|
|
|
+ return _with_timing(last_failure or _fail("gemini_unknown_error", query_text=query_text), timing_metrics)
|
|
|
+
|
|
|
+ def _fetch_video(self, play_url: str, platform: str) -> tuple[str, dict[str, Any]]:
|
|
|
+ if self.fetch_fn is video_fetch.fetch_and_compress:
|
|
|
+ return video_fetch.fetch_and_compress_with_metrics(play_url, platform)
|
|
|
+ started_at = time.monotonic()
|
|
|
+ result = self.fetch_fn(play_url, platform)
|
|
|
+ if isinstance(result, tuple) and len(result) == 2 and isinstance(result[1], dict):
|
|
|
+ return result[0], result[1]
|
|
|
+ return result, {"total_duration_ms": _elapsed_ms(started_at, time.monotonic())}
|
|
|
|
|
|
|
|
|
class MissingGeminiVideoClient:
|
|
|
@@ -237,3 +301,7 @@ def _media_unavailable_update(failure_reason: str) -> dict[str, Any]:
|
|
|
"failure_reason": failure_reason,
|
|
|
"raw_payload": {"failure_reason": failure_reason},
|
|
|
}
|
|
|
+
|
|
|
+
|
|
|
+def _elapsed_ms(start: float, end: float) -> int:
|
|
|
+ return max(0, int((end - start) * 1000))
|