File size: 2,716 Bytes
65602c7
e549219
 
 
 
 
 
 
 
 
 
65602c7
 
 
 
 
 
 
 
 
 
e549219
65602c7
 
 
e549219
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65602c7
 
 
 
 
 
 
 
 
 
e549219
 
65602c7
 
 
 
e549219
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
"""Service to handle task result notifications with retries and diagnostics."""

from typing import Dict, Any
import httpx
from pydantic import HttpUrl
from tenacity import retry, stop_after_attempt, wait_exponential

class NotificationService:
    """Service for sending notifications with retry logic."""
    
    def __init__(self, max_retries: int = 5, timeout: float = 30.0):
        """Create a reusable HTTP client.
        timeout can be a float or seconds; we prefer a more generous read timeout on Spaces.
        """
        # Increase timeouts to accommodate slower evaluators
        timeout_cfg = httpx.Timeout(
            connect=10.0,
            read=max(60.0, float(timeout)),
            write=30.0,
            pool=10.0,
        )
        # Follow redirects to ensure we validate the final response code
        self.client = httpx.AsyncClient(timeout=timeout_cfg, follow_redirects=True, headers={
            "User-Agent": "TDS-Project1-API/1.0"
        })
        self.max_retries = max_retries
    
    @retry(
        stop=stop_after_attempt(5),
        wait=wait_exponential(multiplier=1, min=1, max=60),
        reraise=True
    )
    async def notify_evaluation(self, url: str | HttpUrl, task_result: Dict[str, Any]) -> None:
        """
        Send task result to evaluation URL with retries.
        
        Args:
            url: The evaluation URL to send results to (str or HttpUrl)
            task_result: The task result data to send
            
        Raises:
            httpx.HTTPError: If the request fails after all retries
        """
        # Convert HttpUrl to string if needed
        url_str = str(url) if not isinstance(url, str) else url
        
        try:
            response = await self.client.post(
                url_str,
                json=task_result,
                headers={"Content-Type": "application/json"}
            )
        except httpx.RequestError as e:
            # Provide clearer diagnostics for network-level errors
            print(f"[Notify] Request error to {url_str}: {e}")
            raise
        # Enforce strict 200 OK per requirement (not just any 2xx)
        if response.status_code != 200:
            body_preview = (response.text or "").strip()
            if len(body_preview) > 512:
                body_preview = body_preview[:512] + "..."
            print(f"[Notify] Non-200 from evaluator: {response.status_code}. Body: {body_preview}")
            raise httpx.HTTPStatusError(
                f"Expected 200 OK, got {response.status_code}", request=response.request, response=response
            )
        
    async def close(self):
        """Close the HTTP client."""
        await self.client.aclose()