Skip to content

Commit 66385fa

Browse files
torrid-fishclaude
andcommitted
fix(accent): degrade gracefully when OJAD is unavailable
OJAD (www.gavo.t.u-tokyo.ac.jp) outages made /MarkAccent/ hang for the full global 10s timeout and then return HTTP 500, because get_ojad_result re-raised the transport error and the pipeline turned it into a 500. Pitch accent from OJAD only enriches the furigana result, so an OJAD outage should not fail the whole request: - ojad.py: add a short per-request timeout (connect=2s, read=5s) so a down OJAD fails fast instead of hanging on the global 10s, and raise a dedicated OJADUnavailableError on any httpx.HTTPError (connect/read timeouts, connection errors, non-2xx status). - pipeline.py: catch OJADUnavailableError and degrade to furigana-only output (align_accent already emits accent_marking_type=0 for an empty OJAD list) with status 200 and a warning, instead of 500. - models.py: add an optional, backward-compatible `warning` field to AccentResponse for degraded results. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 0c17733 commit 66385fa

3 files changed

Lines changed: 41 additions & 7 deletions

File tree

api/accent/models.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,3 +111,8 @@ class AccentResponse(BaseModel):
111111
default=None,
112112
description="An object that describes the details of an error when one occurs",
113113
)
114+
warning: str | None = Field(
115+
default=None,
116+
description="A non-fatal warning when results are degraded, e.g. furigana "
117+
"returned without pitch accent because OJAD was unavailable",
118+
)

api/accent/ojad.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,20 @@
2323

2424
OJAD_URL = "https://www.gavo.t.u-tokyo.ac.jp/ojad/phrasing/index"
2525

26+
# Per-request timeout for the OJAD POST. Overrides the global client timeout so a
27+
# down / unresponsive OJAD fails fast (≈2s on a dead host) instead of hanging for
28+
# the full global 10s before the pipeline can degrade gracefully.
29+
OJAD_TIMEOUT = httpx.Timeout(5.0, connect=2.0)
30+
31+
32+
class OJADUnavailableError(Exception):
33+
"""OJAD could not be reached or returned an error.
34+
35+
Raised on any transport failure (connect / read timeout, connection
36+
refused) or non-2xx status. Callers should treat this as "no pitch
37+
contour available" and degrade rather than fail the whole request.
38+
"""
39+
2640

2741
async def get_ojad_result(
2842
query_text: str,
@@ -47,12 +61,14 @@ async def get_ojad_result(
4761

4862
# Send a POST and receive the website html code
4963
try:
50-
response = await client.post(OJAD_URL, data=data)
64+
response = await client.post(OJAD_URL, data=data, timeout=OJAD_TIMEOUT)
5165
response.raise_for_status()
5266
logger.debug(f"[OJAD] Status Code: {response.status_code}")
53-
except Exception:
54-
logger.exception("[OJAD] Request Failed")
55-
raise
67+
except httpx.HTTPError as e:
68+
# Covers ConnectTimeout / ReadTimeout / ConnectError as well as the
69+
# HTTPStatusError from raise_for_status() — all httpx.HTTPError subclasses.
70+
logger.warning(f"[OJAD] Unavailable: {e}")
71+
raise OJADUnavailableError(str(e)) from e
5672

5773
website = response.text
5874

api/accent/pipeline.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from api.accent.align import align_accent
1919
from api.accent.furigana import fetch_furigana
2020
from api.accent.models import AccentResponse, ErrorInfo
21-
from api.accent.ojad import get_ojad_result
21+
from api.accent.ojad import OJADUnavailableError, get_ojad_result
2222

2323
logger = logging.getLogger("api")
2424

@@ -42,11 +42,24 @@ async def process_accent_chunk(text: str, client: httpx.AsyncClient) -> AccentRe
4242
furigana_results = furigana_response.result
4343
logger.debug(f"Yahoo Results Count: {len(furigana_results)}")
4444

45-
_ojad_surface, ojad_results = await get_ojad_result(query_text, client)
45+
# OJAD only enriches the result with pitch accent. If it is down, fall
46+
# back to furigana-only output (align_accent emits accent_marking_type=0
47+
# for every mora when given an empty list) rather than failing the
48+
# request — see api/accent/align.py "MATCH FAILED" branch.
49+
warning = None
50+
try:
51+
_ojad_surface, ojad_results = await get_ojad_result(query_text, client)
52+
except OJADUnavailableError as e:
53+
logger.warning(f"OJAD unavailable, degrading to furigana-only: {e}")
54+
ojad_results = []
55+
warning = (
56+
"OJAD pitch-accent service is unavailable; "
57+
"returning furigana without pitch accent."
58+
)
4659

4760
final_results = await align_accent(furigana_results, ojad_results)
4861

49-
return AccentResponse(status=200, result=final_results)
62+
return AccentResponse(status=200, result=final_results, warning=warning)
5063

5164
except Exception as e:
5265
logger.exception(f"Unexpected error occurred: {text}")

0 commit comments

Comments
 (0)