Skip to content

Conversation

@Sicheng-Pan
Copy link
Contributor

@Sicheng-Pan Sicheng-Pan commented Sep 17, 2025

Description of changes

Summarize the changes made by this PR.

  • Improvements & Bug fixes
    • Format code with black
  • New functionality
    • Implements reciprocal rank fusion

Test plan

How are these changes tested?

  • Tests pass locally with pytest for python, yarn test for js, cargo test for rust

Migration plan

Are there any migrations, or any forwards/backwards compatibility changes needed in order to make sure this change deploys reliably?

Observability plan

What is the plan to instrument and monitor this change?

Documentation Changes

Are all docstrings for user-facing APIs updated if required? Do we need to make documentation changes in the docs section?

Copy link
Contributor Author

Sicheng-Pan commented Sep 17, 2025

@github-actions
Copy link

Reviewer Checklist

Please leverage this checklist to ensure your code review is thorough before approving

Testing, Bugs, Errors, Logs, Documentation

  • Can you think of any use case in which the code does not behave as intended? Have they been tested?
  • Can you think of any inputs or external events that could break the code? Is user input validated and safe? Have they been tested?
  • If appropriate, are there adequate property based tests?
  • If appropriate, are there adequate unit tests?
  • Should any logging, debugging, tracing information be added or removed?
  • Are error messages user-friendly?
  • Have all documentation changes needed been made?
  • Have all non-obvious changes been commented?

System Compatibility

  • Are there any potential impacts on other parts of the system or backward compatibility?
  • Does this change intersect with any items on our roadmap, and if so, is there a plan for fitting them together?

Quality

  • Is this code of a unexpectedly high quality (Readability, Modularity, Intuitiveness)

@Sicheng-Pan Sicheng-Pan marked this pull request as ready for review September 17, 2025 22:36
@propel-code-bot
Copy link
Contributor

propel-code-bot bot commented Sep 17, 2025

Implement Reciprocal Rank Fusion (RRF) Helper Expression and Tests

This PR introduces a new Rrf (Reciprocal Rank Fusion) ranking operator for the search expression framework, enabling users to combine multiple ranking strategies using the RRF algorithm. It adds complete support for operator overloading, validation logic, optional weight normalization, extended documentation, and comprehensive tests with error coverage. Additionally, it cleans up formatting and makes minor import and API improvements to maintain consistency across the relevant modules.

Key Changes

• Added new Rrf class to chromadb/execution/expression/operator.py, implementing reciprocal rank fusion logic with customizable weights and optional normalization.
• Extended operator overloading for arithmetic and functional composition on Rank expressions.
• Added input validation for the Rrf class ensuring non-negative k, weights, correct list lengths, and proper normalization semantics.
• Enhanced documentation for Rrf and related classes, covering usage, examples, and mathematical background.
• Modified __init__.py and related plan modules to correctly expose Rrf and maintain public API integrity.
• Added robust tests (test_rrf_to_dict) in chromadb/test/test_api.py covering the functional output, validation, and edge cases for the Rrf expression.
• Refactored imports and formatting for coherence and compliance with code standards (formatted with black and structured imports).

Affected Areas

chromadb/execution/expression/operator.py (core ranking expression logic, new RRF implementation, docs)
chromadb/execution/expression/plan.py (search plan composition and public API adjustments)
chromadb/execution/expression/__init__.py (module exports, API exposure)
chromadb/test/test_api.py (unit tests for RRF, edge/error coverage)

This summary was automatically generated by @propel-code-bot

@Sicheng-Pan Sicheng-Pan force-pushed the 09-17-_enh_implements_rrf_helper_expression branch from d28af3b to bfb4652 Compare September 17, 2025 22:38
Knn(query=[0.1, 0.2], return_rank=True),
Knn(query=sparse_vector, key="#sparse", return_rank=True)
],
weights=[2.0, 1.0], # Dense is 2x more important than sparse
Copy link
Contributor

Choose a reason for hiding this comment

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

normalize to 1 might be easier to compare separate rrf ranking calculations
(sparse + dense + bm25 vs sparse + dense should be comparable)

Copy link
Contributor Author

@Sicheng-Pan Sicheng-Pan Sep 22, 2025

Choose a reason for hiding this comment

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

I see a use case where normalization helps, but I think this should not be the default because normalization seems to me as a separate step, and could cause confusion if always enabled. The user should be able to normalize using an expression if they want using the current api. We could provide a normalize flag as indicator that the weights should be normalized to facilitate this use case.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

From claude:

Option 1: Always normalize weights automatically

Pros:

• User-friendly - no need to worry about normalization
• Consistent behavior across all uses
• Prevents mathematical inconsistencies
• Similar to how probabilities work (always sum to 1)

Cons:

• Loss of interpretability - weights like [2.0, 1.0] are clearer than [0.667, 0.333]
• Breaks backward compatibility if changed now
• May surprise users who expect raw weights to be used as-is
• Different from standard RRF implementations in literature

Option 2: Require normalized weights

Pros:

• Forces users to be explicit about weight distribution
• No ambiguity about weight values
• Mathematically clean

Cons:

• Poor user experience - adds burden on users
• Error-prone - users must calculate normalization manually
• Would need validation to ensure sum equals 1.0 (floating point issues)
• Different from common practice in ML libraries

Option 3: No normalization (current implementation)

Pros:

• Flexible - users can use any scale they prefer
• Intuitive relative weights (e.g., [2, 1] means "2x more important")
• Matches standard RRF implementations
• Users can normalize if they want to

Cons:

• Scale-dependent results - [2, 1] vs [200, 100] give different scores
• May lead to numerical issues with very large weights
• Less mathematically principled

Option 4: Add normalize flag (recommended)

Pros:

• Best of both worlds - flexibility and control
• Backward compatible (default normalize=False)
• Clear intent from API usage
• Allows both use cases:
• Relative importance: weights=[2, 1], normalize=True
• Absolute weights: weights=[0.7, 0.3], normalize=False

Cons:

• Adds API complexity (one more parameter)
• Need to document behavior clearly
• Slight increase in implementation complexity

Recommendation

I recommend Option 4 with a normalize flag for these reasons:

  1. Preserves backward compatibility - Existing code continues to work
  2. Supports both use cases elegantly:
    • Research/experimentation may want exact weight control
    • Production use may prefer normalized weights for consistency
  3. Clear semantics - The flag makes the behavior explicit
  4. Common pattern in ML libraries (e.g., scikit-learn's normalize parameters)

ranks: List of Rank expressions to fuse (must have at least one)
k: Smoothing constant (default: 60, standard in literature)
weights: Optional weights for each ranking strategy. If not provided,
all ranks are weighted equally (weight=1.0 each).
Copy link
Contributor

Choose a reason for hiding this comment

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

weight = 1/n, where n is number of rank expressions

Copy link
Contributor Author

Choose a reason for hiding this comment

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

see above

Copy link
Contributor

@jairad26 jairad26 left a comment

Choose a reason for hiding this comment

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

tests verifying rrf works would be nice

# Test 1: Basic RRF with two KNN rankings (equal weight)
rrf = Rrf(
[
Knn(query=[0.1, 0.2], return_rank=True),
Copy link
Contributor

Choose a reason for hiding this comment

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

validate return_rank in RRF function

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Would like to keep test_api minimal for now. Could add this test later.

rrf = Rrf(
[
Knn(query=[0.1, 0.2], return_rank=True),
Knn(query=[0.3, 0.4], key="#sparse", return_rank=True),
Copy link
Contributor

Choose a reason for hiding this comment

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

note for after schema is added, having a BM25 wrapper around KNN would give more clarity

Copy link
Contributor Author

Sicheng-Pan commented Sep 23, 2025

Merge activity

  • Sep 23, 12:17 AM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Sep 23, 12:19 AM UTC: Graphite rebased this pull request as part of a merge.
  • Sep 23, 12:49 AM UTC: @Sicheng-Pan merged this pull request with Graphite.

@Sicheng-Pan Sicheng-Pan changed the base branch from 09-17-_enh_implement_row_iterator_for_search_result to graphite-base/5499 September 23, 2025 00:17
@Sicheng-Pan Sicheng-Pan changed the base branch from graphite-base/5499 to main September 23, 2025 00:17
@Sicheng-Pan Sicheng-Pan force-pushed the 09-17-_enh_implements_rrf_helper_expression branch from b16853f to cf123a1 Compare September 23, 2025 00:18
@Sicheng-Pan Sicheng-Pan merged commit afd8c02 into main Sep 23, 2025
59 checks passed
@Sicheng-Pan Sicheng-Pan deleted the 09-17-_enh_implements_rrf_helper_expression branch September 23, 2025 01:05
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.

3 participants