Phase 3: Search API — KNN, RRF, field projection, groupBy, read levels#139
Phase 3: Search API — KNN, RRF, field projection, groupBy, read levels#139oss-amikos merged 34 commits intomainfrom
Conversation
- Create SearchApiCloudIntegrationTest with 12 test methods (CLOUD-02, CLOUD-03, D-22) - CLOUD-02: distance space round-trip (cosine/l2/ip), HNSW/SPANN config round-trips, invalid config transition rejection, schema round-trip via CollectionConfiguration - CLOUD-03: string/number/bool array round-trips with type fidelity (D-23), contains/notContains edge cases (D-24), empty array behavior documentation (D-25) - D-22: mixed-type array client-side validation in ChromaHttpCollection.validateMetadataArrayTypes - Wire validation into AddBuilderImpl, UpsertBuilderImpl, UpdateBuilderImpl execute() methods - Create MetadataValidationTest with 18 unit tests (static + behavioral wiring) - All cloud-dependent tests gate on Assume.assumeTrue(cloudAvailable) per D-02
- Add 05-01-SUMMARY.md documenting CLOUD-02, CLOUD-03, D-22 outcomes - Update STATE.md: advance to plan 2, add decisions, record metrics - Update ROADMAP.md: phase 5 progress (1/2 plans complete)
21 decisions across 4 areas: builder API shape (hybrid), result types (single SearchResult with isGrouped()), field projection (Select class, no Include), sparse vector scope (SparseVector type in Phase 3, EFs deferred).
Researches ChromaDB v1.5+ Search endpoint for planning Phase 3. Covers wire format (KNN, RRF, Select, GroupBy, ReadLevel), Go client struct patterns, Java builder/DTO translation strategy, and pitfalls.
…Select, ReadLevel, GroupBy) - SparseVector: immutable type with int[]/float[] arrays, defensive copies, validation - Select: field projection constants (#document, #score, #embedding, #metadata, #id) and key() factory - ReadLevel: enum with INDEX_AND_WAL/INDEX_ONLY constants and fromValue() lookup - GroupBy: builder with required key and optional minK/maxK bounds
…faces, and SearchBuilder on Collection - Knn: factory methods (queryText, queryEmbedding, querySparseVector) with fluent immutable chain - Rrf: builder with rank(Knn, weight), k, normalize; auto-sets returnRank on sub-rankings - Search: builder composing knn/rrf with filter, select, groupBy, limit, offset - SearchResultRow: extends ResultRow with Float getScore() - SearchResultGroup: interface with getKey() and rows() for grouped results - SearchResult: column-oriented (ids/documents/metadatas/embeddings/scores) and row-oriented access - Collection: declares SearchBuilder search() method with SearchBuilder inner interface - ChromaHttpCollection: stub SearchBuilderImpl (throws UnsupportedOperationException, wired in Plan 02)
- Add 03-01-SUMMARY.md documenting all 11 new/modified files - Update STATE.md with decisions, metrics, and session info - Update ROADMAP.md with plan progress (phase 03 now 3/3 summaries) - Mark SEARCH-01, SEARCH-02, SEARCH-03, SEARCH-04 complete in REQUIREMENTS.md
- Add collectionSearch() path builder to ChromaApiPaths - Add SearchRequest/SearchResponse DTOs to ChromaDtos - Add buildKnnRankMap, buildRrfRankMap, buildSearchItemMap helpers - Uses "filter" key (not "where") per Search API spec
…uilderImpl - Add SearchResultRowImpl: composition over ResultRowImpl, Float score field - Add SearchResultGroupImpl: key + ResultGroup<SearchResultRow> - Add SearchResultImpl: lazy-cached row groups, Double scores, grouped flag - Replace stub SearchBuilderImpl with full HTTP POST implementation - SearchBuilderImpl: convenience queryText/queryEmbedding, batch searches, global filter/limit/offset, readLevel, routes to /search via ChromaApiPaths
- Create 03-02-SUMMARY.md documenting DTOs, HTTP wiring, result converters - Update STATE.md: advance plan, record metrics and decisions - Update ROADMAP.md: phase 03 all 3 plans summarized (Complete)
…piUnitTest - SparseVectorTest: 8 tests covering immutability, defensive copies, validation, equals/hashCode, toString - SelectTest: 7 tests covering standard constants, key factory, all(), equals, blank/null guards - SearchApiUnitTest: 30 tests covering Knn, Rrf, Search, GroupBy, ReadLevel, and wire format serialization via ChromaDtos
…tibility test - Add SearchApiIntegrationTest with 12 cloud-only tests (KNN, batch, RRF skip, field projection, ReadLevel, GroupBy, global filter, convenience shortcuts); tests skip gracefully without credentials - Update PublicInterfaceCompatibilityTest: bump EXPECTED_COLLECTION_METHOD_COUNT to 22 and add testCollectionSearchMethod() assertion - Fix wire format bug in ChromaDtos: use '$knn'/'$rrf' keys (not 'knn'/'rrf') - Fix NPE in SearchResultImpl.rows() when Chroma Cloud returns null inner lists in batch search responses (e.g., "documents":[null,null]) - Update SearchApiUnitTest assertions to match corrected '$knn'/'$rrf' keys
- Create 03-03-SUMMARY.md documenting unit + integration test coverage, wire format bug fix ($knn/$rrf keys), and NPE fix in batch responses - Update STATE.md: advance plan counter, record metrics and decisions - Update ROADMAP.md: Phase 3 Search API marked Complete (3/3 plans)
8/8 must-haves verified. SEARCH-01 through SEARCH-04 all satisfied. 3 human verification items noted for server compatibility (RRF, text query, GroupBy key) — these are server-side limitations, not code gaps.
Critical fixes: - Add else-throw in buildKnnRankMap for unrecognized query types (#1) - Fix groups() to return empty list when not grouped, matching interface contract (#2) - Change SearchResultRow.getScore() from Float to Double for wire precision (#3) - Add defensive copy in Knn.getQuery() for float[] queries (#4) - Fix Knn.queryText() Javadoc: text sent to server, not client-side embedded (#5) Important fixes: - Add null dto check in SearchResultImpl.from() (#6) - Add bounds validation with actionable message in rows(searchIndex) (#7) - Add Objects.requireNonNull to Search.Builder setters (knn, rrf, where, groupBy, select) (#8) - Add null validation to Knn.key() (#9) - Validate null elements in SearchBuilderImpl.searches() (#10) - Rename groupCount() to searchCount() with clarified Javadoc (#13) Suggestions: - Add minK/maxK range and ordering validation in GroupBy.build() (#14) - Make Rrf.RankWithWeight fields private (#15) - Add null rows check in SearchResultGroupImpl (#16) - Add defensive rank-missing check in buildSearchItemMap (#18) - Remove internal "D-04" reference from production comment (#23) - Make ResultRow Javadoc mechanism-agnostic (Include/Select) (#24) - Clarify SearchBuilder limit/offset as per-search fallback (#25) - Add mutual-exclusivity docs to SearchBuilder convenience methods - Add IllegalArgumentException to SearchBuilder.execute() @throws
…ts for review findings
…s check Re-review found that returning empty list from groups() when not grouped masks caller mistakes — user can't distinguish "forgot GroupBy" from "server returned no groups". Restore the original throwing behavior. Also adds bounds validation on searchIndex in groups() to match rows().
- Rrf normalize=false not serialized (key absent from wire format) - SearchResultGroupImpl null rows guard (NullPointerException) - groups() bounds check on grouped results (IndexOutOfBoundsException)
…y review Reuse: - Extract ImmutableCopyUtils with shared nestedList/nestedMetadata/ nestedEmbeddings — eliminates ~100 lines of exact duplication between QueryResultImpl and SearchResultImpl - Add Search.toBuilder() to avoid fragile field-by-field reconstruction in SearchBuilderImpl.execute() Quality: - Extract checkSearchIndex() helper in SearchResultImpl (DRY bounds check) - Hoist list extraction (docList, metaList, embList) out of per-row loop - Make $knn/$rrf wire-format strings constants (WIRE_KNN, WIRE_RRF) Efficiency: - Cache SparseVector.getIndices()/getValues() in local variables in buildKnnRankMap to avoid 4 unnecessary defensive array copies
…nTest Cloud API rejects add requests without embeddings when no server-side embedding model is configured. Provide synthetic vectors for all add() calls, matching the pattern used by CloudParityIntegrationTest.
Cloud indexing is eventually consistent with no guaranteed timing. The get() endpoint reads from the WAL, so recently added records are visible immediately without waiting for index completion.
Cloud API may not echo back distance space or SPANN parameters in configuration responses. Re-fetch via getCollection() and skip gracefully when values are absent rather than hard-failing.
|
@claude PTAL |
|
Claude finished @tazarov's task in 3m 7s —— View job Code Review: Phase 3 — Search API
Overall this is a well-structured, thorough implementation. The immutable value types, defensive copies, and fluent builder APIs are consistent with the v2 patterns established earlier. Good test coverage at 144+ tests across unit, property-based, and integration layers. Below are the findings organized by severity. Issues1.
|
| # | Severity | Issue |
|---|---|---|
| 1 | Medium | Missing defensive copy in Search.Builder.select() |
| 2 | Medium | Global offset not applied when per-search limit exists |
| 3 | Low | groups() silently returns stub data |
| 4 | Low | Knn.limit() missing bounds validation |
Items 1-2 are worth fixing before merge. Items 3-4 are lower priority but would improve API robustness.
1. Search.Builder.select(): add defensive copy via Arrays.copyOf() 2. SearchBuilderImpl: apply globalLimit and globalOffset independently as per-search fallbacks, not gated on limit being null 4. Knn.limit(): add validation rejecting zero and negative values
|
@claude PTAL again |
|
Claude finished @tazarov's task in 2m 29s —— View job Re-review: Phase 3 — Search API
Previous Findings — All Addressed
Fresh Review — Remaining ItemsAfter a thorough re-review of all 17 core files, the implementation is in good shape. No high-severity issues found. A few minor items remain: 1.
|
GroupBy is a server-side processing stage that flattens results back into the standard column-major response. Neither the Python nor Go Chroma clients expose a groups() accessor — groupBy results are accessed via rows() like any other search result. Removes SearchResultGroup, SearchResultGroupImpl, and the grouped boolean plumbing through SearchResultImpl.
|
@claude PTAL |
|
Claude finished @tazarov's task in 2m 22s —— View job Re-review: Phase 3 — Search API (Round 3)
Changes Since Last ReviewCommit Previous Findings Status
Remaining Items1.
|
…ration-testing Phase 5: Cloud Integration Testing (#139 follow-up)
Summary
Phase 3: Search API
Goal: Implement the Chroma Search endpoint (v1.5+) with full ranking expression DSL, field projection, groupBy, and read levels — matching Go client capabilities.
Status: Verified ✓ (8/8 must-haves)
Implements the complete Search API for the v2 Java client. Users can execute
collection.search().queryText("foo").limit(5).execute()to perform KNN search, compose RRF from multiple weighted rankings, project specific fields viaSelect, group results by metadata key, and control read levels. The implementation follows the established v2 patterns (immutable value objects, fluent builders, defensive copies, interface-first design).Changes
Plan 03-01: Search API Type System
Search API value types and builder interfaces — the complete type contracts.
Key files created:
SparseVector.java— immutable sparse vector value type with defensive copiesSelect.java— field projection constants (DOCUMENT,SCORE,EMBEDDING,METADATA,ID) andkey()factoryReadLevel.java—INDEX_AND_WAL/INDEX_ONLYenumGroupBy.java— builder with required key and optional minK/maxKKnn.java— factory+fluent-chain ranking expression (queryText, queryEmbedding, querySparseVector)Rrf.java— RRF builder combining multiple KNN sub-rankings with weightsSearch.java— per-search builder composing rank, filter, select, groupBy, limit, offsetSearchResult.java/SearchResultRow.java/SearchResultGroup.java— result interfacesCollection.java— addedSearchBuilder search()andSearchBuilderinner interfacePlan 03-02: HTTP Pipeline
DTOs, serialization, and ChromaHttpCollection wiring.
Key files created/modified:
ChromaDtos.java—SearchRequest/SearchResponseDTOs,buildKnnRankMap/buildRrfRankMap/buildSearchItemMaphelpersChromaApiPaths.java—collectionSearch()path builderSearchResultImpl.java— immutable result with lazy-cached rows viaAtomicReferenceArraySearchResultRowImpl.java— composition overResultRowImplwithDoublescoreSearchResultGroupImpl.java— group key + row groupChromaHttpCollection.java—SearchBuilderImplwith convenience methods, batch support, global filter/limit/offset, readLevelPlan 03-03: Tests
Comprehensive unit and integration test suite.
Key files created/modified:
SparseVectorTest.java— 8 tests (immutability, validation, defensive copies)SelectTest.java— 7 tests (constants, factory, equality)SearchApiUnitTest.java— 65 tests (KNN/RRF/Search builders, DTO wire format, null validation, result parsing, bounds checks)SearchApiPropertyTest.java— 9 property-based tests using QuickTheories (roundtrip precision, immutability, constraint validation)SearchApiIntegrationTest.java— 12 cloud-gated integration tests (KNN, batch, RRF, projection, ReadLevel, GroupBy, filters)PublicInterfaceCompatibilityTest.java— updated toEXPECTED_COLLECTION_METHOD_COUNT=22Post-review fixes
ImmutableCopyUtils(eliminated ~100 lines of duplication between QueryResultImpl/SearchResultImpl)Search.toBuilder()for safe field copyingRequirements Addressed
Verification
$knn/$rrfprefixed keys match Chroma >= 1.5$rrfyet)$knn.query)Key Decisions
Double(notFloat) to preserve wire-format precision end-to-endSearchResultRow.getScore()returnsDoublefor consistency withSearchResult.getScores()groups()throwsIllegalStateExceptionwhen not grouped (fail-fast for caller mistakes)groupCount()renamed tosearchCount()to avoid confusion with GroupBy semantics$knn/$rrfprefixed keys (discovered during integration testing)Test Plan
mvn test -Dtest=SparseVectorTest,SelectTest,SearchApiUnitTest,SearchApiPropertyTest,PublicInterfaceCompatibilityTest— all 144 tests passmvn compilewith animal-sniffer — Java 8 compatiblemvn test -Dtest=SearchApiIntegrationTest— requires Docker with Chroma >= 1.5mvn test -Dtest=SearchApiCloudIntegrationTest— requires CHROMA_API_KEY