{"version":3,"file":"llmToolSelector.cjs","names":["z","BaseLanguageModel","createMiddleware","HumanMessage","initChatModel"],"sources":["../../../src/agents/middleware/llmToolSelector.ts"],"sourcesContent":["import { z } from \"zod/v3\";\nimport { BaseLanguageModel } from \"@langchain/core/language_models/base\";\nimport type { InferInteropZodInput } from \"@langchain/core/utils/types\";\nimport { HumanMessage } from \"@langchain/core/messages\";\nimport type { StructuredToolInterface } from \"@langchain/core/tools\";\n\nimport { createMiddleware } from \"../middleware.js\";\nimport { initChatModel } from \"../../chat_models/universal.js\";\nimport type { Runtime } from \"../runtime.js\";\nimport type { ModelRequest } from \"../nodes/types.js\";\nimport {\n  mergeConfigs,\n  pickRunnableConfigKeys,\n  type RunnableConfig,\n} from \"@langchain/core/runnables\";\n\nconst DEFAULT_SYSTEM_PROMPT =\n  \"Your goal is to select the most relevant tools for answering the user's query.\";\n\n/**\n * Prepared inputs for tool selection.\n */\ninterface SelectionRequest {\n  availableTools: StructuredToolInterface[];\n  systemMessage: string;\n  lastUserMessage: HumanMessage;\n  model: BaseLanguageModel;\n  validToolNames: string[];\n}\n\n/**\n * Create a structured output schema for tool selection.\n *\n * @param tools - Available tools to include in the schema.\n * @returns Zod schema where each tool name is a literal with its description.\n */\nfunction createToolSelectionResponse(tools: StructuredToolInterface[]) {\n  if (!tools || tools.length === 0) {\n    throw new Error(\"Invalid usage: tools must be non-empty\");\n  }\n\n  // Create a union of literals for each tool name\n  const toolLiterals = tools.map((tool) => z.literal(tool.name));\n  const toolEnum = z.union(\n    toolLiterals as [\n      z.ZodLiteral<string>,\n      z.ZodLiteral<string>,\n      ...z.ZodLiteral<string>[],\n    ]\n  );\n\n  return z.object({\n    tools: z\n      .array(toolEnum)\n      .describe(\"Tools to use. Place the most relevant tools first.\"),\n  });\n}\n\n/**\n * Options for configuring the LLM Tool Selector middleware.\n */\nexport const LLMToolSelectorOptionsSchema = z.object({\n  /**\n   * The language model to use for tool selection (default: the provided model from the agent options).\n   */\n  model: z.string().or(z.instanceof(BaseLanguageModel)).optional(),\n  /**\n   * System prompt for the tool selection model.\n   */\n  systemPrompt: z.string().optional(),\n  /**\n   * Maximum number of tools to select. If the model selects more,\n   * only the first maxTools will be used. No limit if not specified.\n   */\n  maxTools: z.number().optional(),\n  /**\n   * Tool names to always include regardless of selection.\n   * These do not count against the maxTools limit.\n   */\n  alwaysInclude: z.array(z.string()).optional(),\n});\nexport type LLMToolSelectorConfig = InferInteropZodInput<\n  typeof LLMToolSelectorOptionsSchema\n>;\n\n/**\n * Middleware for selecting tools using an LLM-based strategy.\n *\n * When an agent has many tools available, this middleware filters them down\n * to only the most relevant ones for the user's query. This reduces token usage\n * and helps the main model focus on the right tools.\n *\n * @param options - Configuration options for the middleware\n * @param options.model - The language model to use for tool selection (default: the provided model from the agent options).\n * @param options.systemPrompt - Instructions for the selection model.\n * @param options.maxTools - Maximum number of tools to select. If the model selects more,\n *   only the first maxTools will be used. No limit if not specified.\n * @param options.alwaysInclude - Tool names to always include regardless of selection.\n *   These do not count against the maxTools limit.\n *\n * @example\n * Limit to 3 tools:\n * ```ts\n * import { llmToolSelectorMiddleware } from \"langchain/agents/middleware\";\n *\n * const middleware = llmToolSelectorMiddleware({ maxTools: 3 });\n *\n * const agent = createAgent({\n *   model: \"openai:gpt-4o\",\n *   tools: [tool1, tool2, tool3, tool4, tool5],\n *   middleware: [middleware],\n * });\n * ```\n *\n * @example\n * Use a smaller model for selection:\n * ```ts\n * const middleware = llmToolSelectorMiddleware({\n *   model: \"openai:gpt-4o-mini\",\n *   maxTools: 2\n * });\n * ```\n */\nexport function llmToolSelectorMiddleware(options: LLMToolSelectorConfig) {\n  return createMiddleware({\n    name: \"LLMToolSelector\",\n    contextSchema: LLMToolSelectorOptionsSchema,\n    async wrapModelCall(request, handler) {\n      const selectionRequest = await prepareSelectionRequest(\n        request,\n        options,\n        request.runtime\n      );\n      if (!selectionRequest) {\n        return handler(request);\n      }\n\n      // Create dynamic response model with union of literal tool names\n      const toolSelectionSchema = createToolSelectionResponse(\n        selectionRequest.availableTools\n      );\n      const structuredModel =\n        await selectionRequest.model.withStructuredOutput?.(\n          toolSelectionSchema\n        );\n\n      const baseConfig: RunnableConfig =\n        pickRunnableConfigKeys(request.runtime) ?? {};\n      const config = mergeConfigs(baseConfig, {\n        metadata: { lc_source: \"llmToolSelector\" },\n        callbacks: [],\n      });\n\n      const response = await structuredModel?.invoke(\n        [\n          { role: \"system\", content: selectionRequest.systemMessage },\n          selectionRequest.lastUserMessage,\n        ],\n        config\n      );\n\n      // Response should be an object with a tools array\n      if (!response || typeof response !== \"object\" || !(\"tools\" in response)) {\n        throw new Error(\n          `Expected object response with tools array, got ${typeof response}`\n        );\n      }\n\n      return handler(\n        processSelectionResponse(\n          response as { tools: string[] },\n          selectionRequest.availableTools,\n          selectionRequest.validToolNames,\n          request,\n          options\n        )\n      );\n    },\n  });\n}\n\n/**\n * Prepare inputs for tool selection.\n *\n * @param request - The model request to process.\n * @param options - Configuration options.\n * @param runtime - Runtime context.\n * @returns SelectionRequest with prepared inputs, or null if no selection is needed.\n */\nasync function prepareSelectionRequest<\n  TState extends Record<string, unknown> = Record<string, unknown>,\n  TContext = unknown,\n>(\n  request: ModelRequest<TState, TContext>,\n  options: LLMToolSelectorConfig,\n  runtime: Runtime<LLMToolSelectorConfig>\n): Promise<SelectionRequest | undefined> {\n  const model = runtime.context.model ?? options.model;\n  const maxTools = runtime.context.maxTools ?? options.maxTools;\n  const alwaysInclude =\n    runtime.context.alwaysInclude ?? options.alwaysInclude ?? [];\n  const systemPrompt =\n    runtime.context.systemPrompt ??\n    options.systemPrompt ??\n    DEFAULT_SYSTEM_PROMPT;\n\n  /**\n   * If no tools available, return null\n   */\n  if (!request.tools || request.tools.length === 0) {\n    return undefined;\n  }\n\n  /**\n   * Filter to only StructuredToolInterface instances (exclude provider-specific tool dicts)\n   */\n  const baseTools = request.tools.filter(\n    (tool): tool is StructuredToolInterface =>\n      typeof tool === \"object\" &&\n      \"name\" in tool &&\n      \"description\" in tool &&\n      typeof tool.name === \"string\"\n  );\n\n  /**\n   * Validate that alwaysInclude tools exist\n   */\n  if (alwaysInclude.length > 0) {\n    const availableToolNames = new Set(baseTools.map((tool) => tool.name));\n    const missingTools = alwaysInclude.filter(\n      (name) => !availableToolNames.has(name)\n    );\n    if (missingTools.length > 0) {\n      throw new Error(\n        `Tools in alwaysInclude not found in request: ${missingTools.join(\n          \", \"\n        )}. ` +\n          `Available tools: ${Array.from(availableToolNames).sort().join(\", \")}`\n      );\n    }\n  }\n\n  /**\n   * Separate tools that are always included from those available for selection\n   */\n  const availableTools = baseTools.filter(\n    (tool) => !alwaysInclude.includes(tool.name)\n  );\n\n  /**\n   * If no tools available for selection, return null\n   */\n  if (availableTools.length === 0) {\n    return undefined;\n  }\n\n  let systemMessage = systemPrompt;\n  /**\n   * If there's a maxTools limit, append instructions to the system prompt\n   */\n  if (maxTools !== undefined) {\n    systemMessage +=\n      `\\nIMPORTANT: List the tool names in order of relevance, ` +\n      `with the most relevant first. ` +\n      `If you exceed the maximum number of tools, ` +\n      `only the first ${maxTools} will be used.`;\n  }\n\n  /**\n   * Get the last user message from the conversation history\n   */\n  let lastUserMessage: HumanMessage | undefined;\n  for (const message of request.messages) {\n    if (HumanMessage.isInstance(message)) {\n      lastUserMessage = message;\n    }\n  }\n\n  if (!lastUserMessage) {\n    throw new Error(\"No user message found in request messages\");\n  }\n\n  const modelInstance = !model\n    ? (request.model as BaseLanguageModel)\n    : typeof model === \"string\"\n      ? await initChatModel(model)\n      : model;\n\n  const validToolNames = availableTools.map((tool) => tool.name);\n\n  return {\n    availableTools,\n    systemMessage,\n    lastUserMessage,\n    model: modelInstance,\n    validToolNames,\n  };\n}\n\n/**\n * Process the selection response and return filtered ModelRequest.\n *\n * @param response - The structured output response from the model.\n * @param availableTools - Tools available for selection.\n * @param validToolNames - Valid tool names that can be selected.\n * @param request - Original model request.\n * @param options - Configuration options.\n * @returns Modified ModelRequest with filtered tools.\n */\nfunction processSelectionResponse<\n  TState extends Record<string, unknown> = Record<string, unknown>,\n  TContext = unknown,\n>(\n  response: { tools: string[] },\n  availableTools: StructuredToolInterface[],\n  validToolNames: string[],\n  request: ModelRequest<TState, TContext>,\n  options: LLMToolSelectorConfig\n): ModelRequest<TState, TContext> {\n  const maxTools = options.maxTools;\n  const alwaysInclude = options.alwaysInclude ?? [];\n\n  const selectedToolNames: string[] = [];\n  const invalidToolSelections: string[] = [];\n\n  for (const toolName of response.tools) {\n    if (!validToolNames.includes(toolName)) {\n      invalidToolSelections.push(toolName);\n      continue;\n    }\n\n    /**\n     * Only add if not already selected and within maxTools limit\n     */\n    if (\n      !selectedToolNames.includes(toolName) &&\n      (maxTools === undefined || selectedToolNames.length < maxTools)\n    ) {\n      selectedToolNames.push(toolName);\n    }\n  }\n\n  if (invalidToolSelections.length > 0) {\n    throw new Error(\n      `Model selected invalid tools: ${invalidToolSelections.join(\", \")}`\n    );\n  }\n\n  /**\n   * Filter tools based on selection\n   */\n  const selectedTools = availableTools.filter((tool) =>\n    selectedToolNames.includes(tool.name)\n  );\n\n  /**\n   * Append always-included tools\n   */\n  const alwaysIncludedTools = (request.tools ?? []).filter(\n    (tool): tool is StructuredToolInterface =>\n      typeof tool === \"object\" &&\n      \"name\" in tool &&\n      typeof tool.name === \"string\" &&\n      alwaysInclude.includes(tool.name)\n  );\n  selectedTools.push(...alwaysIncludedTools);\n\n  /**\n   * Also preserve any provider-specific tool dicts from the original request\n   */\n  const providerTools = (request.tools ?? []).filter(\n    (tool) =>\n      !(\n        typeof tool === \"object\" &&\n        \"name\" in tool &&\n        \"description\" in tool &&\n        typeof tool.name === \"string\"\n      )\n  );\n\n  return {\n    ...request,\n    tools: [...selectedTools, ...providerTools],\n  };\n}\n"],"mappings":"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"}