Files
sousa-gecko/browser/components/aiwindow/models/SmartWindowNavigationInfo.sys.mjs
T
Rolf Rando 552d2eb443 Bug 2039131 - New table indicating state of semantic search db (current model, dimensions). Allow switching models used for embeddings via config and Nimbus r=places-reviewers,ai-platform-reviewers,mak,thasan
New table indicating embedding model of semantic search db (current model, dimensions). Goal is to eventually support running multiple simultaneous experiments using one or more transformer based embedding models in various markets, without concerns about user backlash due to performance.

EmbeddingGenerator is now the 'source of truth' for embedding dimension and model for common use cases.

We support a maximum of two live embeddings engines at a time via the EmbeddingGenerator class:
General - Used for Smart Window memories, Navigation and other future uses - Retain default to Transformer based embeddings
Places - Web history search - Defaults to static embeddings - Retain default to static embeddings

A Nimbus flag allows customization of model type (static vs contexual-transformer embeddings) for Places

For development purposes other models can be set via other configs, but these don't support Nimbus in this patch

background on the plan in this doc (slightly out of date)
https://docs.google.com/document/d/1oifFgmfSfX8HNep7Pb11-YkJsfNdGJ942SLmWrO_NDY/edit?tab=t.0#heading=h.qwjlbyd1kf1

Out of Scope this release:

    More than one embedding model at a time
    Non english / other languages

Differential Revision: https://phabricator.services.mozilla.com/D300202
2026-05-30 17:05:20 +00:00

102 lines
3.2 KiB
JavaScript

// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
/**
* Provides semantic search over Firefox preferences navigation entries to
* answer user queries about where to find Smart Window settings, Memories, etc.
*
* Navigation data is loaded from PreferencesNavMap.sys.mjs, which is
* auto-generated by browser/components/aiwindow/models/scripts/generate_prefs_nav_map.py.
* Run that script and commit the result whenever preferences.js or FTL files change.
*/
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
cosSim: "chrome://global/content/ml/NLPUtils.sys.mjs",
EmbeddingsGenerator: "chrome://global/content/ml/EmbeddingsGenerator.sys.mjs",
PREFERENCES_NAV_MAP:
"moz-src:///browser/components/aiwindow/models/PreferencesNavMap.sys.mjs",
});
export const NAV_TOP_K = 3;
export const NAV_SIMILARITY_THRESHOLD = 0.3;
/**
* Converts the flat nav map into an array of entries with embedding text.
*
* @returns {Array<{url, label, breadcrumb, description, embeddingText}>}
*/
function buildNavEntries() {
return Object.entries(lazy.PREFERENCES_NAV_MAP).map(([url, info]) => ({
url,
label: info.label,
breadcrumb: info.breadcrumb,
description: info.description,
embeddingText: [info.breadcrumb, info.description]
.filter(Boolean)
.join(". ")
.toLowerCase(),
}));
}
/**
* Provides semantic search over Firefox preferences navigation
* entries using embeddings to find settings pages relevant to a query.
*/
class NavigationInfoImpl {
#embeddingsGenerator = null;
#navEntries = null;
#navEmbeddings = null;
/**
* Returns navigation entries semantically similar to the query,
* ranked by cosine similarity.
*
* @param {string} query
* @param {number} [topK=3]
* @param {number} [similarityThreshold=NAV_SIMILARITY_THRESHOLD]
* @returns {Promise<Array<{url, label, breadcrumb, description, similarity}>>}
*/
async getRelevantNavigation(
query,
topK = NAV_TOP_K,
similarityThreshold = NAV_SIMILARITY_THRESHOLD
) {
if (!this.#navEntries) {
this.#navEntries = buildNavEntries();
}
if (!this.#embeddingsGenerator) {
this.#embeddingsGenerator = lazy.EmbeddingsGenerator.forGeneral();
}
if (!this.#navEmbeddings) {
const result = await this.#embeddingsGenerator.embedMany(
this.#navEntries.map(e => e.embeddingText)
);
this.#navEmbeddings = result.output ?? result;
}
const queryResult = await this.#embeddingsGenerator.embed(
query.toLowerCase()
);
let queryEmbedding = queryResult.output ?? queryResult;
if (Array.isArray(queryEmbedding) && queryEmbedding.length === 1) {
queryEmbedding = queryEmbedding[0];
}
return this.#navEmbeddings
.map((emb, idx) => ({
...this.#navEntries[idx],
similarity: lazy.cosSim(queryEmbedding, emb),
}))
.filter(e => e.similarity >= similarityThreshold)
.sort((a, b) => b.similarity - a.similarity)
.slice(0, topK)
.map(({ embeddingText: _omit, ...rest }) => rest);
}
}
export const SmartWindowNavigationInfo = new NavigationInfoImpl();