nano_banana_task.py 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960
  1. import logging
  2. import time
  3. from app.config import settings
  4. from app.service.task.base_task import BaseTask
  5. from app.service.task.task_param import TaskParam
  6. from app.service.task.task_result import TaskResult
  7. from app.util.http_util import logged_client
  8. from app.util.retry_util import retry_on_failure
  9. logger = logging.getLogger(__name__)
  10. class NanoBananaTask(BaseTask):
  11. def get_type(self) -> str:
  12. return "nano_banana"
  13. def _submit(self, endpoint: str, headers: dict, inputs: dict) -> dict:
  14. with logged_client(timeout=120) as client:
  15. resp = client.post(endpoint, headers=headers, json=inputs)
  16. logger.info("[nano_banana] Submit response: status=%d, body_size=%d",
  17. resp.status_code, len(resp.content))
  18. resp.raise_for_status()
  19. data = resp.json()
  20. if not data.get("candidates"):
  21. raise RuntimeError("No candidates in response")
  22. return data
  23. def execute(self, param: TaskParam) -> TaskResult:
  24. endpoint = f"{settings.gemini_base_url}/models/{settings.gemini_nano_banana_model}:generateContent"
  25. headers = {
  26. "x-goog-api-key": settings.gemini_api_key,
  27. "Content-Type": "application/json",
  28. }
  29. logger.info("[nano_banana] Calling Gemini API: POST %s", endpoint)
  30. t0 = time.time()
  31. try:
  32. data = retry_on_failure(
  33. lambda: self._submit(endpoint, headers, param.inputs),
  34. task_name="nano_banana",
  35. )
  36. except Exception as e:
  37. elapsed = time.time() - t0
  38. logger.error("[nano_banana] Request failed in %.2fs: %s", elapsed, e)
  39. return TaskResult.failure(f"submit execute fail: {e}")
  40. encoded_files: list[str] = []
  41. candidates = data.get("candidates", [])
  42. parts = candidates[0].get("content", {}).get("parts", [])
  43. for part in parts:
  44. text = part.get("text")
  45. if text:
  46. logger.info("[nano_banana] Text part: %s", text[:200])
  47. inline_data = part.get("inlineData")
  48. if inline_data and inline_data.get("data"):
  49. encoded_files.append(inline_data["data"])
  50. logger.info("[nano_banana] Completed: %d images generated", len(encoded_files))
  51. return TaskResult.success({"images": encoded_files})