UNPKG

langchain

Version:
1 lines • 8.12 kB
{"version":3,"file":"toolEmulator.cjs","names":["initChatModel","createMiddleware","pickRunnableConfigKeys","mergeConfigs","INTERNAL_CALL_TAG","HumanMessage","ToolMessage"],"sources":["../../../src/agents/middleware/toolEmulator.ts"],"sourcesContent":["import { HumanMessage, ToolMessage } from \"@langchain/core/messages\";\nimport type { BaseChatModel } from \"@langchain/core/language_models/chat_models\";\nimport type { ClientTool, ServerTool } from \"@langchain/core/tools\";\nimport { initChatModel } from \"../../chat_models/universal.js\";\nimport {\n mergeConfigs,\n pickRunnableConfigKeys,\n type RunnableConfig,\n} from \"@langchain/core/runnables\";\nimport { createMiddleware } from \"../middleware.js\";\nimport { INTERNAL_CALL_TAG } from \"./constants.js\";\n\n/**\n * Options for configuring the Tool Emulator middleware.\n */\nexport interface ToolEmulatorOptions {\n /**\n * List of tool names (string) or tool instances to emulate.\n * - If `undefined` (default), ALL tools will be emulated.\n * - If empty array, no tools will be emulated.\n * - If array with tool names/instances, only those tools will be emulated.\n */\n tools?: (string | ClientTool | ServerTool)[];\n\n /**\n * Model to use for emulation.\n * - Can be a model identifier string (e.g., \"anthropic:claude-sonnet-4-5-20250929\")\n * - Can be a BaseChatModel instance\n * - Defaults to agent model\n */\n model?: string | BaseChatModel;\n}\n\n/**\n * Middleware that emulates specified tools using an LLM instead of executing them.\n *\n * This middleware allows selective emulation of tools for testing purposes.\n * By default (when `tools` is undefined), all tools are emulated. You can specify\n * which tools to emulate by passing a list of tool names or tool instances.\n *\n * @param options - Configuration options for the middleware\n * @param options.tools - List of tool names or tool instances to emulate. If undefined, all tools are emulated.\n * @param options.model - Model to use for emulation. Defaults to \"anthropic:claude-sonnet-4-5-20250929\".\n *\n * @example Emulate all tools (default behavior)\n * ```ts\n * import { toolEmulatorMiddleware } from \"@langchain/langchain/agents/middleware\";\n * import { createAgent } from \"@langchain/langchain/agents\";\n *\n * const middleware = toolEmulatorMiddleware();\n *\n * const agent = createAgent({\n * model: \"openai:gpt-4o\",\n * tools: [getWeather, getUserLocation, calculator],\n * middleware: [middleware],\n * });\n * ```\n *\n * @example Emulate specific tools by name\n * ```ts\n * const middleware = toolEmulatorMiddleware({\n * tools: [\"get_weather\", \"get_user_location\"]\n * });\n * ```\n *\n * @example Use a custom model for emulation\n * ```ts\n * const middleware = toolEmulatorMiddleware({\n * tools: [\"get_weather\"],\n * model: \"anthropic:claude-sonnet-4-5-20250929\"\n * });\n * ```\n *\n * @example Emulate specific tools by passing tool instances\n * ```ts\n * const middleware = toolEmulatorMiddleware({\n * tools: [getWeather, getUserLocation]\n * });\n * ```\n */\nexport function toolEmulatorMiddleware(options: ToolEmulatorOptions = {}) {\n let agentModel: BaseChatModel | undefined;\n const { tools, model } = options;\n\n /**\n * Extract tool names from tools\n */\n const emulateAll = !tools || tools.length === 0;\n const toolsToEmulate = new Set<string>();\n\n if (!emulateAll && tools) {\n for (const tool of tools) {\n if (typeof tool === \"string\") {\n toolsToEmulate.add(tool);\n } else {\n // Assume tool instance with .name property\n const toolName =\n typeof tool.name === \"string\" ? tool.name : String(tool.name);\n toolsToEmulate.add(toolName);\n }\n }\n }\n\n /**\n * Initialize emulator model\n * We'll initialize it lazily in wrapToolCall to handle async initChatModel\n */\n let emulatorModel: BaseChatModel | undefined;\n const getEmulatorModel = async (): Promise<BaseChatModel> => {\n if (typeof model === \"object\") {\n return model;\n }\n if (typeof model === \"string\") {\n emulatorModel =\n emulatorModel ??\n (await initChatModel(model, { temperature: 1 }).catch((err) => {\n console.error(\n \"Error initializing emulator model, using agent model:\",\n err\n );\n return agentModel as BaseChatModel;\n }));\n return emulatorModel ?? (agentModel as BaseChatModel);\n }\n return agentModel as BaseChatModel;\n };\n\n return createMiddleware({\n name: \"ToolEmulatorMiddleware\",\n wrapModelCall: async (request, handler) => {\n agentModel = request.model as BaseChatModel;\n return handler(request);\n },\n wrapToolCall: async (request, handler) => {\n const toolName = request.toolCall.name;\n\n // Check if this tool should be emulated\n const shouldEmulate = emulateAll || toolsToEmulate.has(toolName);\n\n if (!shouldEmulate) {\n // Let it execute normally by calling the handler\n return handler(request);\n }\n\n // Extract tool information for emulation\n const toolArgs = request.toolCall.args;\n const toolDescription =\n request.tool?.description || \"No description available\";\n\n // Build prompt for emulator LLM\n const toolArgsString =\n typeof toolArgs === \"string\" ? toolArgs : JSON.stringify(toolArgs);\n const prompt = `You are emulating a tool call for testing purposes.\n\nTool: ${toolName}\nDescription: ${toolDescription}\nArguments: ${toolArgsString}\n\nGenerate a realistic response that this tool would return given these arguments.\nReturn ONLY the tool's output, no explanation or preamble. Introduce variation into your responses.`;\n\n // Get emulated response from LLM\n const emulator = await getEmulatorModel();\n const baseConfig: RunnableConfig =\n pickRunnableConfigKeys(request.runtime) ?? {};\n const config = mergeConfigs(baseConfig, {\n metadata: { lc_source: \"toolEmulation\" },\n tags: [INTERNAL_CALL_TAG],\n });\n const response = await emulator.invoke(\n [new HumanMessage(prompt)],\n config\n );\n\n // Extract content from response\n const content =\n typeof response.content === \"string\"\n ? response.content\n : JSON.stringify(response.content);\n\n // Short-circuit: return emulated result without executing real tool\n return new ToolMessage({\n content,\n tool_call_id: request.toolCall.id ?? \"\",\n name: toolName,\n });\n },\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgFA,SAAgB,uBAAuB,UAA+B,CAAC,GAAG;CACxE,IAAI;CACJ,MAAM,EAAE,OAAO,UAAU;;;;CAKzB,MAAM,aAAa,CAAC,SAAS,MAAM,WAAW;CAC9C,MAAM,iCAAiB,IAAI,IAAY;CAEvC,IAAI,CAAC,cAAc,OACjB,KAAK,MAAM,QAAQ,OACjB,IAAI,OAAO,SAAS,UAClB,eAAe,IAAI,IAAI;MAClB;EAEL,MAAM,WACJ,OAAO,KAAK,SAAS,WAAW,KAAK,OAAO,OAAO,KAAK,IAAI;EAC9D,eAAe,IAAI,QAAQ;CAC7B;;;;;CAQJ,IAAI;CACJ,MAAM,mBAAmB,YAAoC;EAC3D,IAAI,OAAO,UAAU,UACnB,OAAO;EAET,IAAI,OAAO,UAAU,UAAU;GAC7B,gBACE,iBACC,MAAMA,8BAAAA,cAAc,OAAO,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,OAAO,QAAQ;IAC7D,QAAQ,MACN,yDACA,GACF;IACA,OAAO;GACT,CAAC;GACH,OAAO,iBAAkB;EAC3B;EACA,OAAO;CACT;CAEA,OAAOC,mBAAAA,iBAAiB;EACtB,MAAM;EACN,eAAe,OAAO,SAAS,YAAY;GACzC,aAAa,QAAQ;GACrB,OAAO,QAAQ,OAAO;EACxB;EACA,cAAc,OAAO,SAAS,YAAY;GACxC,MAAM,WAAW,QAAQ,SAAS;GAKlC,IAAI,EAFkB,cAAc,eAAe,IAAI,QAAQ,IAI7D,OAAO,QAAQ,OAAO;GAIxB,MAAM,WAAW,QAAQ,SAAS;GAOlC,MAAM,SAAS;;QAEb,SAAS;eAPT,QAAQ,MAAM,eAAe,2BAQN;aAJvB,OAAO,aAAa,WAAW,WAAW,KAAK,UAAU,QAAQ,EAK7C;;;;GAMtB,MAAM,WAAW,MAAM,iBAAiB;GACxC,MAAM,cAAA,GACJC,0BAAAA,uBAAAA,CAAuB,QAAQ,OAAO,KAAK,CAAC;GAC9C,MAAM,UAAA,GAASC,0BAAAA,aAAAA,CAAa,YAAY;IACtC,UAAU,EAAE,WAAW,gBAAgB;IACvC,MAAM,CAACC,kBAAAA,iBAAiB;GAC1B,CAAC;GACD,MAAM,WAAW,MAAM,SAAS,OAC9B,CAAC,IAAIC,yBAAAA,aAAa,MAAM,CAAC,GACzB,MACF;GAGA,MAAM,UACJ,OAAO,SAAS,YAAY,WACxB,SAAS,UACT,KAAK,UAAU,SAAS,OAAO;GAGrC,OAAO,IAAIC,yBAAAA,YAAY;IACrB;IACA,cAAc,QAAQ,SAAS,MAAM;IACrC,MAAM;GACR,CAAC;EACH;CACF,CAAC;AACH"}