normalized method

ActiveModelParams normalized()

The same request with every value the engines normalise away already applied, so two requests that build a bit-identical engine compare equal.

Comparing the RAW arguments caused spurious rebuilds — and a rebuild here unloads and reloads multi-gigabyte weights, so it is expensive and very visible. Three knobs are normalised downstream:

  • maxNumImages is maxNumImages ?? 1 when vision is on and discarded entirely when it is off, in both engines. So null and 1 are the same request, and with supportImage: false the value is dead.
  • preferredVisionBackend / preferredAudioBackend default to CPU (encoderBackendWireName(null) == 'cpu'), which the facade's own doc tells callers — so writing the value explicitly followed the documentation into a reload.

maxTokens is NOT normalised here: the .litertlm engine clamps it up to 1024 but MediaPipe does not, so the effective value depends on which engine canHandle picks and core cannot know it. A caller who asks for 512 and then for the default 1024 still rebuilds on the litertlm path even though both engines end up at 1024. Fixing that means moving the clamp out of the engine and into core; it is left alone rather than guessed at.

Implementation

ActiveModelParams normalized() => ActiveModelParams(
  maxTokens: maxTokens,
  preferredBackend: preferredBackend,
  preferredVisionBackend: preferredVisionBackend ?? PreferredBackend.cpu,
  preferredAudioBackend: preferredAudioBackend ?? PreferredBackend.cpu,
  supportImage: supportImage,
  supportAudio: supportAudio,
  maxNumImages: supportImage ? (maxNumImages ?? 1) : null,
  enableSpeculativeDecoding: enableSpeculativeDecoding,
  maxConcurrentSessions: maxConcurrentSessions,
  loraRanks: loraRanks,
);