Model catalog boundary
Model limits, modalities, and capabilities now belong to the exact-offering catalog, keyed by(driver, wire model). Connection presets and LLMConfig contain only transport, selection,
and request-default data.
This is a breaking removal. Delete contextLength, maxOutputLength, and pricing from
LLMConfig, DSNs, framework configuration, and preset YAML. Use
ModelCatalog::discover()->find($driver, $model) when model facts are needed. Unknown exact
offerings return an explicit profile whose facts are unknown; Polyglot does not infer support
from a provider name or model-name regex.
Inference catalog records do not contain pricing. Embedding and Decision model
catalogs may carry a reviewed pricing snapshot for estimates; callers can still
provide an explicit rate instead.
Embedding and Decision model catalogs
Embeddings and structured decisions now have separate, domain-owned exact model catalogs:dimensions, the
defaultDimensions alias, and maxInputs from EmbeddingsConfig, embedding
DSNs and presets, and Laravel/Symfony embedding connection configuration. Put a
caller-selected width in request options, such as
withOptions(['dimensions' => 256]). Read reviewed defaults and provider limits
from EmbeddingModel.
The old preset-level maxInputs guards are gone. Inject an embedding catalog at
the composition root to enforce a known exact-model input count before HTTP:
models:, embedding requests perform no catalog I/O or catalog-based
check. Unknown exact routes remain executable. Request model overrides now drive
one effective model for both catalog lookup and provider URL/body rendering.
DecisionRuntime::__construct(), fromConfig(), and fromProvider() accept an
optional Decision catalog. DecisionRequest now exposes modelProfile() and
withModelProfile(); the profile is transient and excluded from portable
serialization. Embedding requests follow the same profile rule.
Embedding usage no longer converts an omitted provider count to zero.
EmbeddingsUsage::$inputTokens, input(), and total() are nullable, and the
embedding cost calculator returns ?Cost. Initialize known accumulation with
EmbeddingsUsage::zero(); none() now means unknown. Unknown usage plus a
nonzero rate yields no estimate, while explicit zero usage or an explicit zero
rate still yields a known zero cost.
Decision catalog records for jev-1.13.0 and jev-latest are independent
reviewed snapshots. The 64,000 request and 32,000 state-plus-question values are
planning ceilings, not tokenizer-exact runtime checks. Pin a version when stable
constraints matter, and price the exact model returned in DecisionResponse
instead of inheriting an old alias price.
Individual model files
Runtime discovery no longer readsconfig/llm/models.json. Move each entry from its models
list into config/llm/models/<encoded-driver>/<encoded-model>.yaml, wrapped in schemaVersion: 1,
the original catalog version, and profile: <entry>. Encode driver/model filename components
with ModelRecordDirectory::relativePath(new ModelKey($driver, $model)) so IDs containing /
remain one model filename. See Model Catalog for a complete example.
Replace explicit runtime ModelCatalog::fromFile($jsonPath) calls with
ModelCatalog::fromPaths($modelsDirectory). JSON bulk loading remains available for explicit
offline maintenance; it is not a runtime fallback. Keep whole-record overrides complete enough
for the facts you want to retain. Unknown keys and malformed values now fail instead of being
silently discarded. Numeric limits still accept null for unknown; status fields require strings.
Ordinary inference calls do not require a catalog. A runtime constructed without models: sends
requests directly through its driver and does not read model records. When an application wants
local capability enforcement or model metadata, create one catalog at the composition root and
reuse it across runtimes or inference objects; exact lookup then reads and caches only the selected
record.
Reasoning records
Reasoning capability data now follows the same exact lookup. ReplaceBundledInferenceDrivers::reasoningCapabilities($driver, $model) with:
InferenceRequest::withReasoningCapabilities(). A missing mapping
is reported as missing translation data, not as proven model incompatibility.
Semantic fallback is disabled by default. Set allowLossyFallback: true on LLMConfig only when the
application accepts an observable change such as JSON Schema to JSON Object or a lossy reasoning
effort mapping. Laravel and Symfony connection configuration expose the same setting as
llm.connections.<name>.allow_lossy_fallback. Every approved change is recorded on the effective
request and emitted with InferenceRequested; provider adapters never silently degrade a request.
Failures now distinguish four cases:
- an adapter cannot encode the requested protocol operation;
- a processor needs a translation fact that was not supplied;
- an exact supplied fact explicitly marks the operation unsupported;
- a known fallback exists but the caller has not enabled lossy fallback.
CanDescribeCapabilities,
DriverCapabilities, and SpecifiedInferenceDriver were removed. A custom spec subclass now
extends BaseInferenceRequestDriver:
LLMConfig::fromArray() hydrates the fields owned by LLMConfig and ignores unrelated input
keys. There is no Agents-specific migration or compatibility alias.
Default registry constructors
The staticBundledInferenceDrivers, BundledEmbeddingsDrivers, BundledHttpDrivers, and
BundledHttpPools classes were removed. Use InferenceDriverRegistry::default(),
EmbeddingsDriverRegistry::default(), HttpDriverRegistry::default(), and
HttpPoolRegistry::default() respectively. Each memoized default is immutable; its with*()
methods return an isolated derived registry. Use make() when an empty registry is required.
Custom Inference Drivers in v2.7
Only driver authors are affected. Preset names,Inference, PendingInference,
InferenceStream, InferenceResponse, Embeddings, PendingEmbeddings, and LLMConfig are
unchanged, and so is every interface under Cognesy\Polyglot\Inference\Contracts.
All 26 provider driver shells under Cognesy\Polyglot\Inference\Drivers\ were removed:
A21Driver, CerebrasDriver, DeepseekDriver, FireworksDriver, GlmDriver, GroqDriver,
InceptionDriver, MetaDriver, MinimaxiDriver, MistralDriver, OpenAIDriver,
OpenAICompatibleDriver, OpenRouterDriver, PerplexityDriver, QwenDriver, SambaNovaDriver,
XAiDriver, AnthropicDriver, AzureDriver, BedrockOpenAIDriver, CohereV2Driver,
GeminiDriver, GeminiOAIDriver, HuggingFaceDriver, OpenAIResponsesDriver, and
OpenResponsesDriver. Their only content was composing collaborators, so all bundled inference
registrations — including native-protocol and bespoke-endpoint providers — are now
InferenceDriverSpec rows in InferenceDriverRegistry, all served by one
BaseInferenceRequestDriver. The embeddings driver of the same short name,
Embeddings\Drivers\OpenAI\OpenAIDriver, is untouched.
Replacing a subclass of a bundled driver
ExtendBaseInferenceRequestDriver and name your subclass in the spec’s driverClass. It still
receives the five collaborators assembled for it:
bodyFormat,
requestAdapter, responseAdapter, usageFormat, or messageFormat to the spec. Each
defaults to the OpenAI implementation.
Replacing a capabilities() override
If the application needs capability inspection or local enforcement, declare the exact
(driver, model) offering in a model catalog and inject that catalog into InferenceRuntime.
Ordinary custom-driver execution needs no catalog. Capability inspection no longer constructs or
interrogates a driver.
InferenceDriverRegistry::withDriver() still accepts a class-string or a callable, so drivers
registered that way need no change.
Moved classes
Cognesy\Polyglot\Inference\Contracts\MessageMapper is now
Cognesy\Polyglot\Inference\Drivers\MessageMapper — it is a driver helper, not a contract.
Update the import; the class is otherwise unchanged. This one has no alias.
Five classes that neither subsystem owns moved under Cognesy\Polyglot\Support\:
Polyglot 2.7 temporarily aliased the five old names. Polyglot 2.10 removes that
autoloading layer completely: update every import to the new FQCN in the table.
The old names no longer resolve.
Removed dead classes
Inference\Enums\InferenceContentType, Inference\Collections\InferenceResponseList, and
Embeddings\Traits\HasFinders were deleted. None had a usage anywhere in the repository.
Custom Embeddings Drivers in v2.7
CanHandleVectorization now returns the domain response directly. PHP cannot provide a
compatibility shim for this interface return-type change, so custom implementations must be
updated together with the v2.7 package upgrade.
handle(). The separate fromData() method
is no longer part of the driver contract. Drivers that extend BaseEmbedDriver inherit the
new behavior unless they override handle().
Migrating from v1 to v2
Polyglot 2.0 is centered around explicit request fields. The main migration points are:- remove old output mode usage
- set
responseFormatfor native JSON or JSON schema - set
toolsandtoolChoicefor tool calling - use
stream()->deltas()for streaming
Response Model
Polyglot is now explicitly the raw inference layer.InferenceResponseis the final raw provider response- streaming yields
PartialInferenceDelta - structured value ownership belongs to higher-level packages such as Instructor
Before
After
Streaming Migration
Update old streaming code like this:- replace partial-response iteration with
stream()->deltas() - assemble final raw output with
final() - move partial structured parsing to Instructor or your own delta accumulator