Skip to content

ref(kb): H04 move search lifecycle into handle (#511)#670

Merged
qinxuye merged 12 commits into
xorbitsai:mainfrom
rogercloud:ref/kb-h04-search-lifecycle
Jun 25, 2026
Merged

ref(kb): H04 move search lifecycle into handle (#511)#670
qinxuye merged 12 commits into
xorbitsai:mainfrom
rogercloud:ref/kb-h04-search-lifecycle

Conversation

@rogercloud

Copy link
Copy Markdown
Collaborator

Summary

Part of epic #494, Phase 2 ("KBCollectionHandle Replacement") — follows the merged H01/H02/H03 (#648/#656/#658).

Moves dense/sparse/hybrid search execution mechanics + response-model construction into LanceDBCollectionHandle, re-routes the legacy-step compatibility facade through the coordinator-opened handle, and retires the now-dead engine layer. Behavior-preserving: public functions, import paths, rerank, and pipeline orchestration are unchanged.

Closes #511.

What changed

  • Handle owns search now (kb/collection_handle.py): added search_dense/search_sparse/search_hybrid (+ async dense/sparse and a new async hybrid) that build DenseSearchResponse/SparseSearchResponse/HybridSearchResponse directly. A coarse capabilities.supports_search guard sits before the try/except and degrades to a failed response with a SEARCH_NOT_SUPPORTED warning (never raises). A shared synchronous _fuse_hybrid helper backs both hybrid paths.
  • Facade routes through the coordinator (kb/legacy_step_compatibility.py): search_* (+ async) open a READ handle (hide_missing=True) and delegate to handle.search_*. Sync via open_collection_sync, async via await coordinator.open_collection(...) (no separate event loop). _open_collection_handle gained an access_mode parameter (defaults to WRITE; parse/chunk callers unchanged).
  • Dead engine layer retired: deleted _search_*_impl, the retrieval/search_engine.py dense engine (file removed), and retrieval_compatibility engine wrappers. Kept _rrf_fusion/_linear_fusion (shared with the pipeline), format_search_results_for_llm, and all public search_* entry points. The public search_dense() input validation (DocumentValidationError) was lifted to its boundary since the impl is gone.

Behavior preserved (verified byte-identical)

Dense score 1/(1+distance); sparse TF-IDF norm score/(1+score); substring-fallback score 1.0; warning codes READONLY_MODE/FTS_INDEX_MISSING/FTS_FALLBACK/FTS_SEARCH_FAILED/DENSE_SEARCH_FAILED; RRF + linear fusion; index-status mapping; hybrid top_k*2 over-fetch and score/rank attachment; collection isolation + user/admin filtering. Issue #72 collection-isolation coverage was ported to the handle tests at stronger strength (exact collection-filter equality).

Testing

tests/core/tools/core/RAG_tools/ — 1556 passed, 1 skipped, 1 pre-existing unrelated failure (version_management/test_promote_version_main.py::test_default_lancedb_dir_when_missing_env, an env-path assertion that also fails on the base). New handle search unit tests + a facade routing test added; the retrieval suite was migrated to exercise the handle seam without weakening assertions. ruff and mypy src/xagent clean on changed files.

https://claude.ai/code/session_01LcfvwrLffsY8iGF2XgbhFu

Re-route the legacy-step compatibility facade's search_dense/search_sparse/
search_hybrid (+ async dense/sparse) to open a READ collection handle via the
coordinator and delegate to handle.search_*, instead of the old _search_*_impl
free functions. Lift the public search_dense() DocumentValidationError input
validation to its boundary (the routed path no longer passes through the impl).
Migrate the retrieval unit tests to the new handle seam (shared conftest
fixtures) without weakening any behavioral assertion.

Claude-Session: https://claude.ai/code/session_01LcfvwrLffsY8iGF2XgbhFu
…ai#511)

- Delete search_engine.py (4 dead functions: search_dense_engine,
  _search_dense_engine_impl, search_dense_engine_async,
  _search_dense_engine_async_impl)
- Delete _search_dense_impl/_search_dense_async_impl from search_dense.py;
  drop now-unused search_dense_engine import
- Delete _search_sparse_impl/_search_sparse_async_impl/_substring_fallback/
  _substring_fallback_async/_build_sparse_response from search_sparse.py
- Delete _search_hybrid_impl from search_hybrid.py; drop private impl imports
- Delete search_dense_engine/search_dense_engine_async facade methods from
  retrieval_compatibility.py; clean up now-unused imports

Test changes:
- Delete TestSearchDenseEngine + TestSearchDenseIntegration engine tests
  from test_search_dense.py (behavior covered by handle tests)
- Port Issue xorbitsai#72 exact collection-filter equality assertions to
  test_collection_handle_search.py (new tests verify FilterCondition(
  field=collection, operator=EQ) reaches store)
- Remove all facade.search_dense_engine tests from
  test_retrieval_helper_compatibility.py; keep surface-import + LLM-format tests
- Update test_multitenancy.py to call search_dense() instead of
  search_dense_engine() (response object instead of tuple)
- Update test_search_sparse.py substring-fallback test to call via handle method

Claude-Session: https://claude.ai/code/session_01LcfvwrLffsY8iGF2XgbhFu

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the RAG search data-plane by moving dense, sparse, and hybrid search implementations from free functions into the LanceDBCollectionHandle class, with public search functions now delegating to the KB coordinator facade. The review feedback identifies several critical issues: an unnecessary database fetch in _substring_fallback_async due to a missing outer loop break, potential crashes in both sync and async substring fallbacks when non-dictionary filters are passed, and potential KeyErrors in _dense_engine when accessing optional fields directly instead of using .get().

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/xagent/core/tools/core/RAG_tools/kb/collection_handle.py
Comment thread src/xagent/core/tools/core/RAG_tools/kb/collection_handle.py Outdated
Comment thread src/xagent/core/tools/core/RAG_tools/kb/collection_handle.py Outdated
Comment thread src/xagent/core/tools/core/RAG_tools/kb/collection_handle.py
Comment thread src/xagent/core/tools/core/RAG_tools/kb/collection_handle.py
…tsai#511)

Address defensive-hardening review findings (verbatim pre-existing
behavior preserved from the refactored search helpers):
- guard substring-fallback filter handling with isinstance(dict) checks
  so non-dict filters no longer raise AttributeError/TypeError
- add the missing outer-loop break in the async substring fallback so it
  stops fetching batches once top_k is reached, matching the sync path
- read parse_hash/created_at via row.get() in the sync dense engine to
  match the async engine and avoid KeyError on nullable columns

Claude-Session: https://claude.ai/code/session_01LcfvwrLffsY8iGF2XgbhFu
@rogercloud

Copy link
Copy Markdown
Collaborator Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the RAG search data-plane by consolidating dense, sparse, and hybrid search logic (both sync and async) directly into the LanceDBCollectionHandle and updating legacy compatibility layers to delegate to it. Feedback on these changes highlights several critical improvements: supporting non-dictionary filter types in the dense search engines for consistency with sparse search, correctly parsing legacy filter operators in the substring fallback method to prevent zero-result failures, wrapping the optional PyArrow dependency in a try-except block to avoid import-time errors, and defensively clamping distances to prevent potential division-by-zero errors or invalid scores.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/xagent/core/tools/core/RAG_tools/kb/collection_handle.py
Comment thread src/xagent/core/tools/core/RAG_tools/kb/collection_handle.py
Comment thread src/xagent/core/tools/core/RAG_tools/kb/collection_handle.py
Comment thread src/xagent/core/tools/core/RAG_tools/kb/collection_handle.py Outdated
Comment thread src/xagent/core/tools/core/RAG_tools/kb/collection_handle.py Outdated
Comment thread src/xagent/core/tools/core/RAG_tools/kb/collection_handle.py Outdated
…itsai#511)

Low-risk defensive findings from PR review:
- wrap the pyarrow import in try/except so a missing optional dependency
  degrades to the existing `pa is None` guard instead of failing at import
- clamp distance with max(0.0, distance) in the dense score formula so a
  negative distance (e.g. dot-product metrics) cannot divide by zero or
  yield an out-of-range score

Claude-Session: https://claude.ai/code/session_01LcfvwrLffsY8iGF2XgbhFu
@rogercloud

Copy link
Copy Markdown
Collaborator Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the RAG search data-plane by centralizing the core search execution logic (dense, sparse, and hybrid) inside the LanceDBCollectionHandle class, while updating the public retrieval functions to perform input validation and delegate to this handle. The code review feedback highlights several critical issues with filter handling in the new implementation: both dense search engines and the substring fallback methods silently ignore non-dict FilterExpression inputs, which could lead to data leakage across collections or users. Additionally, the reviewer suggests importing DocumentValidationError in the sparse and hybrid search modules to ensure proper input validation at the public boundary.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/xagent/core/tools/core/RAG_tools/kb/collection_handle.py
Comment thread src/xagent/core/tools/core/RAG_tools/kb/collection_handle.py
Comment thread src/xagent/core/tools/core/RAG_tools/kb/collection_handle.py
Comment thread src/xagent/core/tools/core/RAG_tools/kb/collection_handle.py
Comment thread src/xagent/core/tools/core/RAG_tools/retrieval/search_sparse.py
Comment thread src/xagent/core/tools/core/RAG_tools/retrieval/search_hybrid.py

@qinxuye qinxuye left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I found one async validation regression that should be fixed before approval.

Comment thread src/xagent/core/tools/core/RAG_tools/retrieval/search_dense.py
…sai#511)

The H04 refactor lifted dense input validation from the deleted
_search_dense_impl to the public search_dense(), but the async path
(search_dense_async) was left delegating straight to the facade, dropping
the DocumentValidationError/VectorValidationError contract the original
_search_dense_async_impl enforced. Extract the validation into a shared
_validate_dense_inputs helper and apply it on both the sync and async
public boundaries, with async invalid-input test coverage.

Reported in PR review by @qinxuye.

Claude-Session: https://claude.ai/code/session_01LcfvwrLffsY8iGF2XgbhFu

@qinxuye qinxuye left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

@qinxuye qinxuye merged commit b380102 into xorbitsai:main Jun 25, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ref(kb): H04 move search lifecycle into handle

3 participants