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

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.

Could we treat underscores as separators here so common MCP tool names such as create_issue match a query like create issue, and add a regression test?

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.

2 participants