Skip to content

feat(tools): add dependency-free BM25 ranker - #1207

Open
edelauna wants to merge 1 commit into
mainfrom
issue/575
Open

feat(tools): add dependency-free BM25 ranker#1207
edelauna wants to merge 1 commit into
mainfrom
issue/575

Conversation

@edelauna

@edelauna edelauna commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Related GitHub Issue

Relates to #575

Description

Adds a dependency-free, pure-JavaScript BM25 ranker as the always-available baseline for MCP tool search. This PR is limited to the foundational ranker described in issue #575; it does not implement downstream routing or threshold-gating work.

Implementation

  • Adds the ToolDoc type with serverName, toolName, and description fields.
  • Adds the Ranker interface with rank(query, items, k) returning the top-ranked ToolDoc results.
  • Implements standard BM25 scoring with k1 = 1.5 and b = 0.75, with no external npm dependencies.
  • Tokenizes the combined server name, tool name, and description by lowercasing and splitting on non-word characters.
  • Builds the inverted index lazily on the first rank() call and rebuilds it only when the input array reference changes.
  • Handles empty queries, empty item sets, no-match queries, zero or limited k, empty descriptions, and stable input ordering for equal scores.

Tests and Validation

  • Adds focused unit tests covering BM25 relevance ordering, edge cases, top-k truncation, stable tie ordering, case-insensitive punctuation-aware tokenization, empty descriptions, and reference-based index rebuilding.
  • Validation: cd src && npx vitest run services/tools/__tests__/Bm25Ranker.spec.ts

Summary by CodeRabbit

  • New Features

    • Added relevance-based search for available tools.
    • Search results now account for tool names and descriptions, with case- and punctuation-insensitive matching.
    • Results are limited to the requested number and consistently ordered when relevance is equal.
    • Empty searches and searches without matches return no results.
  • Bug Fixes

    • Improved result accuracy when tool information changes or descriptions are unavailable.

@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds the ToolDoc and Ranker contracts, a cached BM25 ranker for tool metadata, deterministic top-k results, and tests for ranking, tokenization, edge cases, and index rebuilding.

Changes

BM25 tool ranking

Layer / File(s) Summary
Ranking contract and index construction
src/services/tools/types.ts, src/services/tools/Bm25Ranker.ts
Defines ToolDoc and Ranker. Builds a cached inverted index from tool metadata.
BM25 scoring and result selection
src/services/tools/Bm25Ranker.ts
Computes BM25 scores, filters zero-score results, preserves input order for ties, and applies the k limit.
Ranking behavior validation
src/services/tools/__tests__/Bm25Ranker.spec.ts
Tests relevance ordering, empty inputs, unmatched queries, tokenization, top-k results, stable ordering, and cache rebuilding.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related issues

  • Zoo-Code-Org/Zoo-Code#575 — Directly covers the Bm25Ranker, Ranker, and ToolDoc implementation.
  • Zoo-Code-Org/Zoo-Code#576 — Defines the Bm25Ranker and Ranker dependency for an embeddings-based ranker and fallback behavior.

Suggested reviewers: hannesrudolph

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: adding a dependency-free BM25 ranker for tools.
Description check ✅ Passed The description explains the implementation, scope, linked issue, and reproducible unit-test command, although it does not reproduce every template heading.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue/575

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

src/services/tools/Bm25Ranker.ts

ESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox.

src/services/tools/__tests__/Bm25Ranker.spec.ts

ESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.

src/services/tools/types.ts

ESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Aug 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.67442% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/services/tools/Bm25Ranker.ts 97.67% 0 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@edelauna
edelauna marked this pull request as ready for review August 9, 2026 03:23

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/services/tools/Bm25Ranker.ts`:
- Around line 88-90: Update the tokenize function to treat underscores as
separators alongside the existing non-word delimiters, so underscore-separated
tool names produce individual terms. Add a regression test covering matching an
underscore-separated name such as create_issue against separate query terms.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 92e64eac-ea6c-4124-88e9-9ff7e5e059ce

📥 Commits

Reviewing files that changed from the base of the PR and between 4c42f91 and 1e7c588.

📒 Files selected for processing (3)
  • src/services/tools/Bm25Ranker.ts
  • src/services/tools/__tests__/Bm25Ranker.spec.ts
  • src/services/tools/types.ts

Comment on lines +88 to +90
function tokenize(value: string): string[] {
return value.toLowerCase().split(/\W+/).filter(Boolean)
}

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

node - <<'NODE'
console.log("create_issue".split(/\W+/).filter(Boolean))
console.log("create issue".split(/\W+/).filter(Boolean))
NODE

Repository: Zoo-Code-Org/Zoo-Code

Length of output: 200


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

# Locate the ranker implementation and inspect its surrounding code.
fd -a 'Bm25Ranker\.ts$' . | sed 's#^\./##'
echo '--- outline ---'
ast-grep outline src/services/tools/Bm25Ranker.ts --view expanded || true
echo '--- relevant file excerpt ---'
sed -n '1,140p' src/services/tools/Bm25Ranker.ts

echo '--- tests mentioning Bm25Ranker/tokenize ---'
rg -n "Bm25Ranker|tokenize|build.*Index|rank\\(" src -g '*.ts' -g '*.tsx' | head -200

Repository: Zoo-Code-Org/Zoo-Code

Length of output: 6696


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo '--- Bm25Ranker spec excerpt ---'
sed -n '1,110p' src/services/tools/__tests__/Bm25Ranker.spec.ts

Repository: Zoo-Code-Org/Zoo-Code

Length of output: 2736


Split underscore-separated tool names into terms.

The ranked content uses the same tokenize() helper, but \W treats _ as part of a word. A tool named create_issue indexes under create_issue, while the query create issue only produces create and issue, so it cannot match that name. Include _ in the separator expression and add a regression test for an underscore-separated tool name.

Proposed fix
 function tokenize(value: string): string[] {
-	return value.toLowerCase().split(/\W+/).filter(Boolean)
+	return value.toLowerCase().split(/[\W_]+/).filter(Boolean)
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function tokenize(value: string): string[] {
return value.toLowerCase().split(/\W+/).filter(Boolean)
}
function tokenize(value: string): string[] {
return value.toLowerCase().split(/[\W_]+/).filter(Boolean)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/tools/Bm25Ranker.ts` around lines 88 - 90, Update the tokenize
function to treat underscores as separators alongside the existing non-word
delimiters, so underscore-separated tool names produce individual terms. Add a
regression test covering matching an underscore-separated name such as
create_issue against separate query terms.

@github-actions github-actions Bot added the awaiting-review PR changes are ready and waiting for maintainer re-review label Aug 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

awaiting-review PR changes are ready and waiting for maintainer re-review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant