Files
brave-core/test
Anthony Tseng 639a488d7e Enable chromium semantic history search with local embedder (#32689)
* Enable chromium semantic history search with our own embedder

* Replace history_embeddings patch with template override

Replace the trivial patch that commented out scored_url_rows.clear()
with a BraveHistoryEmbeddingsService template subclass (per
docs/gni_sources.md) that overrides OnPassageVisibilityCalculated to
always synthesize passing visibility scores (1.0), bypassing upstream's
PageContentAnnotationsService-based filtering which Brave doesn't use.

The template pattern avoids circular deps between
//brave/browser/history_embeddings and //chrome/browser:browser —
the template is instantiated at the factory site which already has
the required dep.

* Add unit tests for BraveHistoryEmbeddingsService

Run all upstream HistoryEmbeddingsService unit tests with the Brave
template subclass via chromium_src override, ensuring our visibility
bypass doesn't break any existing behavior. Add a Brave-specific test
verifying search results are preserved when the visibility model is
unavailable (upstream clears them).

* Add BraveEmbedder::Observer to notify when idle

Replace the JS-side connection tracking (boundCount /
notifyPassageEmbedderIdle) with a C++ observer pattern.
BravePassageEmbeddingsServiceController observes the embedder
and calls NotifyPassageEmbedderIdle via mojom when all jobs
complete, avoiding the renderer round-trip.

* Run LocalAIService BackgroundWebContents on guest OTR profile

The WASM embedding model doesn't need user profile data, so run
BackgroundWebContents on the guest OTR profile to isolate it from
user data.

- Make BackgroundWebContentsFactory async so it can call
  CreateProfileAsync to obtain the guest OTR profile, then
  construct BackgroundWebContentsImpl in the callback
- Inject the factory via SetBackgroundWebContentsFactory from
  browser_context_keyed_service_factories.cc (which has
  ProfileManager access) to avoid adding sources to sources.gni
- Add a static WebContents bind callback registry on
  LocalAIServiceFactory so UntrustedLocalAIUI::BindInterface can
  route mojo requests from the guest OTR WebContents back to the
  owning LocalAIService without profile-based lookup
- Simplify BravePassageEmbeddingsServiceController to use a single
  shared BraveEmbedder instead of per-profile map

* Add BravePassageEmbeddingsService in-process mojo impl

New BravePassageEmbeddingsService implements upstream
passage_embeddings::mojom::PassageEmbeddingsService in the browser
process. It hosts the guest-OTR BackgroundWebContents that runs the
WASM EmbeddingGemma renderer and owns the PassageEmbedderFactory
registration path (via local_ai::mojom::LocalAIService, which it also
implements so UntrustedLocalAIUI can route the renderer binding).

An internal BraveBatchPassageEmbedder translates upstream's batch
mojom (array<string> + priority) to the renderer's one-passage-at-a-
time interface, processing passages sequentially so callbacks resolve
with results in order.

Not yet wired to any caller — the follow-up commit switches
BravePassageEmbeddingsServiceController over to using this service
instead of the per-profile LocalAIService keyed service.

* Wire controller to launch in-process BravePassageEmbeddingsService

BravePassageEmbeddingsServiceController now mirrors
ChromePassageEmbeddingsServiceController: MaybeLaunchService()
constructs a BravePassageEmbeddingsService and binds the base class's
service_remote_ to it via an in-process mojo pipe; ResetServiceRemote()
tears it down. The constructor fires EmbedderMetadataUpdated once,
since our metadata is static (EmbeddingGemma version 1, 768-dim, 0.45)
and doesn't arrive through optimization_guide.

GetEmbeddings() override replaces the base class's file-loading flow —
we have no tflite/sentencepiece files to open — and calls
service_remote_->LoadModels() with default-constructed params
(BravePassageEmbeddingsService ignores them) before routing
GenerateEmbeddings to the bound embedder_remote_. Upstream's
SchedulingEmbedder drives the job queue end-to-end: priority re-sort
between passages, partial-progress resumption, and performance-scenario
awareness all come along for free.

The chromium_src include shim virtualizes EmbedderReady,
GetEmbedderMetadata, and GetEmbeddings via `#define X virtual X` so
the subclass can override them, and injects
`friend class BravePassageEmbeddingsServiceController` via an
EmbedderRunning macro anchor so the override can reach private
members (observer_list_ to fire the initial metadata notification,
embedder_remote_ to drive LoadModels without the upstream
model-info path).

PageEmbeddingsServiceFactory and HistoryEmbeddingsServiceFactory
overrides drop the GetBraveEmbedder(profile) hook; both now use the
base class's GetEmbedder() which returns the SchedulingEmbedder.

* Delete BraveEmbedder, LocalAIService keyed service, and orphan code

BravePassageEmbeddingsService now owns everything the old setup needed:
the background WebContents, the PassageEmbedderFactory registration,
and the static WebContents→bind-callback registry used by
UntrustedLocalAIUI::BindInterface. That removes the last reason to
keep the LocalAIService keyed service and its factory around.

Removed:
- brave_embedder.{h,cc,_unittest.cc} — replaced by upstream's
  SchedulingEmbedder + BravePassageEmbeddingsService.
- local_ai_service.{h,cc,_unittest.cc} — merged into the new service.
- brave/browser/local_ai/ entirely — factory and bind registry
  relocated to BravePassageEmbeddingsService static methods.
- LocalAIService keyed service registration from
  browser_context_keyed_service_factories.cc.
- GetPassageEmbedder / NotifyPassageEmbedderIdle from local_ai.mojom;
  those flows are now handled via the upstream PassageEmbeddingsService
  mojom and the controller's service_remote_ idle handler.

UntrustedLocalAIUI now routes binds through
BravePassageEmbeddingsService::BindForWebContents. BUILD.gn and
sources.gni are adjusted accordingly.

* Use direct BindPassageEmbedder instead of mojo LoadModels

Upstream's mojom::PassageEmbeddingsService::LoadModels requires
non-null ReadOnlyFile fields for the tflite model and sentencepiece
tokenizer; mojo's serializer DCHECKs that they are physical files
(IsPhysicalFile), which rules out /dev/null or NUL as placeholders.
Since we don't have upstream's model files at all — our renderer
loads its own EmbeddingGemma — there's no way to satisfy that
contract on the caller side.

Keep BravePassageEmbeddingsService's mojom interface for completeness
but expose a direct C++ BindPassageEmbedder(receiver, callback) entry
point and have the controller call it directly. The service_remote_
inherited from the base class is left unbound, and the embedder_remote_
idle handler now fires ResetServiceRemote so the WASM renderer is torn
down on idle.

* Add unit tests for BravePassageEmbeddingsService

Covers the core flows: BindPassageEmbedder creating the background
contents, the barrier-closure that waits for both component-installed
model files and the renderer factory registration, the batch embedder
fanning out passages to the renderer one at a time, Init failure
tearing things down, renderer crash triggering close, and the static
WebContents→bind-callback registry used by UntrustedLocalAIUI.
2026-04-21 03:48:17 +01:00
..