gemini_helpers.py 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. """Deterministic Gemini video-relevance fakes (V4-M3)."""
  2. from __future__ import annotations
  3. import copy
  4. import hashlib
  5. import threading
  6. import time
  7. from typing import Any
  8. def fake_gemini_pool() -> dict[str, Any]:
  9. return {
  10. "schema_version": "v4_gemini_query_relevance.v1",
  11. "query_text": "pool query",
  12. "query_relevance_score": 80,
  13. "query_relevance_reason": "pool stub",
  14. "final_status": "ok",
  15. "retry_count": 0,
  16. }
  17. def fake_gemini_review() -> dict[str, Any]:
  18. return {
  19. "schema_version": "v4_gemini_query_relevance.v1",
  20. "query_text": "review query",
  21. "query_relevance_score": 60,
  22. "query_relevance_reason": "review stub",
  23. "final_status": "ok",
  24. "retry_count": 0,
  25. }
  26. def fake_gemini_fail(reason: str = "gemini_timeout") -> dict[str, Any]:
  27. return {
  28. "schema_version": "v4_gemini_query_relevance.v1",
  29. "query_text": "failed query",
  30. "query_relevance_score": None,
  31. "query_relevance_reason": "",
  32. "final_status": "failed",
  33. "failure_type": reason,
  34. "exception_type": "TimeoutException",
  35. "http_status_code": None,
  36. "retry_count": 1,
  37. }
  38. class FakeGeminiVideoClient:
  39. def __init__(
  40. self,
  41. *,
  42. result_by_content_id: dict[str, dict[str, Any]] | None = None,
  43. default_result: dict[str, Any] | None = None,
  44. ) -> None:
  45. self.result_by_content_id = result_by_content_id or {}
  46. self.default_result = default_result or fake_gemini_pool()
  47. self.calls: list[dict[str, Any]] = []
  48. self._lock = threading.Lock() # M5: analyze 会被并发调用,calls 记录需加锁
  49. def analyze(
  50. self,
  51. content: dict[str, Any],
  52. media: dict[str, Any],
  53. source_context: dict[str, Any],
  54. ) -> dict[str, Any]:
  55. with self._lock:
  56. self.calls.append(
  57. {"content": copy.deepcopy(content), "media": copy.deepcopy(media)}
  58. )
  59. content_id = str(content.get("platform_content_id", ""))
  60. result = self.result_by_content_id.get(content_id, self.default_result)
  61. return copy.deepcopy(result)
  62. class JitteredFakeGeminiVideoClient(FakeGeminiVideoClient):
  63. """按 content_id 哈希定 0-9ms sleep(确定性),强制完成顺序≠提交顺序,
  64. 暴露并发回收未按 offset 归位的 bug;返回值仍由 content_id 决定。"""
  65. def analyze(
  66. self,
  67. content: dict[str, Any],
  68. media: dict[str, Any],
  69. source_context: dict[str, Any],
  70. ) -> dict[str, Any]:
  71. digest = hashlib.sha1(str(content.get("platform_content_id", "")).encode("utf-8")).hexdigest()
  72. time.sleep((int(digest[:4], 16) % 10) / 1000.0)
  73. return super().analyze(content, media, source_context)