Start when the host has no embedding model configured yet - #17
Merged
Conversation
Both embedding-dependent beans resolved the default embedding service while they were being created, via ai.withDefaultEmbeddingService(). That throws when no embedding model is registered, so a host whose provider key arrives at first run rather than at boot could not start — and could not reach the setup flow that would have supplied the key. messageEmbedder now holds a LazyEmbeddingService, which resolves its delegate on first use and pins it thereafter. Deferring keeps bean creation independent of both embedding-model availability and the order in which provider configurations register their services; a host whose model arrives after startup picks up the real service on the first embedding call. Pinning matters for provenance: a vector and the model name recorded alongside it are read separately, so a delegate that changed between the two reads would label a vector with the wrong model. A resolution that throws is not pinned, so a service that only becomes resolvable later is still picked up. Resolution prefers the application's own EmbeddingService bean where there is an unambiguous one (a @primary bean counts), falling back to the platform default. A host that supports late configuration registers a service that reports its own absence and can be switched on later. chatStoreVectorIndexSchema registers nothing when there is no model, rather than guessing a dimension. An index built at the wrong dimension is worse than no index, because writes to it succeed and a real model later disagrees with everything already stored. Absence is signalled either by throwing or by reporting no dimensions, so both are treated as "no model". The two catalogs are now separately owned. Catalogs sharing an owner are merged and their versions with them, so leaving the constraint catalog unowned would version it by the embedding model — and drag that version to null on every boot where the vector catalog is skipped. Behaviour is unchanged for any deployment that has an embedding model. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
jasperblues
force-pushed
the
fix/719-tolerate-absent-embedding-service
branch
from
August 7, 2026 23:51
ac81239 to
9f31fdc
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Both embedding-dependent beans resolved the default embedding service while being created:
withDefaultEmbeddingService()throws when no embedding model is registered. A host whose provider key arrives at first run rather than at boot therefore could not start — and so could not reach the setup flow that would have supplied the key.messageEmbedder— resolve lazily, then pinLazyEmbeddingServiceresolves its delegate on first use and pins it thereafter.Deferring keeps bean creation independent of two things: whether an embedding model exists at all, and the order in which provider configurations register their services. A host whose model arrives after startup picks up the real service on the first embedding call; a host with no model fails that call rather than the application context — and embedding failure is already non-fatal (see
StoredConversation).Pinning matters for provenance: a vector and the model name recorded alongside it are read separately, so a delegate that changed between the two reads would label a vector with the wrong model. A resolution that throws is deliberately not pinned, so a service that only becomes resolvable later is still picked up.
Resolution prefers the application's own
EmbeddingServicebean where there is an unambiguous one (getIfUnique(), so a@Primarybean counts), falling back toai.withDefaultEmbeddingService()— which preserves today's behaviour rather than failing on ambiguity.chatStoreVectorIndexSchema— skip rather than guessRegisters nothing when there is no model. An index built at the wrong dimension is worse than no index, because writes to it succeed — a real model configured later silently disagrees with everything already stored. Skipping is recoverable; a corrupted index is not obviously broken until someone notices bad results.
An absent-tolerant service may signal absence either by throwing or by reporting no dimensions, so both are treated as "no model".
Separately-owned catalogs
The constraint and vector catalogs are now named owners (
CONSTRAINT_SCHEMA_OWNER,VECTOR_SCHEMA_OWNER).This is load-bearing, not tidying. Catalogs sharing an owner are merged, and their versions with them. Leaving the constraint catalog unowned would version it by the embedding model — and drag that version to null on every boot where the vector catalog is skipped, churning schema state on hosts that are simply waiting for a key.
Compatibility
Unchanged for any deployment that has an embedding model — which is every existing one. The new paths are only reachable where resolution would previously have thrown and taken the context down.
Testing
ChatStoreSchemaWiringTestextended to cover the skip path and catalog ownershipPart of a four-repo fix for a host that boots before its provider key exists; the others are embabel/embabel-agent#1886, embabel/embabel-agent-rag-graph#16 and embabel/dice#77.
🤖 Generated with Claude Code