langchain
Version:
Typescript bindings for langchain
129 lines (128 loc) • 4.95 kB
JavaScript
import { createMiddleware } from "../middleware.js";
import { isLangChainTool } from "@langchain/core/utils/function_calling";
//#region src/agents/middleware/providerToolSearch.ts
const SERVER_SEARCH_PROVIDERS = ["anthropic", "openai"];
const SERVER_TOOL_SEARCH_TOOLS = {
anthropic: {
type: "tool_search_tool_bm25_20251119",
name: "tool_search_tool_bm25"
},
openai: { type: "tool_search" }
};
/**
* Provider-side tool search middleware.
*
* Leverages server-side tool search: the full client tool catalog is forwarded
* to the provider, with deferred tools marked `defer_loading` so the provider
* discloses them on demand via its own search. A tool is deferred when it is
* named in `searchableTools` or built with `extras.defer_loading: true`.
*
* Requires a model with server-side tool search support: OpenAI gpt-5.4+ or Anthropic
* Claude Sonnet 4+/Opus 4+/Haiku 4.5+. Non-Anthropic/OpenAI providers throw; an
* in-family model that is too old surfaces the provider's own API error rather
* than being gated here.
*
* @example
* ```ts
* import { createAgent, providerToolSearchMiddleware } from "langchain";
* import { ChatAnthropic } from "@langchain/anthropic";
*
* const agent = createAgent({
* model: new ChatAnthropic({ model: "claude-sonnet-4-5" }),
* tools: [getWeather, ...nicheTools],
* middleware: [
* // Defer the niche tools behind the provider's tool search; the model
* // discovers them on demand instead of receiving every schema up front.
* providerToolSearchMiddleware({ searchableTools: nicheTools }),
* ],
* });
* ```
*
* @example
* ```ts
* import { tool } from "@langchain/core/tools";
* import { createAgent, providerToolSearchMiddleware } from "langchain";
* import { ChatAnthropic } from "@langchain/anthropic";
*
* // A tool marked `defer_loading` at construction is deferred on its own —
* // no need to list it in `searchableTools`; the middleware honors the flag.
* const sendEmail = tool(sendEmailFn, {
* name: "send_email",
* description: "Send an email",
* schema: sendEmailSchema,
* extras: { defer_loading: true },
* });
*
* const agent = createAgent({
* model: new ChatAnthropic({ model: "claude-sonnet-4-5" }),
* tools: [getWeather, sendEmail],
* middleware: [providerToolSearchMiddleware()],
* });
* ```
*
* @param config - Configuration options for the middleware
* @param config.searchableTools - Tools to defer behind tool search
* @returns A middleware instance that can be used with `createAgent`
*/
function providerToolSearchMiddleware(config = {}) {
const deferNames = toToolNames(config.searchableTools);
return createMiddleware({
name: "ProviderToolSearch",
wrapModelCall: (request, handler) => {
const tools = request.tools ?? [];
if (deferNames.size > 0) {
const available = tools.filter(isLangChainTool).map((t) => t.name);
const unknown = [...deferNames].filter((name) => !available.includes(name));
if (unknown.length > 0) throw new Error(`providerToolSearchMiddleware: searchableTools references tool(s) not bound to the model: ${unknown.join(", ")}`);
}
const provider = getModelProvider(request.model);
if (!supportsProviderToolSearch(provider)) throw new Error(`providerToolSearchMiddleware requires a provider with server-side tool search, but got ${provider}`);
if (!hasDeferredTools(tools, deferNames)) return handler(request);
const boundTools = tools.map((tool) => deferToolIfNeeded(tool, deferNames));
const nativeSearchTool = SERVER_TOOL_SEARCH_TOOLS[provider];
return handler({
...request,
tools: [...boundTools, nativeSearchTool]
});
}
});
}
function isDeferred(tool, deferNames) {
return isLangChainTool(tool) && (tool.extras?.defer_loading === true || deferNames.has(tool.name));
}
function hasDeferredTools(tools, deferNames) {
return tools.some((tool) => isDeferred(tool, deferNames));
}
/**
* If a tool should be deferred, return a minimal binding spec carrying `defer_loading`.
* Otherwise return it as-is
*/
function deferToolIfNeeded(tool, deferNames) {
if (!isDeferred(tool, deferNames)) return tool;
return {
name: tool.name,
description: tool.description,
schema: tool.schema,
extras: {
...tool.extras,
defer_loading: true
}
};
}
/** Flatten a list of tool names/instances into a set of tool names. */
function toToolNames(tools = []) {
return new Set(tools.map((t) => typeof t === "string" ? t : t.name));
}
function getModelProvider(model) {
const name = model.getName();
const configured = name === "ConfigurableModel" ? model._defaultConfig?.modelProvider : void 0;
if (name === "ChatAnthropic" || configured === "anthropic") return "anthropic";
if (name === "ChatOpenAI" || configured === "openai") return "openai";
return "other";
}
function supportsProviderToolSearch(provider) {
return SERVER_SEARCH_PROVIDERS.includes(provider);
}
//#endregion
export { providerToolSearchMiddleware };
//# sourceMappingURL=providerToolSearch.js.map