perf: read the model pool with one HGETALL instead of one HGET per model (#27821)

`request.app.state.MODELS` is a `RedisDict` when Redis is configured. Unpacking it with `{**pool}` makes Python call `keys()` and then `__getitem__` once per key, which is one HKEYS plus one HGET per model, issued sequentially through a synchronous client. At 200 models that is 201 blocking Redis round trips per call.

`RedisDict.items()` is a single HGETALL, so `dict(pool.items())` fetches the same data in one round trip. `utils/chat.py:184` already does exactly this and carries a comment explaining why; these ten call sites were missed.

They are on the direct-connection branch of the task endpoints (title, tags, follow-up, autocomplete, query generation and the rest), of `chat_completed`, and of context compaction, so they run for background tasks fired on ordinary chat turns.

Behaviour is unchanged. The merged mapping is identical, the explicitly added direct model still overrides any pool entry with the same id, and when Redis is not configured the pool is a plain dict where `dict(d.items())` and `{**d}` are equivalent.

It also closes a race. `RedisDict.set` writes with HSET and then HDELs the stale keys, so a key returned by HKEYS could be deleted before its HGET arrived, raising `KeyError` out of the dict literal and failing the request mid model refresh. The old path could likewise observe a mix of pre- and post-refresh entries. HGETALL is atomic, so the caller now always sees one coherent snapshot.
This commit is contained in:
Classic298
2026-07-31 17:25:53 -04:00
committed by GitHub
parent 6be11d4fc9
commit 243a39dc9d
3 changed files with 10 additions and 10 deletions
+8 -8
View File
@@ -117,7 +117,7 @@ async def generate_title(request: Request, form_data: dict, user=Depends(get_ver
if getattr(request.state, 'direct', False) and hasattr(request.state, 'model'):
models = {
**request.app.state.MODELS,
**dict(request.app.state.MODELS.items()),
request.state.model['id']: request.state.model,
}
else:
@@ -201,7 +201,7 @@ async def generate_follow_ups(request: Request, form_data: dict, user=Depends(ge
if getattr(request.state, 'direct', False) and hasattr(request.state, 'model'):
models = {
**request.app.state.MODELS,
**dict(request.app.state.MODELS.items()),
request.state.model['id']: request.state.model,
}
else:
@@ -271,7 +271,7 @@ async def generate_chat_tags(request: Request, form_data: dict, user=Depends(get
if getattr(request.state, 'direct', False) and hasattr(request.state, 'model'):
models = {
**request.app.state.MODELS,
**dict(request.app.state.MODELS.items()),
request.state.model['id']: request.state.model,
}
else:
@@ -335,7 +335,7 @@ async def generate_chat_tags(request: Request, form_data: dict, user=Depends(get
async def generate_image_prompt(request: Request, form_data: dict, user=Depends(get_verified_user)):
if getattr(request.state, 'direct', False) and hasattr(request.state, 'model'):
models = {
**request.app.state.MODELS,
**dict(request.app.state.MODELS.items()),
request.state.model['id']: request.state.model,
}
else:
@@ -417,7 +417,7 @@ async def generate_queries(request: Request, form_data: dict, user=Depends(get_v
if getattr(request.state, 'direct', False) and hasattr(request.state, 'model'):
models = {
**request.app.state.MODELS,
**dict(request.app.state.MODELS.items()),
request.state.model['id']: request.state.model,
}
else:
@@ -498,7 +498,7 @@ async def generate_autocompletion(request: Request, form_data: dict, user=Depend
if getattr(request.state, 'direct', False) and hasattr(request.state, 'model'):
models = {
**request.app.state.MODELS,
**dict(request.app.state.MODELS.items()),
request.state.model['id']: request.state.model,
}
else:
@@ -562,7 +562,7 @@ async def generate_autocompletion(request: Request, form_data: dict, user=Depend
async def generate_emoji(request: Request, form_data: dict, user=Depends(get_verified_user)):
if getattr(request.state, 'direct', False) and hasattr(request.state, 'model'):
models = {
**request.app.state.MODELS,
**dict(request.app.state.MODELS.items()),
request.state.model['id']: request.state.model,
}
else:
@@ -628,7 +628,7 @@ async def generate_emoji(request: Request, form_data: dict, user=Depends(get_ver
async def generate_moa_response(request: Request, form_data: dict, user=Depends(get_verified_user)):
if getattr(request.state, 'direct', False) and hasattr(request.state, 'model'):
models = {
**request.app.state.MODELS,
**dict(request.app.state.MODELS.items()),
request.state.model['id']: request.state.model,
}
else:
+1 -1
View File
@@ -316,7 +316,7 @@ async def chat_completed(request: Request, form_data: dict, user: Any):
if getattr(request.state, 'direct', False) and hasattr(request.state, 'model'):
models = {
**request.app.state.MODELS,
**dict(request.app.state.MODELS.items()),
request.state.model['id']: request.state.model,
}
else:
+1 -1
View File
@@ -2347,7 +2347,7 @@ async def process_chat_payload(request, form_data, user, metadata, model):
if is_saved_chat_id(chat_id) and user_message_id:
if getattr(request.state, 'direct', False) and hasattr(request.state, 'model'):
compaction_models = {
**request.app.state.MODELS,
**dict(request.app.state.MODELS.items()),
request.state.model['id']: request.state.model,
}
else: