Skip to content

[ZEPPELIN-6411] Semantic search for Zeppelin#5218

Open
kkalyan wants to merge 8 commits intoapache:masterfrom
kkalyan:ZEPPELIN-6411-semantic-search
Open

[ZEPPELIN-6411] Semantic search for Zeppelin#5218
kkalyan wants to merge 8 commits intoapache:masterfrom
kkalyan:ZEPPELIN-6411-semantic-search

Conversation

@kkalyan
Copy link
Copy Markdown

@kkalyan kkalyan commented Apr 19, 2026

What is this PR for?

Added EmbeddingSearch — a new SearchService implementation that enables natural language search across Zeppelin notebooks using ONNX-based sentence embeddings (all-MiniLM-L6-v2).
Disabled by default, enabled with zeppelin.search.semantic.enable = true.

The problem:
Zeppelin's built-in search uses Lucene's keyword matching, which works well for exact terms but falls short for the way analysts actually search.
A user looking for "yesterday's spending" gets zero results — even though their notebooks contain SELECT sum(cost) WHERE date = current_date -
interval '1' day. The words don't match, so Lucene can't find it.

This PR adds EmbeddingSearch, an alternative SearchService that uses sentence embeddings (all-MiniLM-L6-v2 via ONNX Runtime) to match by meaning
instead of keywords. It runs entirely in-process with no external services required.

Beyond semantic matching, EmbeddingSearch addresses other gaps in notebook search:

  • Indexes paragraph output — table results and text output become searchable, not just the code
  • Extracts SQL table names — FROM/JOIN references are extracted and used to boost related paragraphs in a two-phase ranking
  • Strips interpreter prefixes — %spark.sql, %python etc. are removed so they don't pollute search results
  • Live indexing — new or updated paragraphs are searchable immediately, no restart needed

What type of PR is it?

Feature

Todos

  • EmbeddingSearch core implementation (ONNX inference, mean pooling, cosine similarity)
  • Table name extraction from SQL (FROM/JOIN regex) with two-phase search boosting
  • Paragraph output indexing (TABLE, TEXT results)
  • Versioned binary persistence (v3 format)
  • Live indexing (new paragraphs searchable immediately)
  • Angular UI: render search results with separate code/output/tables blocks
  • Classic UI: same improvements
  • 11 unit tests including semantic validation
  • Documentation

What is the Jira issue?

How should this be tested?

Automated tests:

# Embedding search tests (requires ~86MB model download, one-time)
ZEPPELIN_EMBEDDING_TEST=true mvn test -pl zeppelin-zengine -Dtest=EmbeddingSearchTest

# Verify no regressions to existing Lucene search
mvn test -pl zeppelin-zengine -Dtest=LuceneSearchTest

Manual testing:

1. Set zeppelin.search.semantic.enable = true in zeppelin-site.xml
2. Restart Zeppelin
3. Search for natural language queries like:
  - "yesterday's spending" (Lucene: 0 results → Semantic: finds spend queries)
  - "how much do drivers earn" (finds taxi tip analysis)
  - "late deliveries" (finds shipping performance queries)
  - "airport rides" (both work — keyword match exists)

Screenshots (if appropriate)

Semantic Search with New UI
image
Semantic Search with Classic UI
image

Questions:

  • Does the license files need to update?
  • Yes — NOTICE updated with ONNX Runtime (MIT) and DJL Tokenizers (Apache 2.0) attribution.
  • Is there breaking changes for older versions?
  • No. Disabled by default. Existing LuceneSearch behavior is unchanged.

@kkalyan kkalyan changed the title Zeppelin 6411 semantic search [feat] Semantic search for Zeppelin Apr 19, 2026
@kkalyan kkalyan changed the title [feat] Semantic search for Zeppelin [ZEPPELIN-6411] Semantic search for Zeppelin Apr 19, 2026
@jongyoul jongyoul requested a review from Copilot April 20, 2026 01:00
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces an optional semantic notebook search implementation (EmbeddingSearch) that uses ONNX-based sentence embeddings to match queries by meaning (plus output indexing and SQL table boosting), and updates both Classic and Angular UIs to render richer search results.

Changes:

  • Add EmbeddingSearch (ONNX Runtime + DJL tokenizer) with binary persistence and live indexing.
  • Add semantic-search config flag (zeppelin.search.semantic.enable) and wire the server to select Lucene vs semantic search.
  • Update Classic + Angular search result rendering (code/output/tables blocks) and adjust TypeScript typing/build settings.

Reviewed changes

Copilot reviewed 20 out of 20 changed files in this pull request and generated 13 comments.

Show a summary per file
File Description
zeppelin-zengine/src/main/java/org/apache/zeppelin/search/EmbeddingSearch.java New semantic search service (model download, embedding, indexing, persistence, query ranking).
zeppelin-zengine/src/test/java/org/apache/zeppelin/search/EmbeddingSearchTest.java New gated tests for semantic indexing/query behavior.
zeppelin-zengine/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java Add config accessor + conf var for semantic search enablement.
zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java Bind EmbeddingSearch when semantic search is enabled.
zeppelin-zengine/pom.xml Add ONNX Runtime + DJL tokenizers dependencies.
zeppelin-web/src/app/search/result-list.html Classic UI search results layout changes.
zeppelin-web/src/app/search/result-list.controller.js Classic UI result parsing for code/output/tables + language badge.
zeppelin-web-angular/src/app/pages/workspace/notebook-search/result-item/result-item.component.ts Angular UI result parsing + simplified rendering (no Monaco/highlighting).
zeppelin-web-angular/src/app/pages/workspace/notebook-search/result-item/result-item.component.html Angular UI template to show code/output/tables and badge.
zeppelin-web-angular/src/app/pages/workspace/notebook-search/result-item/result-item.component.less Angular UI styling for new result layout.
zeppelin-web-angular/tsconfig.base.json TS compiler option changes.
zeppelin-web-angular/projects/zeppelin-sdk/tsconfig.json TS compiler option changes for SDK build.
zeppelin-web-angular/src/app/utility/get-keyword-positions.ts Tighten type for positions.
zeppelin-web-angular/src/app/share/run-scripts/run-scripts.directive.ts Type annotations / casts for script execution logic.
zeppelin-web-angular/src/app/services/save-as.service.ts Type annotation for binaryData.
zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/code-editor/code-editor.component.ts Type annotation for newDecorations.
zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.component.ts Safer optional chaining on permissions access.
zeppelin-web-angular/src/app/pages/workspace/credential/credential.component.ts Type cast for destructuring credentials.
docs/embedding-search.md New documentation for semantic search design and usage.
NOTICE Add attributions for ONNX Runtime and DJL tokenizers.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread zeppelin-web-angular/projects/zeppelin-sdk/tsconfig.json
Comment thread zeppelin-web/src/app/search/result-list.html
Comment thread zeppelin-web/src/app/search/result-list.controller.js
Comment thread zeppelin-web-angular/tsconfig.base.json
Comment thread zeppelin-zengine/src/main/java/org/apache/zeppelin/search/EmbeddingSearch.java Outdated
Comment thread zeppelin-zengine/src/main/java/org/apache/zeppelin/search/EmbeddingSearch.java Outdated
@jongyoul
Copy link
Copy Markdown
Member

@kkalyan Thank you for the contribution. Could you please check the CI first and fix it? Moreover, I will review it but I have a simple question. Do we download the model when we start the server?

@kkalyan
Copy link
Copy Markdown
Author

kkalyan commented Apr 20, 2026

@kkalyan Thank you for the contribution. Could you please check the CI first and fix it? Moreover, I will review it but I have a simple question. Do we download the model when we start the server?

Hi @jongyoul - Yes, the model (~86MB) is downloaded on first start when zeppelin.search.semantic.enable=true (disabled by default). It's cached at {zeppelin.search.index.path}/models/all-MiniLM-L6-v2/ and reused on subsequent starts. I'll fix the CI issues — ESLint brace-style in the classic UI controller and the missing ASF license header on the docs file.

@jongyoul
Copy link
Copy Markdown
Member

I think it's better to have it by default as we need to assume the environment not to download it dynamically. Moreover, don't we need to wait until it's downloaded when starting the server?

@kkalyan
Copy link
Copy Markdown
Author

kkalyan commented Apr 20, 2026

I think it's better to have it by default as we need to assume the environment not to download it dynamically. Moreover, don't we need to wait until it's downloaded when starting the server?

Thank you @jongyoul. You're right — downloading at startup is problematic for production/air-gapped environments.
A couple of questions so I get this right:

  1. Should the model be bundled in the distribution (~86MB), or would a configurable local path (zeppelin.search.model.path) where admins
    pre-stage the model be better?
  2. If the model isn't found, should Zeppelin fall back to LuceneSearch with a warning, or fail fast?
  3. Would an optional helper script (bin/install-search-model.sh) be acceptable for the download convenience?

Happy to implement whichever direction you prefer.

@hyunw9
Copy link
Copy Markdown
Contributor

hyunw9 commented Apr 22, 2026

This looks like a useful feature.
I have a couple of questions on this PR :

  1. Model version pinning: The model URL points to resolve/main/, so the weights could silently change if the upstream model is updated. Since embedding_index.bin doesn't seem to store which model version generated the vectors, how would we detect a mismatch between an existing index and the current model? Pinning to a specific commit hash might be safer.

  2. JNA compatibility: Looks like the DJL tokenizer's JNA dependency is excluded and the runtime falls back to zeppelin-server's JNA 4.1.0. This seems to work for now, but could this break if DJL needs to be upgraded to a version that requires a newer JNA? Just wondering if this is something we should keep in mind.

Kalyan Kanuri added 8 commits April 22, 2026 08:31
Add EmbeddingSearch — a new SearchService implementation that enables
natural language search across notebooks using ONNX-based sentence
embeddings (all-MiniLM-L6-v2). Disabled by default, enabled with:

  zeppelin.search.semantic.enable = true

Key improvements over keyword search:
- Understands meaning, not just exact keywords
- Indexes paragraph output (table data, text results)
- Strips interpreter prefixes for cleaner matching
- Zero external services — runs entirely in-process

JIRA: https://issues.apache.org/jira/browse/ZEPPELIN-6411
Add EmbeddingSearch — a new SearchService implementation that enables
natural language search across notebooks using ONNX-based sentence
embeddings (all-MiniLM-L6-v2). Disabled by default, enabled with:

  zeppelin.search.semantic.enable = true

Key improvements over keyword search:
- Understands meaning, not just exact keywords
- Indexes paragraph output (table data, text results)
- Extracts and boosts SQL table names (FROM/JOIN)
- Two-phase search: discover relevant tables, then boost matches
- Strips interpreter prefixes for cleaner matching
- Zero external services — runs entirely in-process

Frontend improvements (both Angular and Classic UI):
- Search results show SQL code, output data, and table names
  in separate styled blocks instead of a single code editor
- Language badges (sql/python/md) on search result cards

New files:
- EmbeddingSearch.java: core implementation
- EmbeddingSearchTest.java: 11 tests including semantic validation
- docs/embedding-search.md: architecture documentation

JIRA: https://issues.apache.org/jira/browse/ZEPPELIN-6411
Add two-phase search, table extraction, output indexing,
frontend changes, and live indexing test to documentation.

JIRA: https://issues.apache.org/jira/browse/ZEPPELIN-6411
Expand single-line if blocks in detectLang() to satisfy ESLint
brace-style rule, and add ASF license header to embedding-search.md
to pass Apache RAT audit.

JIRA: https://issues.apache.org/jira/browse/ZEPPELIN-6411
- Fix table boosting bug: results now re-sorted by boosted score
- Add connect/read timeouts to model download (30s/60s)
- Atomic index persistence: write to temp file, then rename
- Strip <B> highlight tags from LuceneSearch results in both UIs
- Hide language badge for unknown content types (return '' not 'text')
- Remove unused SNIPPET_LENGTH constant
- Share model directory across test methods to avoid 86MB re-download

JIRA: https://issues.apache.org/jira/browse/ZEPPELIN-6411
Pin all-MiniLM-L6-v2 model and tokenizer URLs to commit
c9745ed1d9f207416be6d2e6f8de32d1f16199bf instead of resolve/main/
to prevent silent model weight changes from upstream updates.

JIRA: https://issues.apache.org/jira/browse/ZEPPELIN-6411
Add bin/install-search-model.sh that downloads the ONNX model and
tokenizer from a pinned HuggingFace commit (c9745ed1d9f2). Remove
auto-download from EmbeddingSearch.initModel() — server now fails
fast with a clear error if the model is not pre-installed.

This avoids blocking server startup on network I/O and eliminates
the risk of silent model version drift.

JIRA: https://issues.apache.org/jira/browse/ZEPPELIN-6411
@kkalyan kkalyan force-pushed the ZEPPELIN-6411-semantic-search branch from a9a11e3 to 6c63b5f Compare April 22, 2026 15:32
@kkalyan
Copy link
Copy Markdown
Author

kkalyan commented Apr 22, 2026

@jongyoul:

Updated in the latest push:

  • Added bin/install-search-model.sh — downloads the model (~86MB) and tokenizer from a pinned HuggingFace commit. Run once before enabling semantic
    search.
  • Removed all runtime download logic from EmbeddingSearch. Server now fails fast with a clear error message if the model isn't pre-installed.
  • No network I/O at startup.

Usage:

bin/install-search-model.sh            # uses default /tmp/zeppelin-index
bin/install-search-model.sh /my/path   # custom index path

@kkalyan
Copy link
Copy Markdown
Author

kkalyan commented Apr 22, 2026

Thanks for the review @hyunw9!

  1. Model version pinning: Good catch. The URLs are now pinned to a specific HuggingFace commit (c9745ed1d9f2) in bin/install-search-model.sh, and the
    runtime download logic has been removed entirely from EmbeddingSearch.java. The model is pre-installed via the helper script, so there's no risk of
    silent drift. Upgrading the model becomes an explicit, reviewable change to the script.

  2. JNA compatibility: You're right to flag this. DJL tokenizers 0.28.0 pulls JNA 5.14.0, which we exclude to avoid conflicting with Zeppelin's JNA
    4.1.0 (from Hadoop). This works because DJL only uses basic Native.load() which is stable across JNA versions. If a future DJL upgrade requires newer
    JNA APIs, it would fail immediately at startup (tokenizer init), not silently. At that point the fix would be upgrading Zeppelin's JNA globally —
    same pattern other modules follow. Added a comment in the pom.xml exclusion to document this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants