model => the only accepted dimension (the model's native size; reduced sizes are not * supported yet). * * @return array> */ public static function supported(): array { $models = []; foreach (RemoteEmbedder::MODELS as $model => $dimension) { $models[$model] = $dimension; } return ['openai' => $models]; } private function __construct( public readonly int $siteId, public readonly string $mode, public readonly ?string $provider, public readonly ?string $model, public readonly ?int $dimension, public readonly int $version, public readonly ?int $connectionId, /** True when stored data existed but could not be trusted (the lexical default is used meanwhile). */ public readonly bool $corrupt = false, ) { } public static function lexical(int $siteId, int $version = 0, bool $corrupt = false): self { return new self($siteId, self::MODE_LEXICAL, null, null, null, max(0, $version), null, $corrupt); } /** @throws InvalidEmbeddingConfig */ public static function semantic(int $siteId, string $provider, string $model, int $dimension, ?int $connectionId = null, int $version = 1): self { $models = self::supported()[$provider] ?? null; if ($models === null) { throw new InvalidEmbeddingConfig('unsupported_provider'); } if (!isset($models[$model])) { throw new InvalidEmbeddingConfig('unsupported_model'); } if ($models[$model] !== $dimension) { throw new InvalidEmbeddingConfig('unsupported_dimension'); } if ($connectionId !== null && $connectionId < 1) { throw new InvalidEmbeddingConfig('invalid_connection'); } if ($siteId < 1 || $version < 0) { throw new InvalidEmbeddingConfig('invalid_site'); } return new self($siteId, self::MODE_SEMANTIC, $provider, $model, $dimension, $version, $connectionId); } public function isSemantic(): bool { return $this->mode === self::MODE_SEMANTIC; } /** What retrieval would actually use: lexical unless semantic is both configured and allowed to run. */ public function effectiveMode(): string { return $this->isSemantic() && self::SEMANTIC_ENABLED ? self::MODE_SEMANTIC : self::MODE_LEXICAL; } /** The model id recorded on index versions, identical to RemoteEmbedder::model(). Null for lexical. */ public function modelId(): ?string { return $this->isSemantic() ? $this->provider . ':' . $this->model . ':' . $this->dimension : null; } /** * Identity of the index a configuration needs: same provider, model and dimension give the same fingerprint, so an index * built for one configuration is never used for another. The connection and the configuration version are deliberately not * part of it (rotating a key does not invalidate an index). Null for lexical. */ public function fingerprint(): ?string { return $this->isSemantic() ? self::fingerprintOf((string) $this->provider, (string) $this->model, (int) $this->dimension) : null; } public static function fingerprintOf(string $provider, string $model, int $dimension): string { return hash('sha256', 'aiwend-embedding-index-v1|' . $provider . '|' . $model . '|' . $dimension); } /** @return array the stored form (no secrets) */ public function toArray(): array { return ['v' => self::SCHEMA, 'site_id' => $this->siteId, 'mode' => $this->mode, 'provider' => $this->provider, 'model' => $this->model, 'dimension' => $this->dimension, 'version' => $this->version, 'connection_id' => $this->connectionId]; } }