fix: resolve redundant fetches in OpenRouter client with asyncio locks

This commit is contained in:
vegu-ai-tools
2026-05-01 12:56:08 +03:00
parent 28b3621ead
commit 23ba656190
2 changed files with 70 additions and 56 deletions

View File

@@ -47,6 +47,7 @@
- "Character Visuals: Fixed an infinite loop in the cover image and portrait tabs that spammed `scene_assets/search` requests at the backend whenever a character had no scene assets to fall back on as references. The reference-asset search now runs at most once per character, breaking the watcher feedback loop that previously kept firing throughout the time the Generate dialog was open."
- "macOS Modifiers: Ctrl+click affordances throughout the UI now also accept Cmd (Meta) on macOS, where the OS reserves Ctrl+click for the system context menu. Tooltips, hints, and docs label the modifier as Cmd on Mac and Ctrl elsewhere. Affects regenerate, scene tools (narrator/director/actor/creative), world state manager, contextual generate, message asset image, autocomplete (Ctrl+Enter), and history navigation. (#261)"
- "Game Loop Actor Iter: Fixed a bug where the `game_loop_actor_iter` event was only firing for player turns, so agents that listen for actor iteration (like passive narration) silently skipped AI turns."
- "OpenRouter: Fixed redundant provider/model list fetches when multiple OpenRouter clients are configured. Concurrent `status()` calls all saw the fetched-flag as still false and each kicked off its own HTTP request; the fetches are now serialized with an asyncio lock and double-checked locking so only one request runs."
improvements:
- "System Prompt Override Indicators: The system prompt override list now shows a pencil icon next to entries that have an active override, making it easy to see which prompts have been customized."
- "Search Strictness: The distance_mod embedding preset value is now a float (was int) with a range of 0.12.0, allowing both tighter and looser similarity matching. A 'Search Strictness' slider is now available in the Context Database UI, letting users tune search sensitivity on the fly. Changes persist to the active embedding preset immediately."

View File

@@ -50,6 +50,9 @@ DEFAULT_MODEL = "google/gemini-3-flash-preview"
MODELS_FETCHED = False
PROVIDERS_FETCHED = False
_MODELS_LOCK = asyncio.Lock()
_PROVIDERS_LOCK = asyncio.Lock()
async def fetch_available_models(api_key: str = None):
"""Fetch available models from OpenRouter API"""
@@ -58,30 +61,36 @@ async def fetch_available_models(api_key: str = None):
if MODELS_FETCHED:
return AVAILABLE_MODELS
try:
log.debug("Fetching models from OpenRouter")
async with httpx.AsyncClient() as client:
response = await client.get(
"https://openrouter.ai/api/v1/models", timeout=10.0
)
if response.status_code == 200:
data = response.json()
models = []
for model in data.get("data", []):
model_id = model.get("id")
if model_id:
models.append(model_id)
AVAILABLE_MODELS = sorted(models)
log.debug(f"Fetched {len(AVAILABLE_MODELS)} models from OpenRouter")
else:
log.warning(
f"Failed to fetch models from OpenRouter: {response.status_code}"
)
except Exception as e:
log.error(f"Error fetching models from OpenRouter: {e}")
async with _MODELS_LOCK:
if MODELS_FETCHED:
return AVAILABLE_MODELS
MODELS_FETCHED = True
return AVAILABLE_MODELS
try:
log.debug("Fetching models from OpenRouter")
async with httpx.AsyncClient() as client:
response = await client.get(
"https://openrouter.ai/api/v1/models", timeout=10.0
)
if response.status_code == 200:
data = response.json()
models = []
for model in data.get("data", []):
model_id = model.get("id")
if model_id:
models.append(model_id)
AVAILABLE_MODELS = sorted(models)
log.debug(
f"Fetched {len(AVAILABLE_MODELS)} models from OpenRouter"
)
else:
log.warning(
f"Failed to fetch models from OpenRouter: {response.status_code}"
)
except Exception as e:
log.error(f"Error fetching models from OpenRouter: {e}")
MODELS_FETCHED = True
return AVAILABLE_MODELS
async def fetch_available_providers(api_key: str = None):
@@ -91,43 +100,47 @@ async def fetch_available_providers(api_key: str = None):
if PROVIDERS_FETCHED:
return AVAILABLE_PROVIDERS
if not api_key:
api_key = get_config().openrouter.api_key
async with _PROVIDERS_LOCK:
if PROVIDERS_FETCHED:
return AVAILABLE_PROVIDERS
if not api_key:
api_key = get_config().openrouter.api_key
if not api_key:
log.warning("No OpenRouter API key available, cannot fetch providers")
PROVIDERS_FETCHED = True
return AVAILABLE_PROVIDERS
try:
log.debug("Fetching providers from OpenRouter")
async with httpx.AsyncClient() as client:
response = await client.get(
"https://openrouter.ai/api/v1/providers",
headers={"Authorization": f"Bearer {api_key}"},
timeout=10.0,
)
if response.status_code == 200:
data = response.json()
providers = []
for provider in data.get("data", []):
provider_name = provider.get("name")
if provider_name:
providers.append(provider_name)
AVAILABLE_PROVIDERS = sorted(providers)
log.info(
f"Fetched {len(AVAILABLE_PROVIDERS)} providers from OpenRouter"
)
else:
log.error(
f"Failed to fetch providers from OpenRouter: HTTP {response.status_code}"
)
except Exception as e:
log.error(f"Error fetching providers from OpenRouter: {e}")
if not api_key:
log.warning("No OpenRouter API key available, cannot fetch providers")
PROVIDERS_FETCHED = True
return AVAILABLE_PROVIDERS
try:
log.debug("Fetching providers from OpenRouter")
async with httpx.AsyncClient() as client:
response = await client.get(
"https://openrouter.ai/api/v1/providers",
headers={"Authorization": f"Bearer {api_key}"},
timeout=10.0,
)
if response.status_code == 200:
data = response.json()
providers = []
for provider in data.get("data", []):
provider_name = provider.get("name")
if provider_name:
providers.append(provider_name)
AVAILABLE_PROVIDERS = sorted(providers)
log.info(
f"Fetched {len(AVAILABLE_PROVIDERS)} providers from OpenRouter"
)
else:
log.error(
f"Failed to fetch providers from OpenRouter: HTTP {response.status_code}"
)
except Exception as e:
log.error(f"Error fetching providers from OpenRouter: {e}")
PROVIDERS_FETCHED = True
return AVAILABLE_PROVIDERS
def on_talemate_started(event):
"""Spawn background tasks to fetch models and providers"""