Service Entry and Registry
This page answers one of the most central engineering questions of the Core SDK: when you actually start wiring providers, switching models, and adding capabilities, where should your code enter.
1. Keep two main lines in mind first
Single-instance line
Configuration
-> AiService
-> IChatService / IResponsesService / IEmbeddingService / ...
This is the most common first wiring chain.
Multi-instance line
Configuration + AiConfig.platforms
-> DefaultAiServiceRegistry.from(...)
-> AiServiceRegistry
-> get(id)
-> registration.platformType + registration.aiService
This chain fits multi-account, multi-tenant, and multi-provider configurations that coexist.
Both lines are public, real, and extensible entry points. The docs do not promote ChatClient.openAi(...) or a hidden Ai4j.chat() as the main entry, because they obscure the boundaries of Tool, MCP, RAG, Memory, Responses, and custom network stacks.
2. What AiService is actually responsible for
AiService is not an entry point only for Chat; it is the unified capability factory of the current Core SDK.
From the implementation side, it currently owns:
getChatService(...)getResponsesService(...)getEmbeddingService(...)getAudioService(...)getRealtimeService(...)getImageService(...)getRerankService(...)getRagService(...)getIngestionPipeline(...)getModelReranker(...)getAgentFlow(...)webSearchEnhance(...)
This means it is not simply a "provider chooser" — it funnels model access, retrieval augmentation, and some composition capabilities into a single entry object.
3. AiService is an explicit factory today, not dynamic discovery
This fact must be stated up front.
Internally, AiService currently decides which implementation class to create through a set of switch(platform) statements.
So today's provider/service capability matrix is not auto-discovered; it is explicitly maintained.
This has two consequences:
- The support matrix is clear.
- Adding a new provider or a new service surface requires editing the factory's main chain.
This is exactly why AiService itself is the first entry point for understanding the support matrix and the cost of extension.
4. What AiServiceRegistry actually adds
AiServiceRegistry is not simply a Map<String, AiService>.
What it formally adds is:
- Managing multiple registration entries by
id - Each entry is bound to a
PlatformType - Exposing convenience methods that fetch
Chat / Responses / Embedding / RAG / Ingestion / Rerankerbyiddirectly
In other words, it does not merely store objects for you; it turns the "multi-instance capability entry point" itself into a formal abstraction.
5. The real behavior of DefaultAiServiceRegistry
The most notable thing about this implementation is not "it can register", but how it registers.
It will:
- Read
AiConfig.platforms - Validate each
AiPlatform.id - Resolve
platform->PlatformType - Copy the base
Configuration - Override only the configuration fields belonging to the current instance's provider
- Construct a scoped
AiServiceviaAiServiceFactory.create(...) - Produce an
AiServiceRegistration
This means multi-instance is not a set of fully independent containers; it is closer to:
- Sharing the underlying base configuration
- Each id owning its own provider-scoped configuration and
AiService
6. The role of FreeAiService
FreeAiService is currently a compatibility layer, not a new main-line entry point.
It retains:
- The static
getChatService(id) - The static
getEmbeddingService(id) - The static
getResponsesService(id)and other legacy entry points
But the more robust way to understand it in the docs is:
- Main-line entry:
AiService - Formal multi-instance abstraction:
AiServiceRegistry - Legacy compatibility shell:
FreeAiService
7. Where to place an OpenAI-compatible profile
Do not create a new provider type first for an OpenAI-compatible relay platform. Typically you configure it as platform: openai, then distinguish it with a different id and api-host:
ai:
platforms:
- id: trovebox-low-cost
platform: openai
api-key: ${TROVEBOX_API_KEY}
api-host: https://codex.trovebox.online/
IChatService chatService = aiServiceRegistry.getChatService("trovebox-low-cost");
Here trovebox-low-cost is a business profile id; openai indicates the underlying protocol adaptation. For the full recipe, see OpenAI-compatible and TroveBox.
8. How this page divides labor with adjacent pages
service-entry-and-registrycovers "where to enter the capabilities"platform-service-matrixcovers "which services each platform supports"model-accesscovers "how to model request semantics once inside a service"extensioncovers "which line to extend along when the default entry points are not enough"
9. A few facts most easily overlooked
Service objects are not cached by default
The AiService code still shows traces of having considered caching chatService / embeddingService, but the current implementation does not enable it.
This means get*Service() creates a concrete service instance per call by default.
AiServiceRegistry.get(id) throws directly on unknown ids
This makes the registry better suited as a formal multi-instance entry point, rather than a loose lookup tool that "might or might not have it".
PlatformType.getPlatform(...) is tolerant by design
This method falls back to OPENAI for unknown values, while DefaultAiServiceRegistry.resolvePlatformType(...) throws explicitly on unknown platforms.
Formal multi-instance configuration should rely on the latter's strict behavior.
10. Conclusion of this page
AI4J's current service entry system is explicit and layered:
AiServiceis the single-instance unified capability factory,AiServiceRegistryhandles formal multi-instance registration and routing, andFreeAiServiceonly plays the role of a compatibility shell. Understanding this entry chain matters more than memorizing any single provider's API, because the support matrix, extension cost, and upper-layer wiring all build on top of it.