kontext_pro_task.py 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  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 KontextProTask(BaseTask):
  11. def get_type(self) -> str:
  12. return "kontext_pro"
  13. @property
  14. def execute_url(self) -> str:
  15. return settings.kontext_pro_url
  16. def _headers(self) -> dict[str, str]:
  17. return {
  18. "Content-Type": "application/json",
  19. "x-key": settings.kontext_api_key,
  20. }
  21. def _submit(self, inputs: dict) -> dict:
  22. with logged_client(timeout=60) as client:
  23. resp = client.post(self.execute_url, headers=self._headers(), json=inputs)
  24. logger.info("[%s] Submit response: status=%d", self.get_type(), resp.status_code)
  25. resp.raise_for_status()
  26. bean = resp.json()
  27. if not bean:
  28. raise RuntimeError("Empty response from API")
  29. return bean
  30. def execute(self, param: TaskParam) -> TaskResult:
  31. logger.info("[%s] Submitting task: POST %s", self.get_type(), self.execute_url)
  32. t0 = time.time()
  33. try:
  34. bean = retry_on_failure(
  35. lambda: self._submit(param.inputs),
  36. task_name=self.get_type(),
  37. )
  38. except Exception as e:
  39. elapsed = time.time() - t0
  40. logger.error("[%s] Submit failed in %.2fs: %s", self.get_type(), elapsed, e)
  41. return TaskResult.failure(f"submit task fail: {e}")
  42. task_id = bean.get("id")
  43. polling_url = bean.get("polling_url")
  44. logger.info("[%s] Task submitted, remote_id=%s, polling_url=%s",
  45. self.get_type(), task_id, polling_url)
  46. poll_count = 0
  47. poll_t0 = time.time()
  48. while True:
  49. poll_count += 1
  50. poll = self._poll(polling_url)
  51. status = poll.get("status")
  52. logger.info("[%s] Poll #%d: remote_id=%s, status=%s, elapsed=%.1fs",
  53. self.get_type(), poll_count, task_id, status, time.time() - poll_t0)
  54. if status == "Pending":
  55. time.sleep(3)
  56. elif status == "Ready":
  57. result = poll.get("result", {})
  58. break
  59. else:
  60. logger.warning("[%s] Unexpected status '%s', treating as failure", self.get_type(), status)
  61. time.sleep(3)
  62. return TaskResult.failure(str(poll))
  63. if not result:
  64. return TaskResult.failure("No result found")
  65. images: list[str] = []
  66. sample = result.get("sample")
  67. if sample and sample.strip():
  68. images.append(sample)
  69. logger.info("[%s] Completed: %d images, total_elapsed=%.2fs",
  70. self.get_type(), len(images), time.time() - t0)
  71. return TaskResult.success({"images": images})
  72. def _poll(self, polling_url: str) -> dict:
  73. with logged_client(timeout=60) as client:
  74. resp = client.get(polling_url, headers=self._headers())
  75. return resp.json()