fix(core): stop blocking the event loop in OpenAI parsing and static routing

OpenAIParser now uses AsyncOpenAI — the previous sync SDK call ran directly
on the event loop (submitting to a ThreadPoolExecutor and immediately
blocking on .result() serialized it anyway). TitleParser.raw_parser and its
callers become async accordingly.

The SPA catch-all route listed dist/ on every request; snapshot it once at
startup. The module-level bangumi TTL cache is now lock-guarded — it is
written from asyncio.to_thread workers in notification paths while the event
loop reads it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014w1Z6Nxy6XTRgkFXqPr9Zh
This commit is contained in:
Estrella Pan
2026-07-02 11:39:10 +02:00
parent 6c39227aed
commit fa24ec4e2a
8 changed files with 43 additions and 37 deletions

View File

@@ -1,9 +1,8 @@
import json
import logging
from concurrent.futures import ThreadPoolExecutor
from typing import Any, Optional
from openai import AzureOpenAI, OpenAI
from openai import AsyncAzureOpenAI, AsyncOpenAI
from pydantic import BaseModel
from module.models import Bangumi
@@ -62,19 +61,19 @@ class OpenAIParser:
if not api_key:
raise ValueError("API key is required.")
if api_type == "azure":
self.client = AzureOpenAI(
self.client = AsyncAzureOpenAI(
api_key=api_key,
base_url=api_base,
azure_deployment=kwargs.get("deployment_id", ""),
api_version=kwargs.get("api_version", "2023-05-15"),
)
else:
self.client = OpenAI(api_key=api_key, base_url=api_base)
self.client = AsyncOpenAI(api_key=api_key, base_url=api_base)
self.model = model
self.openai_kwargs = kwargs
def parse(
async def parse(
self, text: str, prompt: str | None = None, asdict: bool = True
) -> dict | str:
"""parse text with openai
@@ -96,11 +95,8 @@ class OpenAIParser:
params = self._prepare_params(text, prompt)
with ThreadPoolExecutor(max_workers=1) as worker:
future = worker.submit(self.client.beta.chat.completions.parse, **params)
resp = future.result()
result = resp.choices[0].message.parsed
resp = await self.client.beta.chat.completions.parse(**params)
result = resp.choices[0].message.parsed
if asdict:
if hasattr(result, "model_dump"):