BM25SearchStrategy (the default) never matches underscore-joined terms in tool descriptions or param descriptions
Los mantenedores suelen responder en 1 día
Ya se ha fusionado un pull request relacionado.
Evaluación
- Dificultad
- 1/5
- Tiempo estimado
- Menos de una hora
- Aptitud para principiantes
- 88/100
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
BM25SearchStrategydocstring (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 attool_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:
ToolSearchToolsetdefaults toBM25SearchStrategy(tool_search.py:79), so everytool_searchcall 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.pyonly 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
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de livekit/agents
-
enhancement
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 62/100
livekit/agents#8366 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
Dificultad 1/5 Menos de una hora Aptitud para principiantes 78/100
livekit/agents#8284 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
ci: unit tests pin Python 3.12 while the package advertises 3.14 supportQuizá libre de nuevo @huong8373tt-beep la tomó hace 9 días y no hay ningún pull request abierto. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 74/100
livekit/agents#8231 · 3 comentarios ·
Los mantenedores suelen responder en 1 día
-
Assistant markup is stripped from chat history when expressive mode is offPosiblemente ocupada @ShivamSharma43 la tomó hace 30 días. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
livekit/agents#7647 · 1 comentario ·
Los mantenedores suelen responder en 1 día
Todos los issues de livekit/agents
Issues similares
-
bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
micropython/micropython#19906 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
FinanceFlash/unvibecode#597 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 65/100
TomAnthony/YourOwnWeigh#21 ·
Los mantenedores suelen responder en 1 día
-
[BUG]: Compliance rules never see Unity Catalog views as viewsPosiblemente ocupada @wagnercsantos la tomó hoy. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
databrickslabs/ontos#1348 ·
Los mantenedores suelen responder en 3 días
-
Deprecated datetime.utcnow() is called on every log line (DeprecationWarning on Python 3.12+)Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
Los mantenedores suelen responder en 2 días