Skip to content

Conversation

christian-bromann
Copy link
Member

Overview

This PR introduces the llmToolSelectorMiddleware - a new middleware for LangChain agents that intelligently selects the most relevant tools for each user query using an LLM-based strategy. This middleware helps reduce cognitive load on the main model and improves response quality by filtering tools before they reach the primary agent model.

Features

Core Functionality

  • Intelligent Tool Selection: Uses an LLM to analyze user queries and select the most relevant tools from the available set
  • Configurable Limits: Set maximum number of tools to select per query
  • Retry Logic: Built-in retry mechanism with configurable attempts for robust tool selection
  • Full History Support: Option to include conversation history in tool selection decisions
  • Flexible Model Configuration: Use a separate model for tool selection or reuse the agent's primary model

Usage Example

import { llmToolSelectorMiddleware } from "langchain/agents/middleware";

const middleware = llmToolSelectorMiddleware({
  maxTools: 3,
  systemPrompt: "Select the most relevant tools for the user's query.",
  includeFullHistory: true,
  maxRetries: 2
});

const agent = createAgent({
  model: "openai:gpt-4o",
  tools: [weatherTool, calculatorTool, searchTool, emailTool, calendarTool],
  middleware: [middleware],
});

Benefits

  1. Performance: Reduces token usage by limiting tools sent to the main model
  2. Quality: Improves response quality by providing only relevant tools
  3. Scalability: Handles large tool sets more efficiently
  4. Flexibility: Configurable to match different use cases and requirements
  5. Robustness: Built-in error handling and fallback mechanisms

Copy link

changeset-bot bot commented Sep 26, 2025

⚠️ No Changeset found

Latest commit: 0eed1f9

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link

vercel bot commented Sep 26, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
langchainjs-docs Ready Ready Preview Comment Sep 26, 2025 9:19pm
1 Skipped Deployment
Project Deployment Preview Comments Updated (UTC)
langchainjs-api-refs Ignored Ignored Sep 26, 2025 9:19pm

@christian-bromann christian-bromann merged commit 3b4d86b into v1 Sep 26, 2025
32 checks passed
@christian-bromann christian-bromann deleted the cb/big-tool-new-for-real branch September 26, 2025 21:19
Copy link
Contributor

@sydney-runkle sydney-runkle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

generally I think this is at least a great example for us to have

great opportunity for education too, lots of folks don't know about why using an abundance of tools is a problem!

* });
* ```
*/
export function llmToolSelectorMiddleware(options: LLMToolSelectorConfig) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not bigtool? Just curious

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got a Python implementation from @eyurtsev and copied the name from there, (changed file name later on)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer renaming to toolSelection.ts personally and put some different strategies here in the long run... unless we think that bigTool.ts is obvious

Comment on lines +120 to +122
const toolRepresentation = toolInfo
.map(({ name, description }) => `- ${name}: ${description}`)
.join("\n");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be configurable?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this goes into prompt engineering - maybe there are ways you would want to represent tools in different ways, e.g. I could imagine some models like an XML description of a tool better?

Comment on lines +86 to +99
const model = runtime.context.model ?? options.model;
const maxTools = runtime.context.maxTools ?? options.maxTools;
const includeFullHistory =
runtime.context.includeFullHistory === DEFAULT_INCLUDE_FULL_HISTORY
? options.includeFullHistory ?? runtime.context.includeFullHistory
: runtime.context.includeFullHistory ?? options.includeFullHistory;
const maxRetries =
runtime.context.maxRetries === DEFAULT_MAX_RETRIES
? options.maxRetries ?? runtime.context.maxRetries
: runtime.context.maxRetries ?? options.maxRetries;
const defaultSystemPrompt =
runtime.context.systemPrompt === DEFAULT_SYSTEM_PROMPT
? options.systemPrompt ?? runtime.context.systemPrompt
: runtime.context.systemPrompt ?? options.systemPrompt;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the short term could these just be args to middleware? not fallback to runtime context spec?

gives users 1 way to do things for now

if (includeFullHistory) {
const userMessages = request.messages
.filter(HumanMessage.isInstance)
.map((msg: BaseMessage) => msg.content)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are we only keeping HumanMessages for the full history? the full history is usually meant to include also AImessage and ToolMessage

/**
* Get the latest user message
*/
const latestMessage = request.messages.at(-1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe that there's a bug here -- the latest message may not be a user message (it may be a ToolMessage).

);

if (selectedToolNames.length === 0) {
systemMessage += `\n\nNote: You have not selected any tools. Please select at least one tool.`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not appear to be correct behavior. I think it's OK to select 0 tools if there are no relevant tools.

export function llmToolSelectorMiddleware(options: LLMToolSelectorConfig) {
return createMiddleware({
name: "LLMToolSelector",
contextSchema: LLMToolSelectorOptionsSchema,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're missing here an option to always include some tools.

A user may want to make sure that some tools are always included (and these tools should be removed from the prompt; i.e., the model shouldn't necessarily know about them.)

* });
* ```
*/
export function llmToolSelectorMiddleware(options: LLMToolSelectorConfig) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer renaming to toolSelection.ts personally and put some different strategies here in the long run... unless we think that bigTool.ts is obvious

* Zod schema for tool selection structured output.
*/
const ToolSelectionSchema = z.object({
selectedTools: z.array(z.string()).describe("List of selected tool names"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we generate this dynamically so it's not a list of string, but a list of possible enums with descriptions?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants