Skip to content

feat(cache): cache model schemas in the daemon - #781

Closed
GareArc wants to merge 2 commits into
mainfrom
feat/esq1-190-model-schema-cache
Closed

feat(cache): cache model schemas in the daemon#781
GareArc wants to merge 2 commits into
mainfrom
feat/esq1-190-model-schema-cache

Conversation

@GareArc

@GareArc GareArc commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Description

Follow-up to #780. Same ownership problem, different cache — not required to fix #3547, so it ships separately.

Dify caches model schemas under a key built from the plugin id with no version:

# api/core/plugin/impl/model_runtime.py
cache_key = f"{self.tenant_id}:{provider}:{model_type.value}:{model}:{cache_user_id}"

provider resolves to the plugin id, so an upgrade does not change the key. Nothing invalidates it either — the only redis_client.delete on that path fires when a cached blob fails to parse as AIModelEntity, which catches a shape change but not a value change. An enterprise-side plugin upgrade therefore serves stale schemas for up to PLUGIN_MODEL_SCHEMA_CACHE_TTL (1h by default), and Dify has no way to learn about it.

What changed

Cache them here instead, keyed on PluginUniqueIdentifier, which pins version and checksum. An upgrade lands on a different key, so a stale entry is unreachable and nothing needs to invalidate this cache — the same content-addressed trick CombinedGetPluginDeclaration already uses. Credentials are folded in as a digest, as Dify does today.

GetAIModelSchema moves out of the generated dispatchers, following the InvokeAgentStrategy precedent already commented into definitions.go. The generated service body calls baseSSEWithSession, which creates a session before the callback runs; a cache hit that still creates a session and dispatches to the plugin would not be saving anything. The hand-written service answers a hit directly in the same SSE wire format.

On a miss the response stream is teed via Filter + OnClose, storing only if exactly one chunk arrived with a non-nil schema, so an error mid-stream caches nothing.

Remote and debugging plugins keep one identifier across code changes and are never cached — otherwise a developer iterating on a plugin would be served their own stale build.

TTL defaults to 60 minutes via PLUGIN_MODEL_SCHEMA_CACHE_TTL; since the key is content-addressed it only bounds memory.

Separate first commit

The generator still wrote to internal/core/plugin_daemon, which was renamed to io_tunnel, so go run cmd/codegen/main.go failed partway and left the tree half-regenerated. Regenerating with the corrected path also settles import ordering in files that had been reformatted by hand after the last run.

Type of Change

  • Bug fix
  • New feature
  • Refactor
  • Performance improvement
  • Other

Essential Checklist

Testing

  • I have tested the changes locally and confirmed they work as expected
  • I have added unit tests where necessary and they pass successfully

4 tests in pkg/utils/cache/helper cover the round trip, keyed-on-version, keyed-on-credentials, and the remote-plugin skip. go build, go vet and gofmt are clean, and /model/schema is verified to register exactly once per router group.

Not verified locally: the endpoint end-to-end, which needs a running daemon with an installed plugin. The SSE cache-hit path is the piece most worth exercising against a real request before this leaves draft.

Additional Information

Only useful once PLUGIN_MODEL_SCHEMA_CACHE_ENABLED (langgenius/dify#39632) is turned off; until then Dify never calls through on a hit of its own cache.

Independent of #780 — the two touch the same four config files but no shared logic, so they can merge in either order.

GareArc added 2 commits July 27, 2026 01:54
The generator still wrote to internal/core/plugin_daemon, which was renamed
to io_tunnel, so `go run cmd/codegen/main.go` failed partway through and left
the tree half-regenerated.

Regenerating with the corrected path also settles import ordering in the files
that were reformatted by hand after the last run.
Dify caches model schemas under a key built from the plugin id with no
version, so an enterprise-side upgrade leaves stale schemas behind and Dify
has no way to learn about it.

Cache them here instead, keyed on the plugin unique identifier, which pins
version and checksum. An upgrade lands on a different key, so a stale entry
is unreachable and nothing needs to invalidate it.

GetAIModelSchema moves out of the generated dispatchers, following the
InvokeAgentStrategy precedent, so a cache hit can answer without creating a
session or invoking the plugin. Remote and debugging plugins keep one
identifier across code changes and are never cached.

TTL defaults to 60 minutes via PLUGIN_MODEL_SCHEMA_CACHE_TTL; since the key
is content-addressed it only bounds memory.
@GareArc GareArc closed this Jul 27, 2026
@GareArc
GareArc deleted the feat/esq1-190-model-schema-cache branch July 27, 2026 09:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant