UNPKG

langchain

Version:
129 lines (128 loc) 4.95 kB
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