Skip to content

Ambiguous tool-ranking case: documentation query ranks unrelated weather tool above docs tool #56

Description

@k33wee

Summary

I found a small reproducible ambiguous tool-ranking case while testing @ratel-ai/sdk locally with a toy tool catalog.
For a documentation/configuration-related query, the expected tool search_docs is retrieved in the top-K, but an unrelated tool, weather_lookup, ranks above it.
This seems useful as a diagnostic case because it looks like a ranking-quality issue rather than a recall issue.

Edited: Updated script with correct reproduction step

Reproduction

import { ToolCatalog } from "@ratel-ai/sdk";

const catalog = new ToolCatalog();

const makeTextTool = (
  id: string,
  description: string,
  handler: (args: Record<string, unknown>) => Promise<Record<string, unknown>>,
) => {
  catalog.register({
    id,
    name: id,
    description,
    inputSchema: {
      properties: {
        query: {
          type: "string",
          description: "Natural language request or search query",
        },
        path: {
          type: "string",
          description: "Optional file path or directory path",
        },
        pattern: {
          type: "string",
          description: "Optional keyword or regular expression pattern",
        },
      },
    },
    outputSchema: {
      properties: {
        summary: {
          type: "string",
          description: "Textual summary of the tool result",
        },
      },
    },
    execute: handler,
  });
};

makeTextTool(
  "read_file",
  "Read a file from local disk and return its textual contents.",
  async ({ path }) => ({
    summary: `Read file at ${String(path ?? "<missing>")}`,
  }),
);

makeTextTool(
  "search_code",
  "Search source code across a repository for keywords, symbols, or configuration values.",
  async ({ pattern }) => ({
    summary: `Searched code for pattern ${String(pattern ?? "<missing>")}`,
  }),
);

makeTextTool(
  "search_docs",
  "Search project documentation, markdown files, and technical docs for relevant explanations.",
  async ({ query }) => ({
    summary: `Searched docs for ${String(query ?? "<missing>")}`,
  }),
);

makeTextTool(
  "search_logs",
  "Search application logs and runtime logs for errors, warnings, and diagnostics.",
  async ({ query }) => ({
    summary: `Searched logs for ${String(query ?? "<missing>")}`,
  }),
);

makeTextTool(
  "read_env_file",
  "Read environment variable files such as .env, .env.production, or deployment config files.",
  async ({ path }) => ({
    summary: `Read env file ${String(path ?? "<missing>")}`,
  }),
);

makeTextTool(
  "read_config_file",
  "Read structured configuration files like JSON, YAML, TOML, or application config files.",
  async ({ path }) => ({
    summary: `Read config file ${String(path ?? "<missing>")}`,
  }),
);

makeTextTool(
  "send_email",
  "Send an email message to one or more recipients.",
  async ({ query }) => ({
    summary: `Sent email request: ${String(query ?? "<missing>")}`,
  }),
);

makeTextTool(
  "send_slack_message",
  "Send a Slack message to a user or channel.",
  async ({ query }) => ({
    summary: `Sent Slack message: ${String(query ?? "<missing>")}`,
  }),
);

makeTextTool(
  "currency_convert",
  "Convert an amount of money from one currency to another using exchange rates.",
  async ({ query }) => ({
    summary: `Converted currency for request: ${String(query ?? "<missing>")}`,
  }),
);

makeTextTool(
  "weather_lookup",
  "Look up weather forecast for a city or location.",
  async ({ query }) => ({
    summary: `Looked up weather for ${String(query ?? "<missing>")}`,
  }),
);

makeTextTool(
  "create_issue",
  "Create a GitHub issue with title, body, labels, and assignee information.",
  async ({ query }) => ({
    summary: `Created issue for ${String(query ?? "<missing>")}`,
  }),
);

makeTextTool(
  "create_pull_request",
  "Create a GitHub pull request with title, description, and branch information.",
  async ({ query }) => ({
    summary: `Created pull request for ${String(query ?? "<missing>")}`,
  }),
);

const scenarios = [
  {
    query: "find where redis host is configured in the repo",
    topK: 5,
    invoke: { toolId: "search_code", args: { pattern: "REDIS_HOST" } },
  },
  {
    query: "read the production environment variables file",
    topK: 5,
    invoke: { toolId: "read_env_file", args: { path: ".env.production" } },
  },
  {
    query: "look for documentation about deployment configuration",
    topK: 5,
    invoke: {
      toolId: "search_docs",
      args: { query: "deployment configuration" },
    },
  },
  {
    query: "message the team that the release is out",
    topK: 5,
    invoke: {
      toolId: "send_slack_message",
      args: { query: "The release is out" },
    },
  },
  {
    query: "open a PR for the release branch",
    topK: 5,
    invoke: {
      toolId: "create_pull_request",
      args: { query: "release branch PR" },
    },
  },
];

for (const scenario of scenarios) {
  const hits = catalog.search(scenario.query, scenario.topK);
  console.log(`\nScenario query: ${scenario.query}`);
  console.log("Top hits:");
  console.log(hits);

  const result = await catalog.invoke(
    scenario.invoke.toolId,
    scenario.invoke.args,
  );
  console.log(`Invoke ${scenario.invoke.toolId}:`);
  console.log(result);
}

Observed output for "look for documentation about deployment configuration" query

[
  { toolId: "weather_lookup", score: 2.1971445083618164 },
  { toolId: "search_docs", score: 2.1485352516174316 },
  { toolId: "read_env_file", score: 2.1367175579071045 },
  { toolId: "search_code", score: 1.6402997970581055 },
  { toolId: "read_config_file", score: 1.6135268211364746 }
]

Expected behavior

Since the query explicitly asks for documentation, I expected search_docs to rank first.

Actual behavior

search_docs is retrieved, but it ranks second behind weather_lookup.

This does not look like a recall failure, because the expected tool is present in the top-K, it looks more like a ranking-quality issue caused by ambiguous lexical overlap. In this case, weather_lookup may be benefiting from overlap around terms like look / lookup, while the semantically better documentation tool remains slightly below it

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions