langchain - ✅(Solved) Fix Proposal: Tier-aware tool definitions for capability-heterogeneous models [1 pull requests, 3 comments, 3 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
langchain-ai/langchain#36264Fetched 2026-04-08 01:31:00
View on GitHub
Comments
3
Participants
3
Timeline
10
Reactions
0
Author
Timeline (top)
commented ×3cross-referenced ×2mentioned ×2subscribed ×2

PR fix notes

PR #36366: feat(core): add tier-aware tool descriptions and schema adaptation

Description (problem / solution / changelog)

Small models (≤3B) achieve only 50% tool-selection accuracy when shown 50+ full tool definitions. Presenting tier-adapted subsets raises accuracy to 60%+ while saving ~47% of tokens (per benchmarks in #36264).

Changes:

  • Add optional tier_descriptions, tier_params, and category fields to BaseTool for per-tier description and parameter metadata.
  • Pass new fields through StructuredTool.from_function and the @tool decorator (all overloads).
  • Add langchain_core.tools.tier module exposing:
    • ModelTier — Literal['small', 'medium', 'large']
    • detect_tier(model_name) — pattern-based tier detection
    • get_tier_adapted_tools(tools, tier) — returns adapted copies
  • Add model_tier keyword parameter to create_agent; auto-detects tier from a string model name when not explicitly provided.

All changes are fully backward compatible.

Closes #36264

Fixes #

<!-- Replace everything above this line with a 1-2 sentence description of your change. Keep the "Fixes #xx" keyword and update the issue number. -->

Read the full contributing guidelines: https://docs.langchain.com/oss/python/contributing/overview

All contributions must be in English. See the language policy.

If you paste a large clearly AI generated description here your PR may be IGNORED or CLOSED!

Thank you for contributing to LangChain! Follow these steps to have your pull request considered as ready for review.

  1. PR title: Should follow the format: TYPE(SCOPE): DESCRIPTION
  1. PR description:
  • Write 1-2 sentences summarizing the change.
  • The Fixes #xx line at the top is required for external contributions — update the issue number and keep the keyword. This links your PR to the approved issue and auto-closes it on merge.
  • If there are any breaking changes, please clearly describe them.
  • If this PR depends on another PR being merged first, please include "Depends on #PR_NUMBER" in the description.
  1. Run make format, make lint and make test from the root of the package(s) you've modified.
  • We will not consider a PR unless these three are passing in CI.
  1. How did you verify your code works?

Additional guidelines:

  • All external PRs must link to an issue or discussion where a solution has been approved by a maintainer, and you must be assigned to that issue. PRs without prior approval will be closed.
  • PRs should not touch more than one package unless absolutely necessary.
  • Do not update the uv.lock files or add dependencies to pyproject.toml files (even optional ones) unless you have explicit permission to do so by a maintainer.

Social handles

Twitter: @ LinkedIn: https://www.linkedin.com/in/vishwaspatel24/

Changed files

  • libs/core/langchain_core/tools/__init__.py (modified, +11/-0)
  • libs/core/langchain_core/tools/base.py (modified, +59/-0)
  • libs/core/langchain_core/tools/convert.py (modified, +40/-0)
  • libs/core/langchain_core/tools/structured.py (modified, +6/-0)
  • libs/core/langchain_core/tools/tier.py (added, +190/-0)
  • libs/core/tests/unit_tests/test_tier_tools.py (added, +188/-0)
  • libs/langchain_v1/langchain/agents/factory.py (modified, +54/-0)

Code Example

P(correct tool) = P(correct family) × P(correct tool | correct family)

1.5B model: P(family)=56%, P(tool|family)=89%50% overall
35B model:  P(family)=90%, P(tool|family)=98%88% overall

---

from langchain_core.tools import tool

@tool
def file_read(path: str, encoding: str = "utf-8", 
              line_numbers: bool = False, offset: int = 0) -> str:
    """Read file contents with line numbers, offset, and encoding control."""
    with open(path, encoding=encoding) as f:
        content = f.read()
    if line_numbers:
        lines = content.split("\n")
        content = "\n".join(f"{i+1}: {l}" for i, l in enumerate(lines))
    if offset:
        content = "\n".join(content.split("\n")[offset:])
    return content

---

from langchain_core.tools import tool

@tool(
    tier_descriptions={
        "small":  "Read file",
        "medium": "Read a file from disk",
        "large":  "Read file contents with line numbers, offset, and encoding control",
    },
    tier_params={
        "small":  ["path"],
        "medium": ["path", "encoding"],
        "large":  ["path", "encoding", "line_numbers", "offset"],
    },
    category="filesystem",
)
def file_read(path: str, encoding: str = "utf-8",
              line_numbers: bool = False, offset: int = 0) -> str:
    """Read file contents with line numbers, offset, and encoding control."""
    with open(path, encoding=encoding) as f:
        content = f.read()
    if line_numbers:
        lines = content.split("\n")
        content = "\n".join(f"{i+1}: {l}" for i, l in enumerate(lines))
    if offset:
        content = "\n".join(content.split("\n")[offset:])
    return content

---

from langchain_openai import ChatOpenAI
from langchain.agents import create_tool_calling_agent

# Small local model
llm_small = ChatOpenAI(
    base_url="http://localhost:11434/v1",
    model="qwen2.5:1.5b",
)

# Tools automatically adapt: small model gets 
# "Read file" with only `path` parameter
agent = create_tool_calling_agent(
    llm=llm_small,
    tools=[file_read, web_search, git_status, ...],
    model_tier="small",  # or auto-detect from model name
)

---

# Tier "small" (1.5B) — what the model receives:
{
    "name": "file_read",
    "description": "Read file",
    "parameters": {
        "type": "object",
        "properties": {"path": {"type": "string"}},
        "required": ["path"]
    }
}

# Tier "large" (35B+) — what the model receives:
{
    "name": "file_read",
    "description": "Read file contents with line numbers, offset, and encoding control",
    "parameters": {
        "type": "object",
        "properties": {
            "path": {"type": "string"},
            "encoding": {"type": "string"},
            "line_numbers": {"type": "boolean"},
            "offset": {"type": "integer"}
        },
        "required": ["path"]
    }
}

---

from langchain.agents import TierRouter  # proposed

router = TierRouter(
    tools=[file_read, web_search, grep, git_status, ...],
    model_tier="small",
    strategy="hybrid",  # 8 detailed + rest name-only
)

# For each user query, router selects and adapts tools
adapted_tools = router.route("Read the file config.yaml")
# Returns [file_read, file_write, ...] with small-tier schemas
RAW_BUFFERClick to expand / collapse

Problem

LangChain tool definitions are model-agnostic — a @tool decorated function presents the same name, description, and args schema whether backed by GPT-4 or a 1.5B local model. With 50+ tools in an agent's toolkit, small models waste thousands of prompt tokens on tool descriptions they can't effectively parse, achieving as low as 50% selection accuracy.

Key Finding

Across 1,000+ native tool calling benchmarks (Ollama /api/chat, 80 tools, 50 prompts, 4 model sizes):

P(correct tool) = P(correct family) × P(correct tool | correct family)

1.5B model: P(family)=56%, P(tool|family)=89%  →  50% overall
35B model:  P(family)=90%, P(tool|family)=98%  →  88% overall

Even a 1.5B model picks the right tool 89% of the time — when shown the right neighborhood. The problem isn't tool selection, it's tool discovery in large sets.

Proposed Enhancement

Add optional tier-aware metadata to LangChain tool definitions:

Current LangChain tool:

from langchain_core.tools import tool

@tool
def file_read(path: str, encoding: str = "utf-8", 
              line_numbers: bool = False, offset: int = 0) -> str:
    """Read file contents with line numbers, offset, and encoding control."""
    with open(path, encoding=encoding) as f:
        content = f.read()
    if line_numbers:
        lines = content.split("\n")
        content = "\n".join(f"{i+1}: {l}" for i, l in enumerate(lines))
    if offset:
        content = "\n".join(content.split("\n")[offset:])
    return content

Every model sees: "Read file contents with line numbers, offset, and encoding control" + 4 parameters. A 1.5B model doesn't need line_numbers or offset.

Proposed tier-aware tool:

from langchain_core.tools import tool

@tool(
    tier_descriptions={
        "small":  "Read file",
        "medium": "Read a file from disk",
        "large":  "Read file contents with line numbers, offset, and encoding control",
    },
    tier_params={
        "small":  ["path"],
        "medium": ["path", "encoding"],
        "large":  ["path", "encoding", "line_numbers", "offset"],
    },
    category="filesystem",
)
def file_read(path: str, encoding: str = "utf-8",
              line_numbers: bool = False, offset: int = 0) -> str:
    """Read file contents with line numbers, offset, and encoding control."""
    with open(path, encoding=encoding) as f:
        content = f.read()
    if line_numbers:
        lines = content.split("\n")
        content = "\n".join(f"{i+1}: {l}" for i, l in enumerate(lines))
    if offset:
        content = "\n".join(content.split("\n")[offset:])
    return content

Tier-aware agent setup:

from langchain_openai import ChatOpenAI
from langchain.agents import create_tool_calling_agent

# Small local model
llm_small = ChatOpenAI(
    base_url="http://localhost:11434/v1",
    model="qwen2.5:1.5b",
)

# Tools automatically adapt: small model gets 
# "Read file" with only `path` parameter
agent = create_tool_calling_agent(
    llm=llm_small,
    tools=[file_read, web_search, git_status, ...],
    model_tier="small",  # or auto-detect from model name
)

How the agent sees tools at each tier:

# Tier "small" (1.5B) — what the model receives:
{
    "name": "file_read",
    "description": "Read file",
    "parameters": {
        "type": "object",
        "properties": {"path": {"type": "string"}},
        "required": ["path"]
    }
}

# Tier "large" (35B+) — what the model receives:
{
    "name": "file_read",
    "description": "Read file contents with line numbers, offset, and encoding control",
    "parameters": {
        "type": "object",
        "properties": {
            "path": {"type": "string"},
            "encoding": {"type": "string"},
            "line_numbers": {"type": "boolean"},
            "offset": {"type": "integer"}
        },
        "required": ["path"]
    }
}

Routing with category-based filtering:

from langchain.agents import TierRouter  # proposed

router = TierRouter(
    tools=[file_read, web_search, grep, git_status, ...],
    model_tier="small",
    strategy="hybrid",  # 8 detailed + rest name-only
)

# For each user query, router selects and adapts tools
adapted_tools = router.route("Read the file config.yaml")
# Returns [file_read, file_write, ...] with small-tier schemas

Benchmark Results

Strategy1.5B20BToken savings
Baseline (all tools, full desc)50%80%
Hybrid (8 detailed + rest name-only)60%76%47%
Reorder + category hint54%88%0% (accuracy gain)
Family oracle (upper bound)70%84%83%

Backwards Compatible

  • Tools without tier_descriptions work exactly as today
  • model_tier parameter is optional; defaults to "large" (current behavior)
  • No breaking changes to existing @tool or BaseTool

References

This is especially relevant as LangChain increasingly supports local models via Ollama/vLLM. Users running small models locally will benefit most from tier-aware tools.

extent analysis

Fix Plan

To implement tier-aware tool definitions in LangChain, follow these steps:

  1. Update tool definitions: Modify existing tool functions to include tier_descriptions and tier_params in the @tool decorator.

@tool( tier_descriptions={ "small": "Read file", "medium": "Read a file from disk", "large": "Read file contents with line numbers, offset, and encoding control", }, tier_params={ "small": ["path"], "medium": ["path", "encoding"], "large": ["path", "encoding", "line_numbers", "offset"], }, category="filesystem", ) def file_read(path: str, encoding: str = "utf-8", line_numbers: bool = False, offset: int = 0) -> str: # function implementation

2. **Create a TierRouter instance**: Initialize a `TierRouter` object with the updated tools and the desired `model_tier`.
   ```python
router = TierRouter(
    tools=[file_read, web_search, grep, git_status, ...],
    model_tier="small",
    strategy="hybrid",  # 8 detailed + rest name-only
)
  1. Adapt tools for the model tier: Use the route method of the TierRouter instance to select and adapt tools for the given model tier.

adapted_tools = router.route("Read the file config.yaml")


### Verification
To verify that the fix worked, check the following:

* The `tier_descriptions` and `tier_params` are correctly applied to the tool definitions.
* The `TierRouter` instance is correctly initialized with the updated tools and model tier.
* The `route` method of the `TierRouter` instance returns the adapted tools with the correct tier-aware schemas.

### Extra Tips
* Make sure to update the `model_tier` parameter according to the model size being used.
* Use the `hybrid` strategy for the `TierRouter` instance to balance between detailed and name-only tool descriptions.
* Refer to the provided references (whitepaper, SDK, benchmark code, and MCP proposal) for more information on the implementation and benefits of tier-aware tools.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

langchain - ✅(Solved) Fix Proposal: Tier-aware tool definitions for capability-heterogeneous models [1 pull requests, 3 comments, 3 participants]