Documentsdify
Plugin Model Caching
Plugin Model Caching
Type
Topic
Status
Published
Created
Jul 7, 2026
Updated
Jul 7, 2026
Created by
Dosu Bot
Updated by
Dosu Bot

Plugin Model Caching#

Plugin model caching covers how Dify discovers, caches, and refreshes the set of model providers contributed by installed plugins for a given tenant. The system has two separate cache layers — a backend Redis cache for provider lists and model schemas, and a frontend React Query cache for UI state — with explicit invalidation hooks connecting plugin lifecycle events (install, upgrade, uninstall) to both layers.

The entire backend cache is owned by PluginService in api/core/plugin/plugin_service.py. Centralizing ownership here keeps tenant isolation and invalidation logic in one place; all plugin lifecycle mutations (install, uninstall, upgrade) call invalidate_plugin_model_providers_cache() before returning .


Redis Key Structure#

Four Redis key families control the provider cache per tenant :

Key PrefixPurpose
plugin_model_providers:tenant_id:<id>:generation:<n>Provider list payload for a specific generation
plugin_model_providers_generation:tenant_id:<id>Monotonic generation counter; incremented on invalidation
plugin_model_providers_refresh_lock:tenant_id:<id>:generation:<n>Distributed lock preventing cache-fill stampedes
plugin_model_providers_remote_debug:tenant_id:<id>Marker tracking which remote/debug model plugins are active for cache invalidation purposes

Default TTL for the provider cache is 24 hours (PLUGIN_MODEL_PROVIDERS_CACHE_TTL=86400) . The lock TTL is 30 seconds; waiters time out after 2 seconds before falling back to a direct daemon call .

Large payloads (≥ 64 KB) are transparently compressed with zstd level-1 before storage, identified by the \x00dify-plugin-model-providers-zstd-v1: prefix. Readers accept both compressed and legacy plain-JSON payloads to support rolling upgrades .


Cache Read Path (fetch_plugin_model_providers)#

fetch_plugin_model_providers() implements a generation-aware, single-flight read loop:

  1. Read the current generation counter from Redis.
  2. Attempt to load the cached provider list for that generation.
  3. On a cache hit → return immediately.
  4. On a cache miss with a valid generation → acquire the per-generation distributed lock and re-check the cache inside the lock (double-checked locking).
  5. If the lock is acquired and the cache is still empty → fetch from the plugin daemon, store, and return.
  6. If the lock cannot be acquired within 2 seconds → fall back to a direct daemon fetch (no caching) .

This pattern was refined in PR #38226 to separate the lock's lease TTL from the waiter deadline and remove a per-worker memory cache, making Redis the single source of truth.

PluginModelRuntime.fetch_model_providers() delegates directly to this service method , so all model runtime paths share the same cache.


Cache Invalidation Path#

invalidate_plugin_model_providers_cache() uses a Redis pipeline to atomically:

  1. Delete the (legacy, non-generation) base cache key.
  2. Increment the generation counter (INCR), which renders any in-flight reads for the old generation stale.

Any waiter that races the increment and re-reads a changed generation will loop back to the top of fetch_plugin_model_providers , so no stale payload is ever served.

Invalidation is triggered by:

  • Every terminal plugin task status
  • All install, upgrade, and uninstall paths
  • When list_by_category is called for model plugins (category=Model) and the system detects remote debug plugin state has changed by comparing the current list of remote plugins to the cached marker; if the remote plugin marker differs or if remote plugins are present but not represented in the cached providers, the cache is invalidated and the new marker is stored — this ensures that model provider caches are refreshed when debugging remote model plugins

Model Schema Cache (get_model_schema)#

A separate, per-model Redis cache stores AIModelEntity schemas with a 1-hour TTL (PLUGIN_MODEL_SCHEMA_CACHE_TTL=3600) . The cache key is scoped by tenant_id:provider:model_type:model:user_id plus an MD5 hash of sorted credential key-value pairs, preventing cross-credential cache collisions . Invalid (schema-parse-error) entries are deleted on read .


Frontend React Query Cache#

The frontend maintains a parallel cache in React Query. refreshModelProviders() (defined in provider-context-provider.tsx) calls:

queryClient.invalidateQueries({ queryKey: ['common', 'model-providers'] })

useRefreshPluginList expands this for model-category plugins to also refetch every model-type list (LLM, Embedding, Rerank, TTS, Speech2Text) and default-model selections.

PR #37002 extended this pattern to the plugin detail panel: handlePluginUpdated in use-plugin-operations.ts now invalidates:

  • ['models', 'model-list'] and calls refreshModelProviders() for model plugins
  • ['tools', 'builtin-provider-tools'] for tool plugins
  • ['strategy', 'detail'] for agent-strategy plugins

Key Files and Entry Points#

FileRole
api/core/plugin/plugin_service.pyCache owner: Redis read/write/invalidation, distributed lock
api/core/plugin/impl/model_runtime.pyPluginModelRuntime: delegates provider fetch and schema cache to PluginService
api/configs/feature/__init__.pyPLUGIN_MODEL_PROVIDERS_CACHE_TTL (24 h), PLUGIN_MODEL_SCHEMA_CACHE_TTL (1 h)
web/context/provider-context-provider.tsxrefreshModelProviders() → React Query invalidation
web/app/components/plugins/install-plugin/hooks/use-refresh-plugin-list.tsxBroad model-list invalidation after install/update
web/app/components/plugins/plugin-detail-panel/detail-header/hooks/use-plugin-operations.tsPer-category cache invalidation in the detail panel (PR #37002)
web/service/use-tools.tsuseInvalidateAllToolProviders()
Plugin Model Caching | Dosu