langchain 1.4.5-dev-1781048185730 → 1.4.6-dev-1781485641139

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/agents/ReactAgent.cjs +5 -2
  3. package/dist/agents/ReactAgent.cjs.map +1 -1
  4. package/dist/agents/ReactAgent.d.cts +1 -1
  5. package/dist/agents/ReactAgent.d.cts.map +1 -1
  6. package/dist/agents/ReactAgent.d.ts +1 -1
  7. package/dist/agents/ReactAgent.d.ts.map +1 -1
  8. package/dist/agents/ReactAgent.js +4 -1
  9. package/dist/agents/ReactAgent.js.map +1 -1
  10. package/dist/agents/index.cjs +3 -1
  11. package/dist/agents/index.cjs.map +1 -1
  12. package/dist/agents/index.d.cts +3 -1
  13. package/dist/agents/index.d.cts.map +1 -1
  14. package/dist/agents/index.d.ts +3 -1
  15. package/dist/agents/index.d.ts.map +1 -1
  16. package/dist/agents/index.js +3 -1
  17. package/dist/agents/index.js.map +1 -1
  18. package/dist/agents/middleware/constants.cjs +25 -0
  19. package/dist/agents/middleware/constants.cjs.map +1 -1
  20. package/dist/agents/middleware/constants.js +25 -0
  21. package/dist/agents/middleware/constants.js.map +1 -1
  22. package/dist/agents/middleware/contextEditing.cjs.map +1 -1
  23. package/dist/agents/middleware/contextEditing.d.cts.map +1 -1
  24. package/dist/agents/middleware/contextEditing.d.ts.map +1 -1
  25. package/dist/agents/middleware/contextEditing.js.map +1 -1
  26. package/dist/agents/middleware/hitl.cjs +68 -29
  27. package/dist/agents/middleware/hitl.cjs.map +1 -1
  28. package/dist/agents/middleware/hitl.d.cts +2 -105
  29. package/dist/agents/middleware/hitl.d.cts.map +1 -1
  30. package/dist/agents/middleware/hitl.d.ts +2 -105
  31. package/dist/agents/middleware/hitl.d.ts.map +1 -1
  32. package/dist/agents/middleware/hitl.js +68 -29
  33. package/dist/agents/middleware/hitl.js.map +1 -1
  34. package/dist/agents/middleware/index.cjs +1 -0
  35. package/dist/agents/middleware/index.d.cts +2 -1
  36. package/dist/agents/middleware/index.d.ts +2 -1
  37. package/dist/agents/middleware/index.js +1 -0
  38. package/dist/agents/middleware/llmToolSelector.cjs +14 -0
  39. package/dist/agents/middleware/llmToolSelector.cjs.map +1 -1
  40. package/dist/agents/middleware/llmToolSelector.js +14 -0
  41. package/dist/agents/middleware/llmToolSelector.js.map +1 -1
  42. package/dist/agents/middleware/modelCallLimit.cjs +12 -0
  43. package/dist/agents/middleware/modelCallLimit.cjs.map +1 -1
  44. package/dist/agents/middleware/modelCallLimit.js +12 -0
  45. package/dist/agents/middleware/modelCallLimit.js.map +1 -1
  46. package/dist/agents/middleware/modelRetry.cjs +10 -1
  47. package/dist/agents/middleware/modelRetry.cjs.map +1 -1
  48. package/dist/agents/middleware/modelRetry.js +10 -1
  49. package/dist/agents/middleware/modelRetry.js.map +1 -1
  50. package/dist/agents/middleware/pii.cjs +9 -0
  51. package/dist/agents/middleware/pii.cjs.map +1 -1
  52. package/dist/agents/middleware/pii.js +9 -0
  53. package/dist/agents/middleware/pii.js.map +1 -1
  54. package/dist/agents/middleware/piiRedaction.cjs +6 -1
  55. package/dist/agents/middleware/piiRedaction.cjs.map +1 -1
  56. package/dist/agents/middleware/piiRedaction.js +6 -1
  57. package/dist/agents/middleware/piiRedaction.js.map +1 -1
  58. package/dist/agents/middleware/provider/anthropic/promptCaching.cjs +19 -0
  59. package/dist/agents/middleware/provider/anthropic/promptCaching.cjs.map +1 -1
  60. package/dist/agents/middleware/provider/anthropic/promptCaching.js +19 -0
  61. package/dist/agents/middleware/provider/anthropic/promptCaching.js.map +1 -1
  62. package/dist/agents/middleware/provider/openai/moderation.cjs.map +1 -1
  63. package/dist/agents/middleware/provider/openai/moderation.js.map +1 -1
  64. package/dist/agents/middleware/providerToolSearch.cjs +130 -0
  65. package/dist/agents/middleware/providerToolSearch.cjs.map +1 -0
  66. package/dist/agents/middleware/providerToolSearch.d.cts +75 -0
  67. package/dist/agents/middleware/providerToolSearch.d.cts.map +1 -0
  68. package/dist/agents/middleware/providerToolSearch.d.ts +75 -0
  69. package/dist/agents/middleware/providerToolSearch.d.ts.map +1 -0
  70. package/dist/agents/middleware/providerToolSearch.js +129 -0
  71. package/dist/agents/middleware/providerToolSearch.js.map +1 -0
  72. package/dist/agents/middleware/summarization.cjs +62 -1
  73. package/dist/agents/middleware/summarization.cjs.map +1 -1
  74. package/dist/agents/middleware/summarization.js +62 -1
  75. package/dist/agents/middleware/summarization.js.map +1 -1
  76. package/dist/agents/middleware/todoListMiddleware.cjs.map +1 -1
  77. package/dist/agents/middleware/todoListMiddleware.js.map +1 -1
  78. package/dist/agents/middleware/toolCallLimit.cjs +24 -0
  79. package/dist/agents/middleware/toolCallLimit.cjs.map +1 -1
  80. package/dist/agents/middleware/toolCallLimit.js +24 -0
  81. package/dist/agents/middleware/toolCallLimit.js.map +1 -1
  82. package/dist/agents/middleware/toolEmulator.cjs.map +1 -1
  83. package/dist/agents/middleware/toolEmulator.js.map +1 -1
  84. package/dist/agents/middleware/toolRetry.cjs +17 -0
  85. package/dist/agents/middleware/toolRetry.cjs.map +1 -1
  86. package/dist/agents/middleware/toolRetry.js +17 -0
  87. package/dist/agents/middleware/toolRetry.js.map +1 -1
  88. package/dist/agents/middleware.cjs.map +1 -1
  89. package/dist/agents/middleware.js.map +1 -1
  90. package/dist/agents/nodes/AfterAgentNode.cjs.map +1 -1
  91. package/dist/agents/nodes/AfterAgentNode.js.map +1 -1
  92. package/dist/agents/nodes/AfterModelNode.cjs.map +1 -1
  93. package/dist/agents/nodes/AfterModelNode.js.map +1 -1
  94. package/dist/agents/nodes/AgentNode.cjs +17 -0
  95. package/dist/agents/nodes/AgentNode.cjs.map +1 -1
  96. package/dist/agents/nodes/AgentNode.js +17 -0
  97. package/dist/agents/nodes/AgentNode.js.map +1 -1
  98. package/dist/agents/nodes/BeforeAgentNode.cjs.map +1 -1
  99. package/dist/agents/nodes/BeforeAgentNode.js.map +1 -1
  100. package/dist/agents/nodes/BeforeModelNode.cjs.map +1 -1
  101. package/dist/agents/nodes/BeforeModelNode.js.map +1 -1
  102. package/dist/agents/nodes/ToolNode.cjs +3 -0
  103. package/dist/agents/nodes/ToolNode.cjs.map +1 -1
  104. package/dist/agents/nodes/ToolNode.js +3 -0
  105. package/dist/agents/nodes/ToolNode.js.map +1 -1
  106. package/dist/agents/nodes/middleware.cjs +3 -0
  107. package/dist/agents/nodes/middleware.cjs.map +1 -1
  108. package/dist/agents/nodes/middleware.js +3 -0
  109. package/dist/agents/nodes/middleware.js.map +1 -1
  110. package/dist/agents/nodes/utils.cjs.map +1 -1
  111. package/dist/agents/nodes/utils.js.map +1 -1
  112. package/dist/agents/responses.cjs.map +1 -1
  113. package/dist/agents/responses.js.map +1 -1
  114. package/dist/agents/tests/utils.cjs.map +1 -1
  115. package/dist/agents/tests/utils.js.map +1 -1
  116. package/dist/agents/transformers/index.cjs +2 -0
  117. package/dist/agents/transformers/index.d.cts +3 -0
  118. package/dist/agents/transformers/index.d.ts +3 -0
  119. package/dist/agents/transformers/index.js +3 -0
  120. package/dist/agents/transformers/subagent.cjs +205 -0
  121. package/dist/agents/transformers/subagent.cjs.map +1 -0
  122. package/dist/agents/transformers/subagent.d.cts +34 -0
  123. package/dist/agents/transformers/subagent.d.cts.map +1 -0
  124. package/dist/agents/transformers/subagent.d.ts +34 -0
  125. package/dist/agents/transformers/subagent.d.ts.map +1 -0
  126. package/dist/agents/transformers/subagent.js +204 -0
  127. package/dist/agents/transformers/subagent.js.map +1 -0
  128. package/dist/agents/{stream.cjs → transformers/tool-call.cjs} +3 -15
  129. package/dist/agents/transformers/tool-call.cjs.map +1 -0
  130. package/dist/agents/transformers/tool-call.d.cts +17 -0
  131. package/dist/agents/transformers/tool-call.d.cts.map +1 -0
  132. package/dist/agents/transformers/tool-call.d.ts +17 -0
  133. package/dist/agents/transformers/tool-call.d.ts.map +1 -0
  134. package/dist/agents/{stream.js → transformers/tool-call.js} +2 -14
  135. package/dist/agents/transformers/tool-call.js.map +1 -0
  136. package/dist/agents/{stream.d.cts → transformers/types.d.cts} +50 -20
  137. package/dist/agents/transformers/types.d.cts.map +1 -0
  138. package/dist/agents/{stream.d.ts → transformers/types.d.ts} +50 -20
  139. package/dist/agents/transformers/types.d.ts.map +1 -0
  140. package/dist/agents/utils.cjs +3 -0
  141. package/dist/agents/utils.cjs.map +1 -1
  142. package/dist/agents/utils.js +3 -0
  143. package/dist/agents/utils.js.map +1 -1
  144. package/dist/browser.cjs +9 -3
  145. package/dist/browser.d.cts +6 -3
  146. package/dist/browser.d.ts +6 -3
  147. package/dist/browser.js +6 -2
  148. package/dist/chat_models/universal.cjs.map +1 -1
  149. package/dist/chat_models/universal.js.map +1 -1
  150. package/dist/hub/index.cjs.map +1 -1
  151. package/dist/hub/index.js.map +1 -1
  152. package/dist/hub/node.cjs.map +1 -1
  153. package/dist/hub/node.js.map +1 -1
  154. package/dist/index.cjs +9 -3
  155. package/dist/index.d.cts +6 -3
  156. package/dist/index.d.ts +6 -3
  157. package/dist/index.js +6 -2
  158. package/dist/storage/encoder_backed.cjs.map +1 -1
  159. package/dist/storage/encoder_backed.js.map +1 -1
  160. package/dist/storage/file_system.cjs.map +1 -1
  161. package/dist/storage/file_system.js.map +1 -1
  162. package/package.json +13 -13
  163. package/dist/agents/stream.cjs.map +0 -1
  164. package/dist/agents/stream.d.cts.map +0 -1
  165. package/dist/agents/stream.d.ts.map +0 -1
  166. package/dist/agents/stream.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"moderation.cjs","names":["initChatModel","AIMessage","HumanMessage","ToolMessage","createMiddleware"],"sources":["../../../../../src/agents/middleware/provider/openai/moderation.ts"],"sourcesContent":["import type { BaseMessage } from \"@langchain/core/messages\";\nimport { AIMessage, HumanMessage, ToolMessage } from \"@langchain/core/messages\";\nimport type { BaseChatModel } from \"@langchain/core/language_models/chat_models\";\nimport { BaseLanguageModel } from \"@langchain/core/language_models/base\";\n\nimport { initChatModel } from \"../../../../chat_models/universal.js\";\nimport { createMiddleware } from \"../../../middleware.js\";\nimport type { MiddlewareResult, AgentMiddleware } from \"../../types.js\";\nimport type { AgentBuiltInState } from \"../../../runtime.js\";\n\n/**\n * OpenAI model interface.\n */\ninterface OpenAIModel extends BaseLanguageModel {\n getName: () => string;\n _getClientOptions: () => unknown;\n client: {\n moderations: {\n create: (\n input: {\n input: string | string[];\n model: string;\n },\n options?: unknown\n ) => Promise<ModerationResponse>;\n };\n };\n}\n\n/**\n * Check if the model is an OpenAI model that supports moderation.\n * @param model - The model to check.\n * @returns Whether the model is an OpenAI model that supports moderation.\n */\nfunction isOpenAIModel(model: unknown): model is OpenAIModel {\n if (\n !model ||\n typeof model !== \"object\" ||\n model === null ||\n !(\"client\" in model) ||\n !(\"_getClientOptions\" in model) ||\n typeof model._getClientOptions !== \"function\"\n ) {\n return false;\n }\n\n /**\n * client may not yet be initialized, so we need to check if the model has a _getClientOptions method.\n */\n model._getClientOptions();\n return (\n typeof model.client === \"object\" &&\n model.client !== null &&\n \"moderations\" in model.client &&\n typeof model.client.moderations === \"object\" &&\n model.client.moderations !== null &&\n \"create\" in model.client.moderations &&\n typeof model.client.moderations.create === \"function\"\n );\n}\n\n/**\n * Stage where a violation occurred.\n */\nexport type ViolationStage = \"input\" | \"output\" | \"tool\";\n\n/**\n * Default template for violation messages.\n */\nconst DEFAULT_VIOLATION_TEMPLATE =\n \"I'm sorry, but I can't comply with that request. It was flagged for {categories}.\";\n\n/**\n * Result of moderation.\n * @see https://platform.openai.com/docs/api-reference/moderations/object\n */\ninterface ModerationResult {\n flagged: boolean;\n categories: Record<string, boolean>;\n category_scores: Record<string, number>;\n category_applied_input_types: Record<string, string[]>;\n}\n\n/**\n * Moderation response.\n * @see https://platform.openai.com/docs/api-reference/moderations/create\n */\ninterface ModerationResponse {\n id: string;\n model: string;\n results: ModerationResult[];\n}\n\ntype ModerationModel =\n | \"omni-moderation-latest\"\n | \"omni-moderation-2024-09-26\"\n | \"text-moderation-latest\"\n | \"text-moderation-stable\";\n\n/**\n * Error raised when OpenAI flags content and `exitBehavior` is set to `\"error\"`.\n */\nexport class OpenAIModerationError extends Error {\n content: string;\n stage: ViolationStage;\n result: ModerationResult;\n originalMessage: string;\n\n constructor({\n content,\n stage,\n result,\n message,\n }: {\n content: string;\n stage: ViolationStage;\n result: ModerationResult;\n message: string;\n }) {\n super(message);\n this.name = \"OpenAIModerationError\";\n this.content = content;\n this.stage = stage;\n this.result = result;\n this.originalMessage = message;\n }\n}\n\n/**\n * Options for configuring the OpenAI Moderation middleware.\n */\nexport interface OpenAIModerationMiddlewareOptions {\n /**\n * OpenAI model to use for moderation. Can be either a model name or a BaseChatModel instance.\n * @example\n * ```ts\n * const model = new ChatOpenAI({ model: \"gpt-4o-mini\" });\n * const middleware = openAIModerationMiddleware({ model });\n * const agent = createAgent({\n * model,\n * middleware: [middleware],\n * });\n * ```\n * @example\n * ```ts\n * const middleware = openAIModerationMiddleware({ model: \"gpt-4o-mini\" });\n * const agent = createAgent({\n * model: \"gpt-5\",\n * middleware: [middleware],\n * });\n * ```\n */\n model: string | BaseChatModel;\n\n /**\n * Moderation model to use.\n * @default \"omni-moderation-latest\"\n */\n moderationModel?: ModerationModel;\n\n /**\n * Whether to check user input messages.\n * @default true\n */\n checkInput?: boolean;\n\n /**\n * Whether to check model output messages.\n * @default true\n */\n checkOutput?: boolean;\n\n /**\n * Whether to check tool result messages.\n * @default false\n */\n checkToolResults?: boolean;\n\n /**\n * How to handle violations.\n * - `\"error\"`: Throw an error when content is flagged\n * - `\"end\"`: End the agent execution and return a violation message\n * - `\"replace\"`: Replace the flagged content with a violation message\n * @default \"end\"\n */\n exitBehavior?: \"error\" | \"end\" | \"replace\";\n\n /**\n * Custom template for violation messages.\n * Available placeholders: `{categories}`, `{category_scores}`, `{original_content}`\n */\n violationMessage?: string;\n}\n\n/**\n * Middleware that moderates agent traffic using OpenAI's moderation endpoint.\n *\n * This middleware checks messages for content policy violations at different stages:\n * - Input: User messages before they reach the model\n * - Output: AI model responses\n * - Tool results: Results returned from tool executions\n *\n * @param options - Configuration options for the middleware\n * @param options.model - OpenAI model to use for moderation. Can be either a model name or a BaseChatModel instance.\n * @param options.moderationModel - Moderation model to use.\n * @param options.checkInput - Whether to check user input messages.\n * @param options.checkOutput - Whether to check model output messages.\n * @param options.checkToolResults - Whether to check tool result messages.\n * @param options.exitBehavior - How to handle violations.\n * @param options.violationMessage - Custom template for violation messages.\n * @returns Middleware function that can be used to moderate agent traffic.\n *\n * @example Using model instance\n * ```ts\n * import { createAgent, openAIModerationMiddleware } from \"langchain\";\n *\n * const middleware = openAIModerationMiddleware({\n * checkInput: true,\n * checkOutput: true,\n * exitBehavior: \"end\"\n * });\n *\n * const agent = createAgent({\n * model: \"openai:gpt-4o\",\n * tools: [...],\n * middleware: [middleware],\n * });\n * ```\n *\n * @example Using model name\n * ```ts\n * import { createAgent, openAIModerationMiddleware } from \"langchain\";\n *\n * const middleware = openAIModerationMiddleware({\n * model: \"gpt-4o-mini\",\n * checkInput: true,\n * checkOutput: true,\n * exitBehavior: \"end\"\n * });\n *\n * const agent = createAgent({\n * model: \"openai:gpt-4o\",\n * tools: [...],\n * middleware: [middleware],\n * });\n * ```\n *\n * @example Custom violation message\n * ```ts\n * const middleware = openAIModerationMiddleware({\n * violationMessage: \"Content flagged: {categories}. Scores: {category_scores}\"\n * });\n * ```\n */\nexport function openAIModerationMiddleware(\n options: OpenAIModerationMiddlewareOptions\n): AgentMiddleware {\n const {\n model,\n moderationModel = \"omni-moderation-latest\",\n checkInput = true,\n checkOutput = true,\n checkToolResults = false,\n exitBehavior = \"end\",\n violationMessage,\n } = options;\n\n let openaiModel: OpenAIModel | undefined;\n const initModerationModel = async (): Promise<OpenAIModel> => {\n if (openaiModel) {\n return openaiModel;\n }\n\n const resolvedModel =\n typeof model === \"string\" ? await initChatModel(model) : model;\n\n /**\n * Check if the model is an OpenAI model.\n */\n if (!resolvedModel.getName().includes(\"ChatOpenAI\")) {\n throw new Error(\n `Model must be an OpenAI model to use moderation middleware. Got: ${resolvedModel.getName()}`\n );\n }\n\n /**\n * check if OpenAI model package supports moderation.\n */\n if (!isOpenAIModel(resolvedModel)) {\n throw new Error(\n \"Model must support moderation to use moderation middleware.\"\n );\n }\n\n openaiModel = resolvedModel as unknown as OpenAIModel;\n return openaiModel;\n };\n\n /**\n * Extract text content from a message.\n */\n const extractText = (message: BaseMessage): string | null => {\n if (message.content == null) {\n return null;\n }\n const text = message.text;\n return text || null;\n };\n\n /**\n * Find the last index of a message type in the messages array.\n */\n const findLastIndex = (\n messages: BaseMessage[],\n messageType: typeof AIMessage | typeof HumanMessage | typeof ToolMessage\n ): number | null => {\n for (let idx = messages.length - 1; idx >= 0; idx--) {\n if (messageType.isInstance(messages[idx])) {\n return idx;\n }\n }\n return null;\n };\n\n /**\n * Format violation message from moderation result.\n */\n const formatViolationMessage = (\n content: string,\n result: ModerationResult\n ): string => {\n // Convert categories to array of flagged category names\n const categories: string[] = [];\n const categoriesObj = result.categories as unknown as Record<\n string,\n boolean\n >;\n for (const [name, flagged] of Object.entries(categoriesObj)) {\n if (flagged) {\n categories.push(name.replace(/_/g, \" \"));\n }\n }\n\n const categoryLabel =\n categories.length > 0\n ? categories.join(\", \")\n : \"OpenAI's safety policies\";\n\n const template = violationMessage || DEFAULT_VIOLATION_TEMPLATE;\n const scoresJson = JSON.stringify(\n result.category_scores as unknown as Record<string, number>,\n null,\n 2\n );\n\n try {\n return template\n .replace(\"{categories}\", categoryLabel)\n .replace(\"{category_scores}\", scoresJson)\n .replace(\"{original_content}\", content);\n } catch {\n return template;\n }\n };\n\n function moderateContent(\n input: string | string[],\n params?: { model?: ModerationModel; options?: unknown }\n ): Promise<ModerationResponse> {\n const clientOptions = openaiModel?._getClientOptions?.();\n const moderationModel = params?.model ?? \"omni-moderation-latest\";\n const moderationRequest = {\n input,\n model: moderationModel,\n };\n return openaiModel!.client.moderations.create(\n moderationRequest,\n clientOptions\n );\n }\n\n /**\n * Apply violation handling based on exit behavior.\n */\n const applyViolation = (\n messages: BaseMessage[],\n index: number | null,\n stage: ViolationStage,\n content: string,\n result: ModerationResult\n ): MiddlewareResult<Partial<AgentBuiltInState>> | undefined => {\n const violationText = formatViolationMessage(content, result);\n\n if (exitBehavior === \"error\") {\n throw new OpenAIModerationError({\n content,\n stage,\n result,\n message: violationText,\n });\n }\n\n if (exitBehavior === \"end\") {\n return {\n jumpTo: \"end\",\n messages: [new AIMessage({ content: violationText })],\n };\n }\n\n if (index == null) {\n return undefined;\n }\n\n /**\n * Replace the original message with a new message that contains the violation text.\n */\n const newMessages = [...messages];\n const original = newMessages[index];\n const MessageConstructor = Object.getPrototypeOf(original).constructor;\n newMessages[index] = new MessageConstructor({\n ...original,\n content: violationText,\n });\n\n return { messages: newMessages };\n };\n\n /**\n * Moderate user input messages.\n */\n const moderateUserMessage = async (\n messages: BaseMessage[]\n ): Promise<MiddlewareResult<Partial<AgentBuiltInState>> | null> => {\n const idx = findLastIndex(messages, HumanMessage);\n if (idx == null) {\n return null;\n }\n\n const message = messages[idx];\n const text = extractText(message);\n if (!text) {\n return null;\n }\n\n await initModerationModel();\n const response = await moderateContent(text, {\n model: moderationModel,\n });\n\n const flaggedResult = response.results.find((result) => result.flagged);\n if (!flaggedResult) {\n return null;\n }\n\n return applyViolation(messages, idx, \"input\", text, flaggedResult);\n };\n\n /**\n * Moderate tool result messages.\n */\n const moderateToolMessages = async (\n messages: BaseMessage[]\n ): Promise<MiddlewareResult<Partial<AgentBuiltInState>> | null> => {\n const lastAiIdx = findLastIndex(messages, AIMessage);\n if (lastAiIdx == null) {\n return null;\n }\n\n const working = [...messages];\n let modified = false;\n\n for (let idx = lastAiIdx + 1; idx < working.length; idx++) {\n const msg = working[idx];\n if (!ToolMessage.isInstance(msg)) {\n continue;\n }\n\n const text = extractText(msg);\n if (!text) {\n continue;\n }\n\n await initModerationModel();\n const response = await moderateContent(text, {\n model: moderationModel,\n });\n const flaggedResult = response.results.find((result) => result.flagged);\n if (!flaggedResult) {\n continue;\n }\n\n const action = applyViolation(working, idx, \"tool\", text, flaggedResult);\n if (action) {\n if (\"jumpTo\" in action) {\n return action;\n }\n if (\"messages\" in action) {\n working.splice(\n 0,\n working.length,\n ...(action.messages as BaseMessage[])\n );\n modified = true;\n }\n }\n }\n\n if (modified) {\n return { messages: working };\n }\n\n return null;\n };\n\n /**\n * Moderate model output messages.\n */\n const moderateOutput = async (\n messages: BaseMessage[]\n ): Promise<MiddlewareResult<Partial<AgentBuiltInState>> | null> => {\n const lastAiIdx = findLastIndex(messages, AIMessage);\n if (lastAiIdx == null) {\n return null;\n }\n\n const aiMessage = messages[lastAiIdx];\n const text = extractText(aiMessage);\n if (!text) {\n return null;\n }\n\n await initModerationModel();\n const response = await moderateContent(text, {\n model: moderationModel,\n });\n const flaggedResult = response.results.find((result) => result.flagged);\n if (!flaggedResult) {\n return null;\n }\n\n return applyViolation(messages, lastAiIdx, \"output\", text, flaggedResult);\n };\n\n /**\n * Moderate inputs (user messages and tool results) before model call.\n */\n const moderateInputs = async (\n messages: BaseMessage[]\n ): Promise<MiddlewareResult<Partial<AgentBuiltInState>> | null> => {\n const working = [...messages];\n let modified = false;\n\n if (checkToolResults) {\n const action = await moderateToolMessages(working);\n if (action) {\n if (\"jumpTo\" in action) {\n return action;\n }\n if (\"messages\" in action) {\n working.splice(\n 0,\n working.length,\n ...(action.messages as BaseMessage[])\n );\n modified = true;\n }\n }\n }\n\n if (checkInput) {\n const action = await moderateUserMessage(working);\n if (action) {\n if (\"jumpTo\" in action) {\n return action;\n }\n if (\"messages\" in action) {\n working.splice(\n 0,\n working.length,\n ...(action.messages as BaseMessage[])\n );\n modified = true;\n }\n }\n }\n\n if (modified) {\n return { messages: working };\n }\n\n return null;\n };\n\n return createMiddleware({\n name: \"OpenAIModerationMiddleware\",\n beforeModel: {\n hook: async (\n state\n ): Promise<MiddlewareResult<Partial<AgentBuiltInState>> | undefined> => {\n if (!checkInput && !checkToolResults) {\n return undefined;\n }\n\n const messages = state.messages || [];\n if (messages.length === 0) {\n return undefined;\n }\n\n return (await moderateInputs(messages)) ?? undefined;\n },\n canJumpTo: [\"end\"],\n },\n afterModel: {\n hook: async (\n state\n ): Promise<MiddlewareResult<Partial<AgentBuiltInState>> | undefined> => {\n if (!checkOutput) {\n return undefined;\n }\n\n const messages = state.messages || [];\n if (messages.length === 0) {\n return undefined;\n }\n\n return (await moderateOutput(messages)) ?? undefined;\n },\n canJumpTo: [\"end\"],\n },\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"moderation.cjs","names":["initChatModel","AIMessage","HumanMessage","ToolMessage","createMiddleware"],"sources":["../../../../../src/agents/middleware/provider/openai/moderation.ts"],"sourcesContent":["import type { BaseMessage } from \"@langchain/core/messages\";\nimport { AIMessage, HumanMessage, ToolMessage } from \"@langchain/core/messages\";\nimport type { BaseChatModel } from \"@langchain/core/language_models/chat_models\";\nimport { BaseLanguageModel } from \"@langchain/core/language_models/base\";\n\nimport { initChatModel } from \"../../../../chat_models/universal.js\";\nimport { createMiddleware } from \"../../../middleware.js\";\nimport type { MiddlewareResult, AgentMiddleware } from \"../../types.js\";\nimport type { AgentBuiltInState } from \"../../../runtime.js\";\n\n/**\n * OpenAI model interface.\n */\ninterface OpenAIModel extends BaseLanguageModel {\n getName: () => string;\n _getClientOptions: () => unknown;\n client: {\n moderations: {\n create: (\n input: {\n input: string | string[];\n model: string;\n },\n options?: unknown\n ) => Promise<ModerationResponse>;\n };\n };\n}\n\n/**\n * Check if the model is an OpenAI model that supports moderation.\n * @param model - The model to check.\n * @returns Whether the model is an OpenAI model that supports moderation.\n */\nfunction isOpenAIModel(model: unknown): model is OpenAIModel {\n if (\n !model ||\n typeof model !== \"object\" ||\n model === null ||\n !(\"client\" in model) ||\n !(\"_getClientOptions\" in model) ||\n typeof model._getClientOptions !== \"function\"\n ) {\n return false;\n }\n\n /**\n * client may not yet be initialized, so we need to check if the model has a _getClientOptions method.\n */\n model._getClientOptions();\n return (\n typeof model.client === \"object\" &&\n model.client !== null &&\n \"moderations\" in model.client &&\n typeof model.client.moderations === \"object\" &&\n model.client.moderations !== null &&\n \"create\" in model.client.moderations &&\n typeof model.client.moderations.create === \"function\"\n );\n}\n\n/**\n * Stage where a violation occurred.\n */\nexport type ViolationStage = \"input\" | \"output\" | \"tool\";\n\n/**\n * Default template for violation messages.\n */\nconst DEFAULT_VIOLATION_TEMPLATE =\n \"I'm sorry, but I can't comply with that request. It was flagged for {categories}.\";\n\n/**\n * Result of moderation.\n * @see https://platform.openai.com/docs/api-reference/moderations/object\n */\ninterface ModerationResult {\n flagged: boolean;\n categories: Record<string, boolean>;\n category_scores: Record<string, number>;\n category_applied_input_types: Record<string, string[]>;\n}\n\n/**\n * Moderation response.\n * @see https://platform.openai.com/docs/api-reference/moderations/create\n */\ninterface ModerationResponse {\n id: string;\n model: string;\n results: ModerationResult[];\n}\n\ntype ModerationModel =\n | \"omni-moderation-latest\"\n | \"omni-moderation-2024-09-26\"\n | \"text-moderation-latest\"\n | \"text-moderation-stable\";\n\n/**\n * Error raised when OpenAI flags content and `exitBehavior` is set to `\"error\"`.\n */\nexport class OpenAIModerationError extends Error {\n content: string;\n stage: ViolationStage;\n result: ModerationResult;\n originalMessage: string;\n\n constructor({\n content,\n stage,\n result,\n message,\n }: {\n content: string;\n stage: ViolationStage;\n result: ModerationResult;\n message: string;\n }) {\n super(message);\n this.name = \"OpenAIModerationError\";\n this.content = content;\n this.stage = stage;\n this.result = result;\n this.originalMessage = message;\n }\n}\n\n/**\n * Options for configuring the OpenAI Moderation middleware.\n */\nexport interface OpenAIModerationMiddlewareOptions {\n /**\n * OpenAI model to use for moderation. Can be either a model name or a BaseChatModel instance.\n * @example\n * ```ts\n * const model = new ChatOpenAI({ model: \"gpt-4o-mini\" });\n * const middleware = openAIModerationMiddleware({ model });\n * const agent = createAgent({\n * model,\n * middleware: [middleware],\n * });\n * ```\n * @example\n * ```ts\n * const middleware = openAIModerationMiddleware({ model: \"gpt-4o-mini\" });\n * const agent = createAgent({\n * model: \"gpt-5\",\n * middleware: [middleware],\n * });\n * ```\n */\n model: string | BaseChatModel;\n\n /**\n * Moderation model to use.\n * @default \"omni-moderation-latest\"\n */\n moderationModel?: ModerationModel;\n\n /**\n * Whether to check user input messages.\n * @default true\n */\n checkInput?: boolean;\n\n /**\n * Whether to check model output messages.\n * @default true\n */\n checkOutput?: boolean;\n\n /**\n * Whether to check tool result messages.\n * @default false\n */\n checkToolResults?: boolean;\n\n /**\n * How to handle violations.\n * - `\"error\"`: Throw an error when content is flagged\n * - `\"end\"`: End the agent execution and return a violation message\n * - `\"replace\"`: Replace the flagged content with a violation message\n * @default \"end\"\n */\n exitBehavior?: \"error\" | \"end\" | \"replace\";\n\n /**\n * Custom template for violation messages.\n * Available placeholders: `{categories}`, `{category_scores}`, `{original_content}`\n */\n violationMessage?: string;\n}\n\n/**\n * Middleware that moderates agent traffic using OpenAI's moderation endpoint.\n *\n * This middleware checks messages for content policy violations at different stages:\n * - Input: User messages before they reach the model\n * - Output: AI model responses\n * - Tool results: Results returned from tool executions\n *\n * @param options - Configuration options for the middleware\n * @param options.model - OpenAI model to use for moderation. Can be either a model name or a BaseChatModel instance.\n * @param options.moderationModel - Moderation model to use.\n * @param options.checkInput - Whether to check user input messages.\n * @param options.checkOutput - Whether to check model output messages.\n * @param options.checkToolResults - Whether to check tool result messages.\n * @param options.exitBehavior - How to handle violations.\n * @param options.violationMessage - Custom template for violation messages.\n * @returns Middleware function that can be used to moderate agent traffic.\n *\n * @example Using model instance\n * ```ts\n * import { createAgent, openAIModerationMiddleware } from \"langchain\";\n *\n * const middleware = openAIModerationMiddleware({\n * checkInput: true,\n * checkOutput: true,\n * exitBehavior: \"end\"\n * });\n *\n * const agent = createAgent({\n * model: \"openai:gpt-4o\",\n * tools: [...],\n * middleware: [middleware],\n * });\n * ```\n *\n * @example Using model name\n * ```ts\n * import { createAgent, openAIModerationMiddleware } from \"langchain\";\n *\n * const middleware = openAIModerationMiddleware({\n * model: \"gpt-4o-mini\",\n * checkInput: true,\n * checkOutput: true,\n * exitBehavior: \"end\"\n * });\n *\n * const agent = createAgent({\n * model: \"openai:gpt-4o\",\n * tools: [...],\n * middleware: [middleware],\n * });\n * ```\n *\n * @example Custom violation message\n * ```ts\n * const middleware = openAIModerationMiddleware({\n * violationMessage: \"Content flagged: {categories}. Scores: {category_scores}\"\n * });\n * ```\n */\nexport function openAIModerationMiddleware(\n options: OpenAIModerationMiddlewareOptions\n): AgentMiddleware {\n const {\n model,\n moderationModel = \"omni-moderation-latest\",\n checkInput = true,\n checkOutput = true,\n checkToolResults = false,\n exitBehavior = \"end\",\n violationMessage,\n } = options;\n\n let openaiModel: OpenAIModel | undefined;\n const initModerationModel = async (): Promise<OpenAIModel> => {\n if (openaiModel) {\n return openaiModel;\n }\n\n const resolvedModel =\n typeof model === \"string\" ? await initChatModel(model) : model;\n\n /**\n * Check if the model is an OpenAI model.\n */\n if (!resolvedModel.getName().includes(\"ChatOpenAI\")) {\n throw new Error(\n `Model must be an OpenAI model to use moderation middleware. Got: ${resolvedModel.getName()}`\n );\n }\n\n /**\n * check if OpenAI model package supports moderation.\n */\n if (!isOpenAIModel(resolvedModel)) {\n throw new Error(\n \"Model must support moderation to use moderation middleware.\"\n );\n }\n\n openaiModel = resolvedModel as unknown as OpenAIModel;\n return openaiModel;\n };\n\n /**\n * Extract text content from a message.\n */\n const extractText = (message: BaseMessage): string | null => {\n if (message.content == null) {\n return null;\n }\n const text = message.text;\n return text || null;\n };\n\n /**\n * Find the last index of a message type in the messages array.\n */\n const findLastIndex = (\n messages: BaseMessage[],\n messageType: typeof AIMessage | typeof HumanMessage | typeof ToolMessage\n ): number | null => {\n for (let idx = messages.length - 1; idx >= 0; idx--) {\n if (messageType.isInstance(messages[idx])) {\n return idx;\n }\n }\n return null;\n };\n\n /**\n * Format violation message from moderation result.\n */\n const formatViolationMessage = (\n content: string,\n result: ModerationResult\n ): string => {\n // Convert categories to array of flagged category names\n const categories: string[] = [];\n const categoriesObj = result.categories as unknown as Record<\n string,\n boolean\n >;\n for (const [name, flagged] of Object.entries(categoriesObj)) {\n if (flagged) {\n categories.push(name.replace(/_/g, \" \"));\n }\n }\n\n const categoryLabel =\n categories.length > 0\n ? categories.join(\", \")\n : \"OpenAI's safety policies\";\n\n const template = violationMessage || DEFAULT_VIOLATION_TEMPLATE;\n const scoresJson = JSON.stringify(\n result.category_scores as unknown as Record<string, number>,\n null,\n 2\n );\n\n try {\n return template\n .replace(\"{categories}\", categoryLabel)\n .replace(\"{category_scores}\", scoresJson)\n .replace(\"{original_content}\", content);\n } catch {\n return template;\n }\n };\n\n function moderateContent(\n input: string | string[],\n params?: { model?: ModerationModel; options?: unknown }\n ): Promise<ModerationResponse> {\n const clientOptions = openaiModel?._getClientOptions?.();\n const moderationModel = params?.model ?? \"omni-moderation-latest\";\n const moderationRequest = {\n input,\n model: moderationModel,\n };\n return openaiModel!.client.moderations.create(\n moderationRequest,\n clientOptions\n );\n }\n\n /**\n * Apply violation handling based on exit behavior.\n */\n const applyViolation = (\n messages: BaseMessage[],\n index: number | null,\n stage: ViolationStage,\n content: string,\n result: ModerationResult\n ): MiddlewareResult<Partial<AgentBuiltInState>> | undefined => {\n const violationText = formatViolationMessage(content, result);\n\n if (exitBehavior === \"error\") {\n throw new OpenAIModerationError({\n content,\n stage,\n result,\n message: violationText,\n });\n }\n\n if (exitBehavior === \"end\") {\n return {\n jumpTo: \"end\",\n messages: [new AIMessage({ content: violationText })],\n };\n }\n\n if (index == null) {\n return undefined;\n }\n\n /**\n * Replace the original message with a new message that contains the violation text.\n */\n const newMessages = [...messages];\n const original = newMessages[index];\n const MessageConstructor = Object.getPrototypeOf(original).constructor;\n newMessages[index] = new MessageConstructor({\n ...original,\n content: violationText,\n });\n\n return { messages: newMessages };\n };\n\n /**\n * Moderate user input messages.\n */\n const moderateUserMessage = async (\n messages: BaseMessage[]\n ): Promise<MiddlewareResult<Partial<AgentBuiltInState>> | null> => {\n const idx = findLastIndex(messages, HumanMessage);\n if (idx == null) {\n return null;\n }\n\n const message = messages[idx];\n const text = extractText(message);\n if (!text) {\n return null;\n }\n\n await initModerationModel();\n const response = await moderateContent(text, {\n model: moderationModel,\n });\n\n const flaggedResult = response.results.find((result) => result.flagged);\n if (!flaggedResult) {\n return null;\n }\n\n return applyViolation(messages, idx, \"input\", text, flaggedResult);\n };\n\n /**\n * Moderate tool result messages.\n */\n const moderateToolMessages = async (\n messages: BaseMessage[]\n ): Promise<MiddlewareResult<Partial<AgentBuiltInState>> | null> => {\n const lastAiIdx = findLastIndex(messages, AIMessage);\n if (lastAiIdx == null) {\n return null;\n }\n\n const working = [...messages];\n let modified = false;\n\n for (let idx = lastAiIdx + 1; idx < working.length; idx++) {\n const msg = working[idx];\n if (!ToolMessage.isInstance(msg)) {\n continue;\n }\n\n const text = extractText(msg);\n if (!text) {\n continue;\n }\n\n await initModerationModel();\n const response = await moderateContent(text, {\n model: moderationModel,\n });\n const flaggedResult = response.results.find((result) => result.flagged);\n if (!flaggedResult) {\n continue;\n }\n\n const action = applyViolation(working, idx, \"tool\", text, flaggedResult);\n if (action) {\n if (\"jumpTo\" in action) {\n return action;\n }\n if (\"messages\" in action) {\n working.splice(\n 0,\n working.length,\n ...(action.messages as BaseMessage[])\n );\n modified = true;\n }\n }\n }\n\n if (modified) {\n return { messages: working };\n }\n\n return null;\n };\n\n /**\n * Moderate model output messages.\n */\n const moderateOutput = async (\n messages: BaseMessage[]\n ): Promise<MiddlewareResult<Partial<AgentBuiltInState>> | null> => {\n const lastAiIdx = findLastIndex(messages, AIMessage);\n if (lastAiIdx == null) {\n return null;\n }\n\n const aiMessage = messages[lastAiIdx];\n const text = extractText(aiMessage);\n if (!text) {\n return null;\n }\n\n await initModerationModel();\n const response = await moderateContent(text, {\n model: moderationModel,\n });\n const flaggedResult = response.results.find((result) => result.flagged);\n if (!flaggedResult) {\n return null;\n }\n\n return applyViolation(messages, lastAiIdx, \"output\", text, flaggedResult);\n };\n\n /**\n * Moderate inputs (user messages and tool results) before model call.\n */\n const moderateInputs = async (\n messages: BaseMessage[]\n ): Promise<MiddlewareResult<Partial<AgentBuiltInState>> | null> => {\n const working = [...messages];\n let modified = false;\n\n if (checkToolResults) {\n const action = await moderateToolMessages(working);\n if (action) {\n if (\"jumpTo\" in action) {\n return action;\n }\n if (\"messages\" in action) {\n working.splice(\n 0,\n working.length,\n ...(action.messages as BaseMessage[])\n );\n modified = true;\n }\n }\n }\n\n if (checkInput) {\n const action = await moderateUserMessage(working);\n if (action) {\n if (\"jumpTo\" in action) {\n return action;\n }\n if (\"messages\" in action) {\n working.splice(\n 0,\n working.length,\n ...(action.messages as BaseMessage[])\n );\n modified = true;\n }\n }\n }\n\n if (modified) {\n return { messages: working };\n }\n\n return null;\n };\n\n return createMiddleware({\n name: \"OpenAIModerationMiddleware\",\n beforeModel: {\n hook: async (\n state\n ): Promise<MiddlewareResult<Partial<AgentBuiltInState>> | undefined> => {\n if (!checkInput && !checkToolResults) {\n return undefined;\n }\n\n const messages = state.messages || [];\n if (messages.length === 0) {\n return undefined;\n }\n\n return (await moderateInputs(messages)) ?? undefined;\n },\n canJumpTo: [\"end\"],\n },\n afterModel: {\n hook: async (\n state\n ): Promise<MiddlewareResult<Partial<AgentBuiltInState>> | undefined> => {\n if (!checkOutput) {\n return undefined;\n }\n\n const messages = state.messages || [];\n if (messages.length === 0) {\n return undefined;\n }\n\n return (await moderateOutput(messages)) ?? undefined;\n },\n canJumpTo: [\"end\"],\n },\n });\n}\n"],"mappings":";;;;;;;;;;AAkCA,SAAS,cAAc,OAAsC;AAC3D,KACE,CAAC,SACD,OAAO,UAAU,YACjB,UAAU,QACV,EAAE,YAAY,UACd,EAAE,uBAAuB,UACzB,OAAO,MAAM,sBAAsB,WAEnC,QAAO;;;;AAMT,OAAM,mBAAmB;AACzB,QACE,OAAO,MAAM,WAAW,YACxB,MAAM,WAAW,QACjB,iBAAiB,MAAM,UACvB,OAAO,MAAM,OAAO,gBAAgB,YACpC,MAAM,OAAO,gBAAgB,QAC7B,YAAY,MAAM,OAAO,eACzB,OAAO,MAAM,OAAO,YAAY,WAAW;;;;;AAY/C,MAAM,6BACJ;;;;AAgCF,IAAa,wBAAb,cAA2C,MAAM;CAC/C;CACA;CACA;CACA;CAEA,YAAY,EACV,SACA,OACA,QACA,WAMC;AACD,QAAM,QAAQ;AACd,OAAK,OAAO;AACZ,OAAK,UAAU;AACf,OAAK,QAAQ;AACb,OAAK,SAAS;AACd,OAAK,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkI3B,SAAgB,2BACd,SACiB;CACjB,MAAM,EACJ,OACA,kBAAkB,0BAClB,aAAa,MACb,cAAc,MACd,mBAAmB,OACnB,eAAe,OACf,qBACE;CAEJ,IAAI;CACJ,MAAM,sBAAsB,YAAkC;AAC5D,MAAI,YACF,QAAO;EAGT,MAAM,gBACJ,OAAO,UAAU,WAAW,MAAMA,8BAAAA,cAAc,MAAM,GAAG;;;;AAK3D,MAAI,CAAC,cAAc,SAAS,CAAC,SAAS,aAAa,CACjD,OAAM,IAAI,MACR,oEAAoE,cAAc,SAAS,GAC5F;;;;AAMH,MAAI,CAAC,cAAc,cAAc,CAC/B,OAAM,IAAI,MACR,8DACD;AAGH,gBAAc;AACd,SAAO;;;;;CAMT,MAAM,eAAe,YAAwC;AAC3D,MAAI,QAAQ,WAAW,KACrB,QAAO;AAGT,SADa,QAAQ,QACN;;;;;CAMjB,MAAM,iBACJ,UACA,gBACkB;AAClB,OAAK,IAAI,MAAM,SAAS,SAAS,GAAG,OAAO,GAAG,MAC5C,KAAI,YAAY,WAAW,SAAS,KAAK,CACvC,QAAO;AAGX,SAAO;;;;;CAMT,MAAM,0BACJ,SACA,WACW;EAEX,MAAM,aAAuB,EAAE;EAC/B,MAAM,gBAAgB,OAAO;AAI7B,OAAK,MAAM,CAAC,MAAM,YAAY,OAAO,QAAQ,cAAc,CACzD,KAAI,QACF,YAAW,KAAK,KAAK,QAAQ,MAAM,IAAI,CAAC;EAI5C,MAAM,gBACJ,WAAW,SAAS,IAChB,WAAW,KAAK,KAAK,GACrB;EAEN,MAAM,WAAW,oBAAoB;EACrC,MAAM,aAAa,KAAK,UACtB,OAAO,iBACP,MACA,EACD;AAED,MAAI;AACF,UAAO,SACJ,QAAQ,gBAAgB,cAAc,CACtC,QAAQ,qBAAqB,WAAW,CACxC,QAAQ,sBAAsB,QAAQ;UACnC;AACN,UAAO;;;CAIX,SAAS,gBACP,OACA,QAC6B;EAC7B,MAAM,gBAAgB,aAAa,qBAAqB;EAExD,MAAM,oBAAoB;GACxB;GACA,OAHsB,QAAQ,SAAS;GAIxC;AACD,SAAO,YAAa,OAAO,YAAY,OACrC,mBACA,cACD;;;;;CAMH,MAAM,kBACJ,UACA,OACA,OACA,SACA,WAC6D;EAC7D,MAAM,gBAAgB,uBAAuB,SAAS,OAAO;AAE7D,MAAI,iBAAiB,QACnB,OAAM,IAAI,sBAAsB;GAC9B;GACA;GACA;GACA,SAAS;GACV,CAAC;AAGJ,MAAI,iBAAiB,MACnB,QAAO;GACL,QAAQ;GACR,UAAU,CAAC,IAAIC,yBAAAA,UAAU,EAAE,SAAS,eAAe,CAAC,CAAC;GACtD;AAGH,MAAI,SAAS,KACX;;;;EAMF,MAAM,cAAc,CAAC,GAAG,SAAS;EACjC,MAAM,WAAW,YAAY;EAC7B,MAAM,qBAAqB,OAAO,eAAe,SAAS,CAAC;AAC3D,cAAY,SAAS,IAAI,mBAAmB;GAC1C,GAAG;GACH,SAAS;GACV,CAAC;AAEF,SAAO,EAAE,UAAU,aAAa;;;;;CAMlC,MAAM,sBAAsB,OAC1B,aACiE;EACjE,MAAM,MAAM,cAAc,UAAUC,yBAAAA,aAAa;AACjD,MAAI,OAAO,KACT,QAAO;EAGT,MAAM,UAAU,SAAS;EACzB,MAAM,OAAO,YAAY,QAAQ;AACjC,MAAI,CAAC,KACH,QAAO;AAGT,QAAM,qBAAqB;EAK3B,MAAM,iBAAgB,MAJC,gBAAgB,MAAM,EAC3C,OAAO,iBACR,CAAC,EAE6B,QAAQ,MAAM,WAAW,OAAO,QAAQ;AACvE,MAAI,CAAC,cACH,QAAO;AAGT,SAAO,eAAe,UAAU,KAAK,SAAS,MAAM,cAAc;;;;;CAMpE,MAAM,uBAAuB,OAC3B,aACiE;EACjE,MAAM,YAAY,cAAc,UAAUD,yBAAAA,UAAU;AACpD,MAAI,aAAa,KACf,QAAO;EAGT,MAAM,UAAU,CAAC,GAAG,SAAS;EAC7B,IAAI,WAAW;AAEf,OAAK,IAAI,MAAM,YAAY,GAAG,MAAM,QAAQ,QAAQ,OAAO;GACzD,MAAM,MAAM,QAAQ;AACpB,OAAI,CAACE,yBAAAA,YAAY,WAAW,IAAI,CAC9B;GAGF,MAAM,OAAO,YAAY,IAAI;AAC7B,OAAI,CAAC,KACH;AAGF,SAAM,qBAAqB;GAI3B,MAAM,iBAAgB,MAHC,gBAAgB,MAAM,EAC3C,OAAO,iBACR,CAAC,EAC6B,QAAQ,MAAM,WAAW,OAAO,QAAQ;AACvE,OAAI,CAAC,cACH;GAGF,MAAM,SAAS,eAAe,SAAS,KAAK,QAAQ,MAAM,cAAc;AACxE,OAAI,QAAQ;AACV,QAAI,YAAY,OACd,QAAO;AAET,QAAI,cAAc,QAAQ;AACxB,aAAQ,OACN,GACA,QAAQ,QACR,GAAI,OAAO,SACZ;AACD,gBAAW;;;;AAKjB,MAAI,SACF,QAAO,EAAE,UAAU,SAAS;AAG9B,SAAO;;;;;CAMT,MAAM,iBAAiB,OACrB,aACiE;EACjE,MAAM,YAAY,cAAc,UAAUF,yBAAAA,UAAU;AACpD,MAAI,aAAa,KACf,QAAO;EAGT,MAAM,YAAY,SAAS;EAC3B,MAAM,OAAO,YAAY,UAAU;AACnC,MAAI,CAAC,KACH,QAAO;AAGT,QAAM,qBAAqB;EAI3B,MAAM,iBAAgB,MAHC,gBAAgB,MAAM,EAC3C,OAAO,iBACR,CAAC,EAC6B,QAAQ,MAAM,WAAW,OAAO,QAAQ;AACvE,MAAI,CAAC,cACH,QAAO;AAGT,SAAO,eAAe,UAAU,WAAW,UAAU,MAAM,cAAc;;;;;CAM3E,MAAM,iBAAiB,OACrB,aACiE;EACjE,MAAM,UAAU,CAAC,GAAG,SAAS;EAC7B,IAAI,WAAW;AAEf,MAAI,kBAAkB;GACpB,MAAM,SAAS,MAAM,qBAAqB,QAAQ;AAClD,OAAI,QAAQ;AACV,QAAI,YAAY,OACd,QAAO;AAET,QAAI,cAAc,QAAQ;AACxB,aAAQ,OACN,GACA,QAAQ,QACR,GAAI,OAAO,SACZ;AACD,gBAAW;;;;AAKjB,MAAI,YAAY;GACd,MAAM,SAAS,MAAM,oBAAoB,QAAQ;AACjD,OAAI,QAAQ;AACV,QAAI,YAAY,OACd,QAAO;AAET,QAAI,cAAc,QAAQ;AACxB,aAAQ,OACN,GACA,QAAQ,QACR,GAAI,OAAO,SACZ;AACD,gBAAW;;;;AAKjB,MAAI,SACF,QAAO,EAAE,UAAU,SAAS;AAG9B,SAAO;;AAGT,QAAOG,mBAAAA,iBAAiB;EACtB,MAAM;EACN,aAAa;GACX,MAAM,OACJ,UACsE;AACtE,QAAI,CAAC,cAAc,CAAC,iBAClB;IAGF,MAAM,WAAW,MAAM,YAAY,EAAE;AACrC,QAAI,SAAS,WAAW,EACtB;AAGF,WAAQ,MAAM,eAAe,SAAS,IAAK,KAAA;;GAE7C,WAAW,CAAC,MAAM;GACnB;EACD,YAAY;GACV,MAAM,OACJ,UACsE;AACtE,QAAI,CAAC,YACH;IAGF,MAAM,WAAW,MAAM,YAAY,EAAE;AACrC,QAAI,SAAS,WAAW,EACtB;AAGF,WAAQ,MAAM,eAAe,SAAS,IAAK,KAAA;;GAE7C,WAAW,CAAC,MAAM;GACnB;EACF,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"moderation.js","names":[],"sources":["../../../../../src/agents/middleware/provider/openai/moderation.ts"],"sourcesContent":["import type { BaseMessage } from \"@langchain/core/messages\";\nimport { AIMessage, HumanMessage, ToolMessage } from \"@langchain/core/messages\";\nimport type { BaseChatModel } from \"@langchain/core/language_models/chat_models\";\nimport { BaseLanguageModel } from \"@langchain/core/language_models/base\";\n\nimport { initChatModel } from \"../../../../chat_models/universal.js\";\nimport { createMiddleware } from \"../../../middleware.js\";\nimport type { MiddlewareResult, AgentMiddleware } from \"../../types.js\";\nimport type { AgentBuiltInState } from \"../../../runtime.js\";\n\n/**\n * OpenAI model interface.\n */\ninterface OpenAIModel extends BaseLanguageModel {\n getName: () => string;\n _getClientOptions: () => unknown;\n client: {\n moderations: {\n create: (\n input: {\n input: string | string[];\n model: string;\n },\n options?: unknown\n ) => Promise<ModerationResponse>;\n };\n };\n}\n\n/**\n * Check if the model is an OpenAI model that supports moderation.\n * @param model - The model to check.\n * @returns Whether the model is an OpenAI model that supports moderation.\n */\nfunction isOpenAIModel(model: unknown): model is OpenAIModel {\n if (\n !model ||\n typeof model !== \"object\" ||\n model === null ||\n !(\"client\" in model) ||\n !(\"_getClientOptions\" in model) ||\n typeof model._getClientOptions !== \"function\"\n ) {\n return false;\n }\n\n /**\n * client may not yet be initialized, so we need to check if the model has a _getClientOptions method.\n */\n model._getClientOptions();\n return (\n typeof model.client === \"object\" &&\n model.client !== null &&\n \"moderations\" in model.client &&\n typeof model.client.moderations === \"object\" &&\n model.client.moderations !== null &&\n \"create\" in model.client.moderations &&\n typeof model.client.moderations.create === \"function\"\n );\n}\n\n/**\n * Stage where a violation occurred.\n */\nexport type ViolationStage = \"input\" | \"output\" | \"tool\";\n\n/**\n * Default template for violation messages.\n */\nconst DEFAULT_VIOLATION_TEMPLATE =\n \"I'm sorry, but I can't comply with that request. It was flagged for {categories}.\";\n\n/**\n * Result of moderation.\n * @see https://platform.openai.com/docs/api-reference/moderations/object\n */\ninterface ModerationResult {\n flagged: boolean;\n categories: Record<string, boolean>;\n category_scores: Record<string, number>;\n category_applied_input_types: Record<string, string[]>;\n}\n\n/**\n * Moderation response.\n * @see https://platform.openai.com/docs/api-reference/moderations/create\n */\ninterface ModerationResponse {\n id: string;\n model: string;\n results: ModerationResult[];\n}\n\ntype ModerationModel =\n | \"omni-moderation-latest\"\n | \"omni-moderation-2024-09-26\"\n | \"text-moderation-latest\"\n | \"text-moderation-stable\";\n\n/**\n * Error raised when OpenAI flags content and `exitBehavior` is set to `\"error\"`.\n */\nexport class OpenAIModerationError extends Error {\n content: string;\n stage: ViolationStage;\n result: ModerationResult;\n originalMessage: string;\n\n constructor({\n content,\n stage,\n result,\n message,\n }: {\n content: string;\n stage: ViolationStage;\n result: ModerationResult;\n message: string;\n }) {\n super(message);\n this.name = \"OpenAIModerationError\";\n this.content = content;\n this.stage = stage;\n this.result = result;\n this.originalMessage = message;\n }\n}\n\n/**\n * Options for configuring the OpenAI Moderation middleware.\n */\nexport interface OpenAIModerationMiddlewareOptions {\n /**\n * OpenAI model to use for moderation. Can be either a model name or a BaseChatModel instance.\n * @example\n * ```ts\n * const model = new ChatOpenAI({ model: \"gpt-4o-mini\" });\n * const middleware = openAIModerationMiddleware({ model });\n * const agent = createAgent({\n * model,\n * middleware: [middleware],\n * });\n * ```\n * @example\n * ```ts\n * const middleware = openAIModerationMiddleware({ model: \"gpt-4o-mini\" });\n * const agent = createAgent({\n * model: \"gpt-5\",\n * middleware: [middleware],\n * });\n * ```\n */\n model: string | BaseChatModel;\n\n /**\n * Moderation model to use.\n * @default \"omni-moderation-latest\"\n */\n moderationModel?: ModerationModel;\n\n /**\n * Whether to check user input messages.\n * @default true\n */\n checkInput?: boolean;\n\n /**\n * Whether to check model output messages.\n * @default true\n */\n checkOutput?: boolean;\n\n /**\n * Whether to check tool result messages.\n * @default false\n */\n checkToolResults?: boolean;\n\n /**\n * How to handle violations.\n * - `\"error\"`: Throw an error when content is flagged\n * - `\"end\"`: End the agent execution and return a violation message\n * - `\"replace\"`: Replace the flagged content with a violation message\n * @default \"end\"\n */\n exitBehavior?: \"error\" | \"end\" | \"replace\";\n\n /**\n * Custom template for violation messages.\n * Available placeholders: `{categories}`, `{category_scores}`, `{original_content}`\n */\n violationMessage?: string;\n}\n\n/**\n * Middleware that moderates agent traffic using OpenAI's moderation endpoint.\n *\n * This middleware checks messages for content policy violations at different stages:\n * - Input: User messages before they reach the model\n * - Output: AI model responses\n * - Tool results: Results returned from tool executions\n *\n * @param options - Configuration options for the middleware\n * @param options.model - OpenAI model to use for moderation. Can be either a model name or a BaseChatModel instance.\n * @param options.moderationModel - Moderation model to use.\n * @param options.checkInput - Whether to check user input messages.\n * @param options.checkOutput - Whether to check model output messages.\n * @param options.checkToolResults - Whether to check tool result messages.\n * @param options.exitBehavior - How to handle violations.\n * @param options.violationMessage - Custom template for violation messages.\n * @returns Middleware function that can be used to moderate agent traffic.\n *\n * @example Using model instance\n * ```ts\n * import { createAgent, openAIModerationMiddleware } from \"langchain\";\n *\n * const middleware = openAIModerationMiddleware({\n * checkInput: true,\n * checkOutput: true,\n * exitBehavior: \"end\"\n * });\n *\n * const agent = createAgent({\n * model: \"openai:gpt-4o\",\n * tools: [...],\n * middleware: [middleware],\n * });\n * ```\n *\n * @example Using model name\n * ```ts\n * import { createAgent, openAIModerationMiddleware } from \"langchain\";\n *\n * const middleware = openAIModerationMiddleware({\n * model: \"gpt-4o-mini\",\n * checkInput: true,\n * checkOutput: true,\n * exitBehavior: \"end\"\n * });\n *\n * const agent = createAgent({\n * model: \"openai:gpt-4o\",\n * tools: [...],\n * middleware: [middleware],\n * });\n * ```\n *\n * @example Custom violation message\n * ```ts\n * const middleware = openAIModerationMiddleware({\n * violationMessage: \"Content flagged: {categories}. Scores: {category_scores}\"\n * });\n * ```\n */\nexport function openAIModerationMiddleware(\n options: OpenAIModerationMiddlewareOptions\n): AgentMiddleware {\n const {\n model,\n moderationModel = \"omni-moderation-latest\",\n checkInput = true,\n checkOutput = true,\n checkToolResults = false,\n exitBehavior = \"end\",\n violationMessage,\n } = options;\n\n let openaiModel: OpenAIModel | undefined;\n const initModerationModel = async (): Promise<OpenAIModel> => {\n if (openaiModel) {\n return openaiModel;\n }\n\n const resolvedModel =\n typeof model === \"string\" ? await initChatModel(model) : model;\n\n /**\n * Check if the model is an OpenAI model.\n */\n if (!resolvedModel.getName().includes(\"ChatOpenAI\")) {\n throw new Error(\n `Model must be an OpenAI model to use moderation middleware. Got: ${resolvedModel.getName()}`\n );\n }\n\n /**\n * check if OpenAI model package supports moderation.\n */\n if (!isOpenAIModel(resolvedModel)) {\n throw new Error(\n \"Model must support moderation to use moderation middleware.\"\n );\n }\n\n openaiModel = resolvedModel as unknown as OpenAIModel;\n return openaiModel;\n };\n\n /**\n * Extract text content from a message.\n */\n const extractText = (message: BaseMessage): string | null => {\n if (message.content == null) {\n return null;\n }\n const text = message.text;\n return text || null;\n };\n\n /**\n * Find the last index of a message type in the messages array.\n */\n const findLastIndex = (\n messages: BaseMessage[],\n messageType: typeof AIMessage | typeof HumanMessage | typeof ToolMessage\n ): number | null => {\n for (let idx = messages.length - 1; idx >= 0; idx--) {\n if (messageType.isInstance(messages[idx])) {\n return idx;\n }\n }\n return null;\n };\n\n /**\n * Format violation message from moderation result.\n */\n const formatViolationMessage = (\n content: string,\n result: ModerationResult\n ): string => {\n // Convert categories to array of flagged category names\n const categories: string[] = [];\n const categoriesObj = result.categories as unknown as Record<\n string,\n boolean\n >;\n for (const [name, flagged] of Object.entries(categoriesObj)) {\n if (flagged) {\n categories.push(name.replace(/_/g, \" \"));\n }\n }\n\n const categoryLabel =\n categories.length > 0\n ? categories.join(\", \")\n : \"OpenAI's safety policies\";\n\n const template = violationMessage || DEFAULT_VIOLATION_TEMPLATE;\n const scoresJson = JSON.stringify(\n result.category_scores as unknown as Record<string, number>,\n null,\n 2\n );\n\n try {\n return template\n .replace(\"{categories}\", categoryLabel)\n .replace(\"{category_scores}\", scoresJson)\n .replace(\"{original_content}\", content);\n } catch {\n return template;\n }\n };\n\n function moderateContent(\n input: string | string[],\n params?: { model?: ModerationModel; options?: unknown }\n ): Promise<ModerationResponse> {\n const clientOptions = openaiModel?._getClientOptions?.();\n const moderationModel = params?.model ?? \"omni-moderation-latest\";\n const moderationRequest = {\n input,\n model: moderationModel,\n };\n return openaiModel!.client.moderations.create(\n moderationRequest,\n clientOptions\n );\n }\n\n /**\n * Apply violation handling based on exit behavior.\n */\n const applyViolation = (\n messages: BaseMessage[],\n index: number | null,\n stage: ViolationStage,\n content: string,\n result: ModerationResult\n ): MiddlewareResult<Partial<AgentBuiltInState>> | undefined => {\n const violationText = formatViolationMessage(content, result);\n\n if (exitBehavior === \"error\") {\n throw new OpenAIModerationError({\n content,\n stage,\n result,\n message: violationText,\n });\n }\n\n if (exitBehavior === \"end\") {\n return {\n jumpTo: \"end\",\n messages: [new AIMessage({ content: violationText })],\n };\n }\n\n if (index == null) {\n return undefined;\n }\n\n /**\n * Replace the original message with a new message that contains the violation text.\n */\n const newMessages = [...messages];\n const original = newMessages[index];\n const MessageConstructor = Object.getPrototypeOf(original).constructor;\n newMessages[index] = new MessageConstructor({\n ...original,\n content: violationText,\n });\n\n return { messages: newMessages };\n };\n\n /**\n * Moderate user input messages.\n */\n const moderateUserMessage = async (\n messages: BaseMessage[]\n ): Promise<MiddlewareResult<Partial<AgentBuiltInState>> | null> => {\n const idx = findLastIndex(messages, HumanMessage);\n if (idx == null) {\n return null;\n }\n\n const message = messages[idx];\n const text = extractText(message);\n if (!text) {\n return null;\n }\n\n await initModerationModel();\n const response = await moderateContent(text, {\n model: moderationModel,\n });\n\n const flaggedResult = response.results.find((result) => result.flagged);\n if (!flaggedResult) {\n return null;\n }\n\n return applyViolation(messages, idx, \"input\", text, flaggedResult);\n };\n\n /**\n * Moderate tool result messages.\n */\n const moderateToolMessages = async (\n messages: BaseMessage[]\n ): Promise<MiddlewareResult<Partial<AgentBuiltInState>> | null> => {\n const lastAiIdx = findLastIndex(messages, AIMessage);\n if (lastAiIdx == null) {\n return null;\n }\n\n const working = [...messages];\n let modified = false;\n\n for (let idx = lastAiIdx + 1; idx < working.length; idx++) {\n const msg = working[idx];\n if (!ToolMessage.isInstance(msg)) {\n continue;\n }\n\n const text = extractText(msg);\n if (!text) {\n continue;\n }\n\n await initModerationModel();\n const response = await moderateContent(text, {\n model: moderationModel,\n });\n const flaggedResult = response.results.find((result) => result.flagged);\n if (!flaggedResult) {\n continue;\n }\n\n const action = applyViolation(working, idx, \"tool\", text, flaggedResult);\n if (action) {\n if (\"jumpTo\" in action) {\n return action;\n }\n if (\"messages\" in action) {\n working.splice(\n 0,\n working.length,\n ...(action.messages as BaseMessage[])\n );\n modified = true;\n }\n }\n }\n\n if (modified) {\n return { messages: working };\n }\n\n return null;\n };\n\n /**\n * Moderate model output messages.\n */\n const moderateOutput = async (\n messages: BaseMessage[]\n ): Promise<MiddlewareResult<Partial<AgentBuiltInState>> | null> => {\n const lastAiIdx = findLastIndex(messages, AIMessage);\n if (lastAiIdx == null) {\n return null;\n }\n\n const aiMessage = messages[lastAiIdx];\n const text = extractText(aiMessage);\n if (!text) {\n return null;\n }\n\n await initModerationModel();\n const response = await moderateContent(text, {\n model: moderationModel,\n });\n const flaggedResult = response.results.find((result) => result.flagged);\n if (!flaggedResult) {\n return null;\n }\n\n return applyViolation(messages, lastAiIdx, \"output\", text, flaggedResult);\n };\n\n /**\n * Moderate inputs (user messages and tool results) before model call.\n */\n const moderateInputs = async (\n messages: BaseMessage[]\n ): Promise<MiddlewareResult<Partial<AgentBuiltInState>> | null> => {\n const working = [...messages];\n let modified = false;\n\n if (checkToolResults) {\n const action = await moderateToolMessages(working);\n if (action) {\n if (\"jumpTo\" in action) {\n return action;\n }\n if (\"messages\" in action) {\n working.splice(\n 0,\n working.length,\n ...(action.messages as BaseMessage[])\n );\n modified = true;\n }\n }\n }\n\n if (checkInput) {\n const action = await moderateUserMessage(working);\n if (action) {\n if (\"jumpTo\" in action) {\n return action;\n }\n if (\"messages\" in action) {\n working.splice(\n 0,\n working.length,\n ...(action.messages as BaseMessage[])\n );\n modified = true;\n }\n }\n }\n\n if (modified) {\n return { messages: working };\n }\n\n return null;\n };\n\n return createMiddleware({\n name: \"OpenAIModerationMiddleware\",\n beforeModel: {\n hook: async (\n state\n ): Promise<MiddlewareResult<Partial<AgentBuiltInState>> | undefined> => {\n if (!checkInput && !checkToolResults) {\n return undefined;\n }\n\n const messages = state.messages || [];\n if (messages.length === 0) {\n return undefined;\n }\n\n return (await moderateInputs(messages)) ?? undefined;\n },\n canJumpTo: [\"end\"],\n },\n afterModel: {\n hook: async (\n state\n ): Promise<MiddlewareResult<Partial<AgentBuiltInState>> | undefined> => {\n if (!checkOutput) {\n return undefined;\n }\n\n const messages = state.messages || [];\n if (messages.length === 0) {\n return undefined;\n }\n\n return (await moderateOutput(messages)) ?? undefined;\n },\n canJumpTo: [\"end\"],\n },\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"moderation.js","names":[],"sources":["../../../../../src/agents/middleware/provider/openai/moderation.ts"],"sourcesContent":["import type { BaseMessage } from \"@langchain/core/messages\";\nimport { AIMessage, HumanMessage, ToolMessage } from \"@langchain/core/messages\";\nimport type { BaseChatModel } from \"@langchain/core/language_models/chat_models\";\nimport { BaseLanguageModel } from \"@langchain/core/language_models/base\";\n\nimport { initChatModel } from \"../../../../chat_models/universal.js\";\nimport { createMiddleware } from \"../../../middleware.js\";\nimport type { MiddlewareResult, AgentMiddleware } from \"../../types.js\";\nimport type { AgentBuiltInState } from \"../../../runtime.js\";\n\n/**\n * OpenAI model interface.\n */\ninterface OpenAIModel extends BaseLanguageModel {\n getName: () => string;\n _getClientOptions: () => unknown;\n client: {\n moderations: {\n create: (\n input: {\n input: string | string[];\n model: string;\n },\n options?: unknown\n ) => Promise<ModerationResponse>;\n };\n };\n}\n\n/**\n * Check if the model is an OpenAI model that supports moderation.\n * @param model - The model to check.\n * @returns Whether the model is an OpenAI model that supports moderation.\n */\nfunction isOpenAIModel(model: unknown): model is OpenAIModel {\n if (\n !model ||\n typeof model !== \"object\" ||\n model === null ||\n !(\"client\" in model) ||\n !(\"_getClientOptions\" in model) ||\n typeof model._getClientOptions !== \"function\"\n ) {\n return false;\n }\n\n /**\n * client may not yet be initialized, so we need to check if the model has a _getClientOptions method.\n */\n model._getClientOptions();\n return (\n typeof model.client === \"object\" &&\n model.client !== null &&\n \"moderations\" in model.client &&\n typeof model.client.moderations === \"object\" &&\n model.client.moderations !== null &&\n \"create\" in model.client.moderations &&\n typeof model.client.moderations.create === \"function\"\n );\n}\n\n/**\n * Stage where a violation occurred.\n */\nexport type ViolationStage = \"input\" | \"output\" | \"tool\";\n\n/**\n * Default template for violation messages.\n */\nconst DEFAULT_VIOLATION_TEMPLATE =\n \"I'm sorry, but I can't comply with that request. It was flagged for {categories}.\";\n\n/**\n * Result of moderation.\n * @see https://platform.openai.com/docs/api-reference/moderations/object\n */\ninterface ModerationResult {\n flagged: boolean;\n categories: Record<string, boolean>;\n category_scores: Record<string, number>;\n category_applied_input_types: Record<string, string[]>;\n}\n\n/**\n * Moderation response.\n * @see https://platform.openai.com/docs/api-reference/moderations/create\n */\ninterface ModerationResponse {\n id: string;\n model: string;\n results: ModerationResult[];\n}\n\ntype ModerationModel =\n | \"omni-moderation-latest\"\n | \"omni-moderation-2024-09-26\"\n | \"text-moderation-latest\"\n | \"text-moderation-stable\";\n\n/**\n * Error raised when OpenAI flags content and `exitBehavior` is set to `\"error\"`.\n */\nexport class OpenAIModerationError extends Error {\n content: string;\n stage: ViolationStage;\n result: ModerationResult;\n originalMessage: string;\n\n constructor({\n content,\n stage,\n result,\n message,\n }: {\n content: string;\n stage: ViolationStage;\n result: ModerationResult;\n message: string;\n }) {\n super(message);\n this.name = \"OpenAIModerationError\";\n this.content = content;\n this.stage = stage;\n this.result = result;\n this.originalMessage = message;\n }\n}\n\n/**\n * Options for configuring the OpenAI Moderation middleware.\n */\nexport interface OpenAIModerationMiddlewareOptions {\n /**\n * OpenAI model to use for moderation. Can be either a model name or a BaseChatModel instance.\n * @example\n * ```ts\n * const model = new ChatOpenAI({ model: \"gpt-4o-mini\" });\n * const middleware = openAIModerationMiddleware({ model });\n * const agent = createAgent({\n * model,\n * middleware: [middleware],\n * });\n * ```\n * @example\n * ```ts\n * const middleware = openAIModerationMiddleware({ model: \"gpt-4o-mini\" });\n * const agent = createAgent({\n * model: \"gpt-5\",\n * middleware: [middleware],\n * });\n * ```\n */\n model: string | BaseChatModel;\n\n /**\n * Moderation model to use.\n * @default \"omni-moderation-latest\"\n */\n moderationModel?: ModerationModel;\n\n /**\n * Whether to check user input messages.\n * @default true\n */\n checkInput?: boolean;\n\n /**\n * Whether to check model output messages.\n * @default true\n */\n checkOutput?: boolean;\n\n /**\n * Whether to check tool result messages.\n * @default false\n */\n checkToolResults?: boolean;\n\n /**\n * How to handle violations.\n * - `\"error\"`: Throw an error when content is flagged\n * - `\"end\"`: End the agent execution and return a violation message\n * - `\"replace\"`: Replace the flagged content with a violation message\n * @default \"end\"\n */\n exitBehavior?: \"error\" | \"end\" | \"replace\";\n\n /**\n * Custom template for violation messages.\n * Available placeholders: `{categories}`, `{category_scores}`, `{original_content}`\n */\n violationMessage?: string;\n}\n\n/**\n * Middleware that moderates agent traffic using OpenAI's moderation endpoint.\n *\n * This middleware checks messages for content policy violations at different stages:\n * - Input: User messages before they reach the model\n * - Output: AI model responses\n * - Tool results: Results returned from tool executions\n *\n * @param options - Configuration options for the middleware\n * @param options.model - OpenAI model to use for moderation. Can be either a model name or a BaseChatModel instance.\n * @param options.moderationModel - Moderation model to use.\n * @param options.checkInput - Whether to check user input messages.\n * @param options.checkOutput - Whether to check model output messages.\n * @param options.checkToolResults - Whether to check tool result messages.\n * @param options.exitBehavior - How to handle violations.\n * @param options.violationMessage - Custom template for violation messages.\n * @returns Middleware function that can be used to moderate agent traffic.\n *\n * @example Using model instance\n * ```ts\n * import { createAgent, openAIModerationMiddleware } from \"langchain\";\n *\n * const middleware = openAIModerationMiddleware({\n * checkInput: true,\n * checkOutput: true,\n * exitBehavior: \"end\"\n * });\n *\n * const agent = createAgent({\n * model: \"openai:gpt-4o\",\n * tools: [...],\n * middleware: [middleware],\n * });\n * ```\n *\n * @example Using model name\n * ```ts\n * import { createAgent, openAIModerationMiddleware } from \"langchain\";\n *\n * const middleware = openAIModerationMiddleware({\n * model: \"gpt-4o-mini\",\n * checkInput: true,\n * checkOutput: true,\n * exitBehavior: \"end\"\n * });\n *\n * const agent = createAgent({\n * model: \"openai:gpt-4o\",\n * tools: [...],\n * middleware: [middleware],\n * });\n * ```\n *\n * @example Custom violation message\n * ```ts\n * const middleware = openAIModerationMiddleware({\n * violationMessage: \"Content flagged: {categories}. Scores: {category_scores}\"\n * });\n * ```\n */\nexport function openAIModerationMiddleware(\n options: OpenAIModerationMiddlewareOptions\n): AgentMiddleware {\n const {\n model,\n moderationModel = \"omni-moderation-latest\",\n checkInput = true,\n checkOutput = true,\n checkToolResults = false,\n exitBehavior = \"end\",\n violationMessage,\n } = options;\n\n let openaiModel: OpenAIModel | undefined;\n const initModerationModel = async (): Promise<OpenAIModel> => {\n if (openaiModel) {\n return openaiModel;\n }\n\n const resolvedModel =\n typeof model === \"string\" ? await initChatModel(model) : model;\n\n /**\n * Check if the model is an OpenAI model.\n */\n if (!resolvedModel.getName().includes(\"ChatOpenAI\")) {\n throw new Error(\n `Model must be an OpenAI model to use moderation middleware. Got: ${resolvedModel.getName()}`\n );\n }\n\n /**\n * check if OpenAI model package supports moderation.\n */\n if (!isOpenAIModel(resolvedModel)) {\n throw new Error(\n \"Model must support moderation to use moderation middleware.\"\n );\n }\n\n openaiModel = resolvedModel as unknown as OpenAIModel;\n return openaiModel;\n };\n\n /**\n * Extract text content from a message.\n */\n const extractText = (message: BaseMessage): string | null => {\n if (message.content == null) {\n return null;\n }\n const text = message.text;\n return text || null;\n };\n\n /**\n * Find the last index of a message type in the messages array.\n */\n const findLastIndex = (\n messages: BaseMessage[],\n messageType: typeof AIMessage | typeof HumanMessage | typeof ToolMessage\n ): number | null => {\n for (let idx = messages.length - 1; idx >= 0; idx--) {\n if (messageType.isInstance(messages[idx])) {\n return idx;\n }\n }\n return null;\n };\n\n /**\n * Format violation message from moderation result.\n */\n const formatViolationMessage = (\n content: string,\n result: ModerationResult\n ): string => {\n // Convert categories to array of flagged category names\n const categories: string[] = [];\n const categoriesObj = result.categories as unknown as Record<\n string,\n boolean\n >;\n for (const [name, flagged] of Object.entries(categoriesObj)) {\n if (flagged) {\n categories.push(name.replace(/_/g, \" \"));\n }\n }\n\n const categoryLabel =\n categories.length > 0\n ? categories.join(\", \")\n : \"OpenAI's safety policies\";\n\n const template = violationMessage || DEFAULT_VIOLATION_TEMPLATE;\n const scoresJson = JSON.stringify(\n result.category_scores as unknown as Record<string, number>,\n null,\n 2\n );\n\n try {\n return template\n .replace(\"{categories}\", categoryLabel)\n .replace(\"{category_scores}\", scoresJson)\n .replace(\"{original_content}\", content);\n } catch {\n return template;\n }\n };\n\n function moderateContent(\n input: string | string[],\n params?: { model?: ModerationModel; options?: unknown }\n ): Promise<ModerationResponse> {\n const clientOptions = openaiModel?._getClientOptions?.();\n const moderationModel = params?.model ?? \"omni-moderation-latest\";\n const moderationRequest = {\n input,\n model: moderationModel,\n };\n return openaiModel!.client.moderations.create(\n moderationRequest,\n clientOptions\n );\n }\n\n /**\n * Apply violation handling based on exit behavior.\n */\n const applyViolation = (\n messages: BaseMessage[],\n index: number | null,\n stage: ViolationStage,\n content: string,\n result: ModerationResult\n ): MiddlewareResult<Partial<AgentBuiltInState>> | undefined => {\n const violationText = formatViolationMessage(content, result);\n\n if (exitBehavior === \"error\") {\n throw new OpenAIModerationError({\n content,\n stage,\n result,\n message: violationText,\n });\n }\n\n if (exitBehavior === \"end\") {\n return {\n jumpTo: \"end\",\n messages: [new AIMessage({ content: violationText })],\n };\n }\n\n if (index == null) {\n return undefined;\n }\n\n /**\n * Replace the original message with a new message that contains the violation text.\n */\n const newMessages = [...messages];\n const original = newMessages[index];\n const MessageConstructor = Object.getPrototypeOf(original).constructor;\n newMessages[index] = new MessageConstructor({\n ...original,\n content: violationText,\n });\n\n return { messages: newMessages };\n };\n\n /**\n * Moderate user input messages.\n */\n const moderateUserMessage = async (\n messages: BaseMessage[]\n ): Promise<MiddlewareResult<Partial<AgentBuiltInState>> | null> => {\n const idx = findLastIndex(messages, HumanMessage);\n if (idx == null) {\n return null;\n }\n\n const message = messages[idx];\n const text = extractText(message);\n if (!text) {\n return null;\n }\n\n await initModerationModel();\n const response = await moderateContent(text, {\n model: moderationModel,\n });\n\n const flaggedResult = response.results.find((result) => result.flagged);\n if (!flaggedResult) {\n return null;\n }\n\n return applyViolation(messages, idx, \"input\", text, flaggedResult);\n };\n\n /**\n * Moderate tool result messages.\n */\n const moderateToolMessages = async (\n messages: BaseMessage[]\n ): Promise<MiddlewareResult<Partial<AgentBuiltInState>> | null> => {\n const lastAiIdx = findLastIndex(messages, AIMessage);\n if (lastAiIdx == null) {\n return null;\n }\n\n const working = [...messages];\n let modified = false;\n\n for (let idx = lastAiIdx + 1; idx < working.length; idx++) {\n const msg = working[idx];\n if (!ToolMessage.isInstance(msg)) {\n continue;\n }\n\n const text = extractText(msg);\n if (!text) {\n continue;\n }\n\n await initModerationModel();\n const response = await moderateContent(text, {\n model: moderationModel,\n });\n const flaggedResult = response.results.find((result) => result.flagged);\n if (!flaggedResult) {\n continue;\n }\n\n const action = applyViolation(working, idx, \"tool\", text, flaggedResult);\n if (action) {\n if (\"jumpTo\" in action) {\n return action;\n }\n if (\"messages\" in action) {\n working.splice(\n 0,\n working.length,\n ...(action.messages as BaseMessage[])\n );\n modified = true;\n }\n }\n }\n\n if (modified) {\n return { messages: working };\n }\n\n return null;\n };\n\n /**\n * Moderate model output messages.\n */\n const moderateOutput = async (\n messages: BaseMessage[]\n ): Promise<MiddlewareResult<Partial<AgentBuiltInState>> | null> => {\n const lastAiIdx = findLastIndex(messages, AIMessage);\n if (lastAiIdx == null) {\n return null;\n }\n\n const aiMessage = messages[lastAiIdx];\n const text = extractText(aiMessage);\n if (!text) {\n return null;\n }\n\n await initModerationModel();\n const response = await moderateContent(text, {\n model: moderationModel,\n });\n const flaggedResult = response.results.find((result) => result.flagged);\n if (!flaggedResult) {\n return null;\n }\n\n return applyViolation(messages, lastAiIdx, \"output\", text, flaggedResult);\n };\n\n /**\n * Moderate inputs (user messages and tool results) before model call.\n */\n const moderateInputs = async (\n messages: BaseMessage[]\n ): Promise<MiddlewareResult<Partial<AgentBuiltInState>> | null> => {\n const working = [...messages];\n let modified = false;\n\n if (checkToolResults) {\n const action = await moderateToolMessages(working);\n if (action) {\n if (\"jumpTo\" in action) {\n return action;\n }\n if (\"messages\" in action) {\n working.splice(\n 0,\n working.length,\n ...(action.messages as BaseMessage[])\n );\n modified = true;\n }\n }\n }\n\n if (checkInput) {\n const action = await moderateUserMessage(working);\n if (action) {\n if (\"jumpTo\" in action) {\n return action;\n }\n if (\"messages\" in action) {\n working.splice(\n 0,\n working.length,\n ...(action.messages as BaseMessage[])\n );\n modified = true;\n }\n }\n }\n\n if (modified) {\n return { messages: working };\n }\n\n return null;\n };\n\n return createMiddleware({\n name: \"OpenAIModerationMiddleware\",\n beforeModel: {\n hook: async (\n state\n ): Promise<MiddlewareResult<Partial<AgentBuiltInState>> | undefined> => {\n if (!checkInput && !checkToolResults) {\n return undefined;\n }\n\n const messages = state.messages || [];\n if (messages.length === 0) {\n return undefined;\n }\n\n return (await moderateInputs(messages)) ?? undefined;\n },\n canJumpTo: [\"end\"],\n },\n afterModel: {\n hook: async (\n state\n ): Promise<MiddlewareResult<Partial<AgentBuiltInState>> | undefined> => {\n if (!checkOutput) {\n return undefined;\n }\n\n const messages = state.messages || [];\n if (messages.length === 0) {\n return undefined;\n }\n\n return (await moderateOutput(messages)) ?? undefined;\n },\n canJumpTo: [\"end\"],\n },\n });\n}\n"],"mappings":"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"}
@@ -0,0 +1,130 @@
1
+ require("../../_virtual/_rolldown/runtime.cjs");
2
+ const require_middleware = require("../middleware.cjs");
3
+ let _langchain_core_utils_function_calling = require("@langchain/core/utils/function_calling");
4
+ //#region src/agents/middleware/providerToolSearch.ts
5
+ const SERVER_SEARCH_PROVIDERS = ["anthropic", "openai"];
6
+ const SERVER_TOOL_SEARCH_TOOLS = {
7
+ anthropic: {
8
+ type: "tool_search_tool_bm25_20251119",
9
+ name: "tool_search_tool_bm25"
10
+ },
11
+ openai: { type: "tool_search" }
12
+ };
13
+ /**
14
+ * Provider-side tool search middleware.
15
+ *
16
+ * Leverages server-side tool search: the full client tool catalog is forwarded
17
+ * to the provider, with deferred tools marked `defer_loading` so the provider
18
+ * discloses them on demand via its own search. A tool is deferred when it is
19
+ * named in `searchableTools` or built with `extras.defer_loading: true`.
20
+ *
21
+ * Requires a model with server-side tool search support: OpenAI gpt-5.4+ or Anthropic
22
+ * Claude Sonnet 4+/Opus 4+/Haiku 4.5+. Non-Anthropic/OpenAI providers throw; an
23
+ * in-family model that is too old surfaces the provider's own API error rather
24
+ * than being gated here.
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * import { createAgent, providerToolSearchMiddleware } from "langchain";
29
+ * import { ChatAnthropic } from "@langchain/anthropic";
30
+ *
31
+ * const agent = createAgent({
32
+ * model: new ChatAnthropic({ model: "claude-sonnet-4-5" }),
33
+ * tools: [getWeather, ...nicheTools],
34
+ * middleware: [
35
+ * // Defer the niche tools behind the provider's tool search; the model
36
+ * // discovers them on demand instead of receiving every schema up front.
37
+ * providerToolSearchMiddleware({ searchableTools: nicheTools }),
38
+ * ],
39
+ * });
40
+ * ```
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * import { tool } from "@langchain/core/tools";
45
+ * import { createAgent, providerToolSearchMiddleware } from "langchain";
46
+ * import { ChatAnthropic } from "@langchain/anthropic";
47
+ *
48
+ * // A tool marked `defer_loading` at construction is deferred on its own —
49
+ * // no need to list it in `searchableTools`; the middleware honors the flag.
50
+ * const sendEmail = tool(sendEmailFn, {
51
+ * name: "send_email",
52
+ * description: "Send an email",
53
+ * schema: sendEmailSchema,
54
+ * extras: { defer_loading: true },
55
+ * });
56
+ *
57
+ * const agent = createAgent({
58
+ * model: new ChatAnthropic({ model: "claude-sonnet-4-5" }),
59
+ * tools: [getWeather, sendEmail],
60
+ * middleware: [providerToolSearchMiddleware()],
61
+ * });
62
+ * ```
63
+ *
64
+ * @param config - Configuration options for the middleware
65
+ * @param config.searchableTools - Tools to defer behind tool search
66
+ * @returns A middleware instance that can be used with `createAgent`
67
+ */
68
+ function providerToolSearchMiddleware(config = {}) {
69
+ const deferNames = toToolNames(config.searchableTools);
70
+ return require_middleware.createMiddleware({
71
+ name: "ProviderToolSearch",
72
+ wrapModelCall: (request, handler) => {
73
+ const tools = request.tools ?? [];
74
+ if (deferNames.size > 0) {
75
+ const available = tools.filter(_langchain_core_utils_function_calling.isLangChainTool).map((t) => t.name);
76
+ const unknown = [...deferNames].filter((name) => !available.includes(name));
77
+ if (unknown.length > 0) throw new Error(`providerToolSearchMiddleware: searchableTools references tool(s) not bound to the model: ${unknown.join(", ")}`);
78
+ }
79
+ const provider = getModelProvider(request.model);
80
+ if (!supportsProviderToolSearch(provider)) throw new Error(`providerToolSearchMiddleware requires a provider with server-side tool search, but got ${provider}`);
81
+ if (!hasDeferredTools(tools, deferNames)) return handler(request);
82
+ const boundTools = tools.map((tool) => deferToolIfNeeded(tool, deferNames));
83
+ const nativeSearchTool = SERVER_TOOL_SEARCH_TOOLS[provider];
84
+ return handler({
85
+ ...request,
86
+ tools: [...boundTools, nativeSearchTool]
87
+ });
88
+ }
89
+ });
90
+ }
91
+ function isDeferred(tool, deferNames) {
92
+ return (0, _langchain_core_utils_function_calling.isLangChainTool)(tool) && (tool.extras?.defer_loading === true || deferNames.has(tool.name));
93
+ }
94
+ function hasDeferredTools(tools, deferNames) {
95
+ return tools.some((tool) => isDeferred(tool, deferNames));
96
+ }
97
+ /**
98
+ * If a tool should be deferred, return a minimal binding spec carrying `defer_loading`.
99
+ * Otherwise return it as-is
100
+ */
101
+ function deferToolIfNeeded(tool, deferNames) {
102
+ if (!isDeferred(tool, deferNames)) return tool;
103
+ return {
104
+ name: tool.name,
105
+ description: tool.description,
106
+ schema: tool.schema,
107
+ extras: {
108
+ ...tool.extras,
109
+ defer_loading: true
110
+ }
111
+ };
112
+ }
113
+ /** Flatten a list of tool names/instances into a set of tool names. */
114
+ function toToolNames(tools = []) {
115
+ return new Set(tools.map((t) => typeof t === "string" ? t : t.name));
116
+ }
117
+ function getModelProvider(model) {
118
+ const name = model.getName();
119
+ const configured = name === "ConfigurableModel" ? model._defaultConfig?.modelProvider : void 0;
120
+ if (name === "ChatAnthropic" || configured === "anthropic") return "anthropic";
121
+ if (name === "ChatOpenAI" || configured === "openai") return "openai";
122
+ return "other";
123
+ }
124
+ function supportsProviderToolSearch(provider) {
125
+ return SERVER_SEARCH_PROVIDERS.includes(provider);
126
+ }
127
+ //#endregion
128
+ exports.providerToolSearchMiddleware = providerToolSearchMiddleware;
129
+
130
+ //# sourceMappingURL=providerToolSearch.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"providerToolSearch.cjs","names":["createMiddleware","isLangChainTool"],"sources":["../../../src/agents/middleware/providerToolSearch.ts"],"sourcesContent":["import type {\n StructuredToolInterface,\n StructuredToolParams,\n ClientTool,\n ServerTool,\n} from \"@langchain/core/tools\";\nimport { isLangChainTool } from \"@langchain/core/utils/function_calling\";\nimport { createMiddleware } from \"../middleware.js\";\nimport type { LanguageModelLike } from \"@langchain/core/language_models/base\";\nimport type {\n ChatModelProvider,\n ConfigurableModel,\n} from \"../../chat_models/universal.js\";\n\ntype ToolName = string;\nexport type ToolIdentifier = ToolName | StructuredToolInterface;\n\nexport type ProviderToolSearchMiddlewareConfig = {\n /**\n * Which tools are deferred; withheld from the model until its tool search surfaces them.\n *\n * Tools already constructed with `extras.defer_loading === true` are deferred\n * regardless of this option; if `searchableTools` is omitted, only those pre-marked\n * tools are deferred.\n */\n searchableTools?: ToolIdentifier[];\n};\n\nconst SERVER_SEARCH_PROVIDERS = [\n \"anthropic\",\n \"openai\",\n] as const satisfies readonly ChatModelProvider[];\n\ntype ServerSearchCapableProvider = (typeof SERVER_SEARCH_PROVIDERS)[number];\ntype DetectedProvider = ServerSearchCapableProvider | \"other\";\n\ntype ServerToolSearchTool = { type: string; name?: string };\n\nconst SERVER_TOOL_SEARCH_TOOLS = {\n anthropic: {\n type: \"tool_search_tool_bm25_20251119\",\n name: \"tool_search_tool_bm25\",\n },\n openai: { type: \"tool_search\" },\n} as const satisfies Record<ServerSearchCapableProvider, ServerToolSearchTool>;\n\n/**\n * Provider-side tool search middleware.\n *\n * Leverages server-side tool search: the full client tool catalog is forwarded\n * to the provider, with deferred tools marked `defer_loading` so the provider\n * discloses them on demand via its own search. A tool is deferred when it is\n * named in `searchableTools` or built with `extras.defer_loading: true`.\n *\n * Requires a model with server-side tool search support: OpenAI gpt-5.4+ or Anthropic\n * Claude Sonnet 4+/Opus 4+/Haiku 4.5+. Non-Anthropic/OpenAI providers throw; an\n * in-family model that is too old surfaces the provider's own API error rather\n * than being gated here.\n *\n * @example\n * ```ts\n * import { createAgent, providerToolSearchMiddleware } from \"langchain\";\n * import { ChatAnthropic } from \"@langchain/anthropic\";\n *\n * const agent = createAgent({\n * model: new ChatAnthropic({ model: \"claude-sonnet-4-5\" }),\n * tools: [getWeather, ...nicheTools],\n * middleware: [\n * // Defer the niche tools behind the provider's tool search; the model\n * // discovers them on demand instead of receiving every schema up front.\n * providerToolSearchMiddleware({ searchableTools: nicheTools }),\n * ],\n * });\n * ```\n *\n * @example\n * ```ts\n * import { tool } from \"@langchain/core/tools\";\n * import { createAgent, providerToolSearchMiddleware } from \"langchain\";\n * import { ChatAnthropic } from \"@langchain/anthropic\";\n *\n * // A tool marked `defer_loading` at construction is deferred on its own —\n * // no need to list it in `searchableTools`; the middleware honors the flag.\n * const sendEmail = tool(sendEmailFn, {\n * name: \"send_email\",\n * description: \"Send an email\",\n * schema: sendEmailSchema,\n * extras: { defer_loading: true },\n * });\n *\n * const agent = createAgent({\n * model: new ChatAnthropic({ model: \"claude-sonnet-4-5\" }),\n * tools: [getWeather, sendEmail],\n * middleware: [providerToolSearchMiddleware()],\n * });\n * ```\n *\n * @param config - Configuration options for the middleware\n * @param config.searchableTools - Tools to defer behind tool search\n * @returns A middleware instance that can be used with `createAgent`\n */\nexport function providerToolSearchMiddleware(\n config: ProviderToolSearchMiddlewareConfig = {}\n) {\n const deferNames = toToolNames(config.searchableTools);\n\n return createMiddleware({\n name: \"ProviderToolSearch\",\n wrapModelCall: (request, handler) => {\n const tools = request.tools ?? [];\n\n // Fail fast if we try to defer a tool that is not bound to the model\n if (deferNames.size > 0) {\n const available = tools.filter(isLangChainTool).map((t) => t.name);\n const unknown = [...deferNames].filter(\n (name) => !available.includes(name)\n );\n if (unknown.length > 0) {\n throw new Error(\n `providerToolSearchMiddleware: searchableTools references tool(s) not bound to the model: ${unknown.join(\", \")}`\n );\n }\n }\n\n const provider = getModelProvider(request.model);\n if (!supportsProviderToolSearch(provider)) {\n throw new Error(\n `providerToolSearchMiddleware requires a provider with server-side tool search, but got ${provider}`\n );\n }\n\n // Nothing to defer -> pass thru\n if (!hasDeferredTools(tools, deferNames)) return handler(request);\n\n // For each deferred tool, emit a minimal binding spec carrying `defer_loading`.\n const boundTools = tools.map((tool) =>\n deferToolIfNeeded(tool, deferNames)\n );\n\n const nativeSearchTool = SERVER_TOOL_SEARCH_TOOLS[provider];\n return handler({ ...request, tools: [...boundTools, nativeSearchTool] });\n },\n });\n}\n\nfunction isDeferred(\n tool: unknown,\n deferNames: Set<string>\n): tool is StructuredToolParams {\n return (\n isLangChainTool(tool) &&\n (tool.extras?.defer_loading === true || deferNames.has(tool.name))\n );\n}\n\nfunction hasDeferredTools(\n tools: readonly (ClientTool | ServerTool)[],\n deferNames: Set<string>\n): boolean {\n return tools.some((tool) => isDeferred(tool, deferNames));\n}\n\n/**\n * If a tool should be deferred, return a minimal binding spec carrying `defer_loading`.\n * Otherwise return it as-is\n */\nfunction deferToolIfNeeded(\n tool: ClientTool | ServerTool,\n deferNames: Set<string>\n): ClientTool | ServerTool {\n if (!isDeferred(tool, deferNames)) return tool;\n return {\n name: tool.name,\n description: tool.description,\n schema: tool.schema,\n extras: { ...tool.extras, defer_loading: true },\n };\n}\n\n/** Flatten a list of tool names/instances into a set of tool names. */\nfunction toToolNames(tools: ToolIdentifier[] = []): Set<string> {\n return new Set(tools.map((t) => (typeof t === \"string\" ? t : t.name)));\n}\n\nfunction getModelProvider(model: LanguageModelLike): DetectedProvider {\n const name = model.getName();\n const configured =\n name === \"ConfigurableModel\"\n ? (model as ConfigurableModel)._defaultConfig?.modelProvider\n : undefined;\n if (name === \"ChatAnthropic\" || configured === \"anthropic\")\n return \"anthropic\";\n if (name === \"ChatOpenAI\" || configured === \"openai\") return \"openai\";\n return \"other\";\n}\n\nfunction supportsProviderToolSearch(\n provider: DetectedProvider\n): provider is ServerSearchCapableProvider {\n return (SERVER_SEARCH_PROVIDERS as readonly string[]).includes(provider);\n}\n"],"mappings":";;;;AA4BA,MAAM,0BAA0B,CAC9B,aACA,SACD;AAOD,MAAM,2BAA2B;CAC/B,WAAW;EACT,MAAM;EACN,MAAM;EACP;CACD,QAAQ,EAAE,MAAM,eAAe;CAChC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyDD,SAAgB,6BACd,SAA6C,EAAE,EAC/C;CACA,MAAM,aAAa,YAAY,OAAO,gBAAgB;AAEtD,QAAOA,mBAAAA,iBAAiB;EACtB,MAAM;EACN,gBAAgB,SAAS,YAAY;GACnC,MAAM,QAAQ,QAAQ,SAAS,EAAE;AAGjC,OAAI,WAAW,OAAO,GAAG;IACvB,MAAM,YAAY,MAAM,OAAOC,uCAAAA,gBAAgB,CAAC,KAAK,MAAM,EAAE,KAAK;IAClE,MAAM,UAAU,CAAC,GAAG,WAAW,CAAC,QAC7B,SAAS,CAAC,UAAU,SAAS,KAAK,CACpC;AACD,QAAI,QAAQ,SAAS,EACnB,OAAM,IAAI,MACR,4FAA4F,QAAQ,KAAK,KAAK,GAC/G;;GAIL,MAAM,WAAW,iBAAiB,QAAQ,MAAM;AAChD,OAAI,CAAC,2BAA2B,SAAS,CACvC,OAAM,IAAI,MACR,0FAA0F,WAC3F;AAIH,OAAI,CAAC,iBAAiB,OAAO,WAAW,CAAE,QAAO,QAAQ,QAAQ;GAGjE,MAAM,aAAa,MAAM,KAAK,SAC5B,kBAAkB,MAAM,WAAW,CACpC;GAED,MAAM,mBAAmB,yBAAyB;AAClD,UAAO,QAAQ;IAAE,GAAG;IAAS,OAAO,CAAC,GAAG,YAAY,iBAAiB;IAAE,CAAC;;EAE3E,CAAC;;AAGJ,SAAS,WACP,MACA,YAC8B;AAC9B,SAAA,GAAA,uCAAA,iBACkB,KAAK,KACpB,KAAK,QAAQ,kBAAkB,QAAQ,WAAW,IAAI,KAAK,KAAK;;AAIrE,SAAS,iBACP,OACA,YACS;AACT,QAAO,MAAM,MAAM,SAAS,WAAW,MAAM,WAAW,CAAC;;;;;;AAO3D,SAAS,kBACP,MACA,YACyB;AACzB,KAAI,CAAC,WAAW,MAAM,WAAW,CAAE,QAAO;AAC1C,QAAO;EACL,MAAM,KAAK;EACX,aAAa,KAAK;EAClB,QAAQ,KAAK;EACb,QAAQ;GAAE,GAAG,KAAK;GAAQ,eAAe;GAAM;EAChD;;;AAIH,SAAS,YAAY,QAA0B,EAAE,EAAe;AAC9D,QAAO,IAAI,IAAI,MAAM,KAAK,MAAO,OAAO,MAAM,WAAW,IAAI,EAAE,KAAM,CAAC;;AAGxE,SAAS,iBAAiB,OAA4C;CACpE,MAAM,OAAO,MAAM,SAAS;CAC5B,MAAM,aACJ,SAAS,sBACJ,MAA4B,gBAAgB,gBAC7C,KAAA;AACN,KAAI,SAAS,mBAAmB,eAAe,YAC7C,QAAO;AACT,KAAI,SAAS,gBAAgB,eAAe,SAAU,QAAO;AAC7D,QAAO;;AAGT,SAAS,2BACP,UACyC;AACzC,QAAQ,wBAA8C,SAAS,SAAS"}
@@ -0,0 +1,75 @@
1
+ import { AgentMiddleware } from "./types.cjs";
2
+ import { ClientTool, ServerTool, StructuredToolInterface } from "@langchain/core/tools";
3
+
4
+ //#region src/agents/middleware/providerToolSearch.d.ts
5
+ type ToolName = string;
6
+ type ToolIdentifier = ToolName | StructuredToolInterface;
7
+ type ProviderToolSearchMiddlewareConfig = {
8
+ /**
9
+ * Which tools are deferred; withheld from the model until its tool search surfaces them.
10
+ *
11
+ * Tools already constructed with `extras.defer_loading === true` are deferred
12
+ * regardless of this option; if `searchableTools` is omitted, only those pre-marked
13
+ * tools are deferred.
14
+ */
15
+ searchableTools?: ToolIdentifier[];
16
+ };
17
+ /**
18
+ * Provider-side tool search middleware.
19
+ *
20
+ * Leverages server-side tool search: the full client tool catalog is forwarded
21
+ * to the provider, with deferred tools marked `defer_loading` so the provider
22
+ * discloses them on demand via its own search. A tool is deferred when it is
23
+ * named in `searchableTools` or built with `extras.defer_loading: true`.
24
+ *
25
+ * Requires a model with server-side tool search support: OpenAI gpt-5.4+ or Anthropic
26
+ * Claude Sonnet 4+/Opus 4+/Haiku 4.5+. Non-Anthropic/OpenAI providers throw; an
27
+ * in-family model that is too old surfaces the provider's own API error rather
28
+ * than being gated here.
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * import { createAgent, providerToolSearchMiddleware } from "langchain";
33
+ * import { ChatAnthropic } from "@langchain/anthropic";
34
+ *
35
+ * const agent = createAgent({
36
+ * model: new ChatAnthropic({ model: "claude-sonnet-4-5" }),
37
+ * tools: [getWeather, ...nicheTools],
38
+ * middleware: [
39
+ * // Defer the niche tools behind the provider's tool search; the model
40
+ * // discovers them on demand instead of receiving every schema up front.
41
+ * providerToolSearchMiddleware({ searchableTools: nicheTools }),
42
+ * ],
43
+ * });
44
+ * ```
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * import { tool } from "@langchain/core/tools";
49
+ * import { createAgent, providerToolSearchMiddleware } from "langchain";
50
+ * import { ChatAnthropic } from "@langchain/anthropic";
51
+ *
52
+ * // A tool marked `defer_loading` at construction is deferred on its own —
53
+ * // no need to list it in `searchableTools`; the middleware honors the flag.
54
+ * const sendEmail = tool(sendEmailFn, {
55
+ * name: "send_email",
56
+ * description: "Send an email",
57
+ * schema: sendEmailSchema,
58
+ * extras: { defer_loading: true },
59
+ * });
60
+ *
61
+ * const agent = createAgent({
62
+ * model: new ChatAnthropic({ model: "claude-sonnet-4-5" }),
63
+ * tools: [getWeather, sendEmail],
64
+ * middleware: [providerToolSearchMiddleware()],
65
+ * });
66
+ * ```
67
+ *
68
+ * @param config - Configuration options for the middleware
69
+ * @param config.searchableTools - Tools to defer behind tool search
70
+ * @returns A middleware instance that can be used with `createAgent`
71
+ */
72
+ declare function providerToolSearchMiddleware(config?: ProviderToolSearchMiddlewareConfig): AgentMiddleware<undefined, undefined, unknown, readonly (ServerTool | ClientTool)[], readonly []>;
73
+ //#endregion
74
+ export { ProviderToolSearchMiddlewareConfig, providerToolSearchMiddleware };
75
+ //# sourceMappingURL=providerToolSearch.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"providerToolSearch.d.cts","names":[],"sources":["../../../src/agents/middleware/providerToolSearch.ts"],"mappings":";;;;KAcK,QAAA;AAAA,KACO,cAAA,GAAiB,QAAA,GAAW,uBAAA;AAAA,KAE5B,kCAAA;EAHP;;;;;AACL;;EAUE,eAAA,GAAkB,cAAA;AAAA;;AARpB;;;;;AAoFA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAgB,4BAAA,CACd,MAAA,GAAQ,kCAAA,GAAuC,eAAA,0CAAA,UAAA,GAAA,UAAA"}
@@ -0,0 +1,75 @@
1
+ import { AgentMiddleware } from "./types.js";
2
+ import { ClientTool, ServerTool, StructuredToolInterface } from "@langchain/core/tools";
3
+
4
+ //#region src/agents/middleware/providerToolSearch.d.ts
5
+ type ToolName = string;
6
+ type ToolIdentifier = ToolName | StructuredToolInterface;
7
+ type ProviderToolSearchMiddlewareConfig = {
8
+ /**
9
+ * Which tools are deferred; withheld from the model until its tool search surfaces them.
10
+ *
11
+ * Tools already constructed with `extras.defer_loading === true` are deferred
12
+ * regardless of this option; if `searchableTools` is omitted, only those pre-marked
13
+ * tools are deferred.
14
+ */
15
+ searchableTools?: ToolIdentifier[];
16
+ };
17
+ /**
18
+ * Provider-side tool search middleware.
19
+ *
20
+ * Leverages server-side tool search: the full client tool catalog is forwarded
21
+ * to the provider, with deferred tools marked `defer_loading` so the provider
22
+ * discloses them on demand via its own search. A tool is deferred when it is
23
+ * named in `searchableTools` or built with `extras.defer_loading: true`.
24
+ *
25
+ * Requires a model with server-side tool search support: OpenAI gpt-5.4+ or Anthropic
26
+ * Claude Sonnet 4+/Opus 4+/Haiku 4.5+. Non-Anthropic/OpenAI providers throw; an
27
+ * in-family model that is too old surfaces the provider's own API error rather
28
+ * than being gated here.
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * import { createAgent, providerToolSearchMiddleware } from "langchain";
33
+ * import { ChatAnthropic } from "@langchain/anthropic";
34
+ *
35
+ * const agent = createAgent({
36
+ * model: new ChatAnthropic({ model: "claude-sonnet-4-5" }),
37
+ * tools: [getWeather, ...nicheTools],
38
+ * middleware: [
39
+ * // Defer the niche tools behind the provider's tool search; the model
40
+ * // discovers them on demand instead of receiving every schema up front.
41
+ * providerToolSearchMiddleware({ searchableTools: nicheTools }),
42
+ * ],
43
+ * });
44
+ * ```
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * import { tool } from "@langchain/core/tools";
49
+ * import { createAgent, providerToolSearchMiddleware } from "langchain";
50
+ * import { ChatAnthropic } from "@langchain/anthropic";
51
+ *
52
+ * // A tool marked `defer_loading` at construction is deferred on its own —
53
+ * // no need to list it in `searchableTools`; the middleware honors the flag.
54
+ * const sendEmail = tool(sendEmailFn, {
55
+ * name: "send_email",
56
+ * description: "Send an email",
57
+ * schema: sendEmailSchema,
58
+ * extras: { defer_loading: true },
59
+ * });
60
+ *
61
+ * const agent = createAgent({
62
+ * model: new ChatAnthropic({ model: "claude-sonnet-4-5" }),
63
+ * tools: [getWeather, sendEmail],
64
+ * middleware: [providerToolSearchMiddleware()],
65
+ * });
66
+ * ```
67
+ *
68
+ * @param config - Configuration options for the middleware
69
+ * @param config.searchableTools - Tools to defer behind tool search
70
+ * @returns A middleware instance that can be used with `createAgent`
71
+ */
72
+ declare function providerToolSearchMiddleware(config?: ProviderToolSearchMiddlewareConfig): AgentMiddleware<undefined, undefined, unknown, readonly (ServerTool | ClientTool)[], readonly []>;
73
+ //#endregion
74
+ export { ProviderToolSearchMiddlewareConfig, providerToolSearchMiddleware };
75
+ //# sourceMappingURL=providerToolSearch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"providerToolSearch.d.ts","names":[],"sources":["../../../src/agents/middleware/providerToolSearch.ts"],"mappings":";;;;KAcK,QAAA;AAAA,KACO,cAAA,GAAiB,QAAA,GAAW,uBAAA;AAAA,KAE5B,kCAAA;EAHP;;;;;AACL;;EAUE,eAAA,GAAkB,cAAA;AAAA;;AARpB;;;;;AAoFA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAgB,4BAAA,CACd,MAAA,GAAQ,kCAAA,GAAuC,eAAA,0CAAA,UAAA,GAAA,UAAA"}