gabotorresruiz commented on code in PR #45149:
URL: https://github.com/apache/superset/pull/45149#discussion_r4250991531


##########
superset/mcp_service/server.py:
##########
@@ -746,29 +755,66 @@ async def search_tools(
         tool = Tool.from_function(fn=search_tools, 
name=transform._search_tool_name)
         return _fix_search_tool_query(tool)
 
-    def _promote_exact_name(
+    def _promote_name_matches(
         tools: Sequence[Tool],
         query: str,
         ranked: Sequence[Tool],
         max_results: int,
+        *,
+        cover_query_words: bool = False,
     ) -> Sequence[Tool]:
-        """Promote caller-visible exact names without duplicating ranked 
matches."""
+        """Promote tools whose names the query spells out, within 
``max_results``.
+
+        BM25 indexes a tool's name as ordinary text next to its description, so
+        a tool with a long definition can rank below the result limit even when
+        the query contains every word of its name. Two kinds of name match are
+        moved ahead of the upstream ranking:
+
+        1. Exact names, which always come first.
+        2. With ``cover_query_words`` (natural-language search only), 
multi-word
+           names whose words appear in the query as a contiguous, in-order run,
+           for example ``get_dataset_info`` for "get dataset info execute sql",
+           ordered by name length and then by upstream rank. Words scattered
+           across the query do not count.
+
+        Only caller-visible candidates are inspected, never the full catalog,
+        and the result never exceeds ``max_results``.
+        """
         normalized_query = " ".join(query.casefold().replace("_", " ").split())
         exact = [
             tool
             for tool in tools
             if " ".join(tool.name.casefold().replace("_", " ").split())
             == normalized_query
         ]
-        if not exact:
-            return ranked
-        # Only inspect the caller-filtered candidates, never the full catalog.
-        # The upstream top-N contains enough non-exact results to fill the
-        # remaining slots, without changing upstream ordering or shared limits.
         exact_names = {tool.name for tool in exact}
+        covered: list[Tool] = []
+        if cover_query_words:
+            query_words = _name_words(query)
+            ranked_position = {tool.name: index for index, tool in 
enumerate(ranked)}
+            covered = sorted(
+                (
+                    tool
+                    for tool in tools
+                    if tool.name not in exact_names
+                    and len(words := _name_words(tool.name)) > 1
+                    and any(
+                        query_words[start : start + len(words)] == words
+                        for start in range(len(query_words) - len(words) + 1)
+                    )
+                ),
+                key=lambda tool: (
+                    -len(_name_words(tool.name)),
+                    ranked_position.get(tool.name, len(ranked_position)),

Review Comment:
   Not a blocker, just a question about the tie break. 
`ranked_position.get(tool.name, len(ranked_position))` hands the same sentinel 
to every covered tool that BM25 did not rank, so when more names are covered 
than fit in `max_results` the one that gets cut is decided by catalog 
registration order rather than by anything in the query. With the registered 
catalog, `generate chart get chart info get dataset info get dashboard info 
list dashboards execute sql` drops `execute_sql`; hand `_search` the same 
catalog list reversed and the same query drops `generate_chart` instead. Would 
breaking those ties by where the name run starts in the query be more 
defensible, so the names mentioned first survive the cut?
   
   ```python
   key=lambda match: (
       -len(match.words),
       match.run_start,
       ranked_position.get(match.tool.name, len(ranked_position)),
   ),
   ```
   
   Collecting `(tool, words, run_start)` in the filter would also save 
recomputing `_name_words(tool.name)` inside the key.



##########
superset/mcp_service/server.py:
##########
@@ -746,29 +755,66 @@ async def search_tools(
         tool = Tool.from_function(fn=search_tools, 
name=transform._search_tool_name)
         return _fix_search_tool_query(tool)
 
-    def _promote_exact_name(
+    def _promote_name_matches(
         tools: Sequence[Tool],
         query: str,
         ranked: Sequence[Tool],
         max_results: int,
+        *,
+        cover_query_words: bool = False,
     ) -> Sequence[Tool]:
-        """Promote caller-visible exact names without duplicating ranked 
matches."""
+        """Promote tools whose names the query spells out, within 
``max_results``.
+
+        BM25 indexes a tool's name as ordinary text next to its description, so
+        a tool with a long definition can rank below the result limit even when
+        the query contains every word of its name. Two kinds of name match are
+        moved ahead of the upstream ranking:
+
+        1. Exact names, which always come first.
+        2. With ``cover_query_words`` (natural-language search only), 
multi-word
+           names whose words appear in the query as a contiguous, in-order run,
+           for example ``get_dataset_info`` for "get dataset info execute sql",
+           ordered by name length and then by upstream rank. Words scattered
+           across the query do not count.
+
+        Only caller-visible candidates are inspected, never the full catalog,
+        and the result never exceeds ``max_results``.
+        """
         normalized_query = " ".join(query.casefold().replace("_", " ").split())
         exact = [
             tool
             for tool in tools
             if " ".join(tool.name.casefold().replace("_", " ").split())
             == normalized_query
         ]
-        if not exact:
-            return ranked
-        # Only inspect the caller-filtered candidates, never the full catalog.
-        # The upstream top-N contains enough non-exact results to fill the
-        # remaining slots, without changing upstream ordering or shared limits.
         exact_names = {tool.name for tool in exact}
+        covered: list[Tool] = []
+        if cover_query_words:
+            query_words = _name_words(query)
+            ranked_position = {tool.name: index for index, tool in 
enumerate(ranked)}
+            covered = sorted(
+                (
+                    tool
+                    for tool in tools
+                    if tool.name not in exact_names
+                    and len(words := _name_words(tool.name)) > 1
+                    and any(
+                        query_words[start : start + len(words)] == words

Review Comment:
   Small one, and possibly intended: a shorter name's words can themselves be a 
contiguous run inside a longer name's words, so spelling out 
`create_dataset_metric` promotes `create_dataset` as well and one intent takes 
two of the five slots. The catalog has four such pairs 
(`create_dataset_metric`/`create_dataset`, 
`delete_dataset_metric`/`delete_dataset`, 
`update_dataset_metric`/`update_dataset`, 
`update_chart_preview`/`update_chart`). On `create dataset metric for revenue` 
that second promotion is what pushes `list_metrics` out of the results. Worth 
skipping a covered name when another covered name already contains it, or is 
the extra one useful context?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to