BM25SearchStrategy (the default) never matches underscore-joined terms in tool descriptions or param descriptions

Cerrado Apto para principiantes
#7,727 1 comentario 0 reacciones 0 asignados Ver en GitHub

Los mantenedores suelen responder en 1 día

Ya se ha fusionado un pull request relacionado.

  • #7733 de @BlueX888 — fusionado
  • #7745 de @abhikhurannah — cerrado sin fusionar

Evaluación

Dificultad
1/5
Tiempo estimado
Menos de una hora
Aptitud para principiantes
88/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
python
Área
search

Línea de trabajo

Empieza en livekit-agents/livekit/agents/beta/toolsets/tool_search.py en tokenize (alrededor de las líneas 324-334), donde las claves de nombre y de parámetros reciben el reemplazo de guion bajo por espacio, pero los tokens de descripción y de valor de parámetro no. Reproduce el problema con el script BM25 vs. KeywordSearchStrategy del issue y luego aplica el mismo .replace("", " ") a los tokens de descripción y de valor de parámetro. Está terminado cuando las cuatro consultas devuelven get_records desde BM25 y tests/test_tool_search.py sigue pasando, además de una nueva prueba de regresión con un término de descripción unido por guion bajo.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

Bug Description

BM25SearchStrategy — the default search strategy for ToolSearchToolset (livekit-agents/livekit/agents/beta/toolsets/tool_search.py:79) — never matches underscore-joined terms that appear in a tool's description or in a parameter description (the values of the parameters dict).

search() normalizes the query with query.lower().replace("_", " ").split() (tool_search.py:307), and _tokenize applies that same underscore→space split to the tool name (line 326) and to parameter keys (line 330) — but not to description tokens (line 327) or parameter value tokens (line 331). So a description containing checking_balance is indexed as the single token checking_balance, while no query can ever produce that token (every query has _ split into spaces at line 307). Any search for checking_balance, checking balance, pending_refunds, pending refunds, … scores 0 and the tool is silently not returned.

The sibling KeywordSearchStrategy returns the same tool for the same query, and a control case where the description uses a space instead (checking balance) matches fine on BM25 — so this is specific to underscore-joined terms in descriptions/param descriptions.

This hits the LLM-facing path: with the default strategy, a tool_search call with query="checking_balance" raises ToolError: No tools found matching 'checking_balance'. even though the tool's description contains that exact term.

Expected Behavior

BM25SearchStrategy should return a tool whose description or parameter description contains the query terms, matching KeywordSearchStrategy's behaviour for the same input.

Basis:

  • The BM25SearchStrategy docstring (tool_search.py:260-261): "Each SearchItem is treated as a document composed of its name (weighted 3x), description (weighted 2x), and parameter names/descriptions (weighted 1x)."
  • search()'s own query normalization at tool_search.py:307 (query.lower().replace("_", " ")) shows the intended tokenization, and makes underscore-joined description tokens unreachable by any query.
  • The LLM-facing query contract _DEFAULT_QUERY_DESCRIPTION (tool_search.py:51-53): "keywords to search for in the tool names and descriptions, split by spaces".
Reproduction Steps

No network or credentials needed — pure framework-internal search. With livekit-agents 1.8.3 (built from source at commit 1257fe0f987b53054e1d31b98e514991776f85d4), Python 3.12.14:

from livekit.agents.beta.toolsets.tool_search import (
    BM25SearchStrategy,
    KeywordSearchStrategy,
    SearchItem,
)


def run(label, items, query):
    kw_items = [SearchItem(source=i.source, name=i.name, description=i.description, parameters=i.parameters) for i in items]
    KeywordSearchStrategy().build_index(kw_items)
    kw_res = [i.name for i in KeywordSearchStrategy().search(query, kw_items, 5)]

    bm_items = [SearchItem(source=i.source, name=i.name, description=i.description, parameters=i.parameters) for i in items]
    BM25SearchStrategy().build_index(bm_items)
    bm_res = [i.name for i in BM25SearchStrategy().search(query, bm_items, 5)]
    print(f"[{label}] query={query!r:20} Keyword -> {kw_res}   BM25 -> {bm_res}")


# case 1: underscore-joined term only in the DESCRIPTION
desc_items = [
    SearchItem(
        name="get_records",
        description="Fetch the user's checking_balance from the ledger",
        parameters={"record_type": "Which records to fetch"},
        source="src_a",
    ),
    SearchItem(
        name="get_weather",
        description="Get current weather for a city",
        parameters={"city": "City name"},
        source="src_b",
    ),
]
run("desc-only", desc_items, "checking_balance")
run("desc-only", desc_items, "checking balance")

# case 2: underscore-joined term only in a parameter VALUE
val_items = [
    SearchItem(
        name="get_records",
        description="Fetch ledger records for the user",
        parameters={"filter": "One of: pending_refunds only, settled only, all"},
        source="src_a",
    ),
    SearchItem(
        name="get_weather",
        description="Get current weather for a city",
        parameters={"city": "City name"},
        source="src_b",
    ),
]
run("param-value", val_items, "pending_refunds")
run("param-value", val_items, "pending refunds")

Actual output:

[desc-only] query='checking_balance'   Keyword -> ['get_records']   BM25 -> []
[desc-only] query='checking balance'   Keyword -> ['get_records']   BM25 -> []
[param-value] query='pending_refunds'    Keyword -> ['get_records']   BM25 -> []
[param-value] query='pending refunds'    Keyword -> ['get_records']   BM25 -> []

Going through ToolSearchToolset with the default strategy (a @function_tool-decorated get_records whose docstring is """Fetch the user's checking_balance from the ledger."""), await ts._handle_search({"query": "checking_balance"}) — i.e. the tool_search function the LLM calls — raises:

livekit.agents.llm.tool_context.ToolError: No tools found matching 'checking_balance'.

at tool_search.py:153, while the same toolset built with search_strategy=KeywordSearchStrategy() loads the tool and returns "Tools loaded successfully.".

Operating System

macOS (darwin 25.6.0, arm64). No OS-specific code involved in the affected module.

Models Used

N/A — reproduces without any model; this is framework-internal search over SearchItems.

Package Versions

livekit-agents==1.8.3 (built from source at commit 1257fe0f987b53054e1d31b98e514991776f85d4; the affected file is unchanged on current main)
Python 3.12.14

Proposed Solution

In _tokenize (tool_search.py:324-334), apply the same .replace("_", " ") normalization to desc_tokens (line 327) and to the parameter VALUE tokens (line 331), matching the existing name/key handling and the query normalization in search():

desc_tokens = item.description.lower().replace("_", " ").split()
param_tokens.extend(v.lower().replace("_", " ").split())

I verified locally that with these two changes the repro above returns get_records from BM25 for all four queries, and the existing tests/test_tool_search.py suite (40 tests) still passes. Happy to open a PR with this change plus a regression test.

Additional Context

Root cause: tool_search.py:327 (desc_tokens = item.description.lower().split()) and tool_search.py:331 (param_tokens.extend(v.lower().split())) omit the underscore→space split that is applied to the name (tool_search.py:326), parameter keys (tool_search.py:330) and the query (tool_search.py:307), so description/param-description tokens containing _ are unreachable by any query.

Notes:

  • ToolSearchToolset defaults to BM25SearchStrategy (tool_search.py:79), so every tool_search call the LLM makes goes through this path (_handle_search → _search_tools → strategy.search).
  • Tool descriptions in Python routinely reference snake_case parameter/function names, so dynamic tool discovery silently misses those tools for those terms.
  • The existing tests/test_tool_search.py only uses space-separated description terms, so this case is uncovered — all 40 tests pass at this commit.
  • I searched the tracker for existing reports about tool_search / BM25 and found none.
Lenguaje dominante
Python
Estrellas
14.6k
Forks
3.9k
Merge medio
3 d
PR fusionados (30 d)
262

Preparar el entorno

Este proyecto no incluye contenedor de desarrollo, Dockerfile ni guía de contribución, así que la configuración corre por tu cuenta: empieza por su README y consulta nuestra guía para la primera contribución para los pasos generales.

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de livekit/agents

Todos los issues de livekit/agents

Issues similares

Más issues de Python

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.