langchain 1.0.0-alpha.6 → 1.0.0-alpha.7
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.
- package/dist/agents/ReactAgent.cjs.map +1 -1
- package/dist/agents/ReactAgent.d.cts +1 -2
- package/dist/agents/ReactAgent.d.cts.map +1 -1
- package/dist/agents/ReactAgent.d.ts +1 -2
- package/dist/agents/ReactAgent.d.ts.map +1 -1
- package/dist/agents/ReactAgent.js.map +1 -1
- package/dist/agents/annotation.cjs.map +1 -1
- package/dist/agents/annotation.d.cts +12 -5
- package/dist/agents/annotation.d.cts.map +1 -1
- package/dist/agents/annotation.d.ts +12 -5
- package/dist/agents/annotation.d.ts.map +1 -1
- package/dist/agents/annotation.js.map +1 -1
- package/dist/agents/createAgent.cjs.map +1 -1
- package/dist/agents/createAgent.js.map +1 -1
- package/dist/agents/index.cjs.map +1 -1
- package/dist/agents/index.d.cts +3 -3
- package/dist/agents/index.d.cts.map +1 -1
- package/dist/agents/index.d.ts +3 -3
- package/dist/agents/index.d.ts.map +1 -1
- package/dist/agents/index.js.map +1 -1
- package/dist/agents/interrupt.d.cts +14 -1
- package/dist/agents/interrupt.d.cts.map +1 -1
- package/dist/agents/interrupt.d.ts +14 -1
- package/dist/agents/interrupt.d.ts.map +1 -1
- package/dist/agents/middlewareAgent/ReactAgent.cjs +262 -55
- package/dist/agents/middlewareAgent/ReactAgent.cjs.map +1 -1
- package/dist/agents/middlewareAgent/ReactAgent.d.cts +97 -10
- package/dist/agents/middlewareAgent/ReactAgent.d.cts.map +1 -1
- package/dist/agents/middlewareAgent/ReactAgent.d.ts +97 -10
- package/dist/agents/middlewareAgent/ReactAgent.d.ts.map +1 -1
- package/dist/agents/middlewareAgent/ReactAgent.js +264 -57
- package/dist/agents/middlewareAgent/ReactAgent.js.map +1 -1
- package/dist/agents/middlewareAgent/annotation.cjs +10 -4
- package/dist/agents/middlewareAgent/annotation.cjs.map +1 -1
- package/dist/agents/middlewareAgent/annotation.js +10 -4
- package/dist/agents/middlewareAgent/annotation.js.map +1 -1
- package/dist/agents/middlewareAgent/constants.d.cts +5 -0
- package/dist/agents/middlewareAgent/constants.d.cts.map +1 -0
- package/dist/agents/middlewareAgent/constants.d.ts +5 -0
- package/dist/agents/middlewareAgent/constants.d.ts.map +1 -0
- package/dist/agents/middlewareAgent/index.cjs.map +1 -1
- package/dist/agents/middlewareAgent/index.js.map +1 -1
- package/dist/agents/middlewareAgent/middleware/bigTool.cjs +162 -0
- package/dist/agents/middlewareAgent/middleware/bigTool.cjs.map +1 -0
- package/dist/agents/middlewareAgent/middleware/bigTool.d.cts +113 -0
- package/dist/agents/middlewareAgent/middleware/bigTool.d.cts.map +1 -0
- package/dist/agents/middlewareAgent/middleware/bigTool.d.ts +113 -0
- package/dist/agents/middlewareAgent/middleware/bigTool.d.ts.map +1 -0
- package/dist/agents/middlewareAgent/middleware/bigTool.js +161 -0
- package/dist/agents/middlewareAgent/middleware/bigTool.js.map +1 -0
- package/dist/agents/middlewareAgent/middleware/dynamicSystemPrompt.cjs +58 -0
- package/dist/agents/middlewareAgent/middleware/dynamicSystemPrompt.cjs.map +1 -0
- package/dist/agents/middlewareAgent/middleware/dynamicSystemPrompt.d.cts +46 -0
- package/dist/agents/middlewareAgent/middleware/dynamicSystemPrompt.d.cts.map +1 -0
- package/dist/agents/middlewareAgent/middleware/dynamicSystemPrompt.d.ts +46 -0
- package/dist/agents/middlewareAgent/middleware/dynamicSystemPrompt.d.ts.map +1 -0
- package/dist/agents/middlewareAgent/middleware/dynamicSystemPrompt.js +58 -0
- package/dist/agents/middlewareAgent/middleware/dynamicSystemPrompt.js.map +1 -0
- package/dist/agents/middlewareAgent/middleware/hitl.cjs +160 -84
- package/dist/agents/middlewareAgent/middleware/hitl.cjs.map +1 -1
- package/dist/agents/middlewareAgent/middleware/hitl.d.cts +271 -51
- package/dist/agents/middlewareAgent/middleware/hitl.d.cts.map +1 -1
- package/dist/agents/middlewareAgent/middleware/hitl.d.ts +271 -51
- package/dist/agents/middlewareAgent/middleware/hitl.d.ts.map +1 -1
- package/dist/agents/middlewareAgent/middleware/hitl.js +161 -85
- package/dist/agents/middlewareAgent/middleware/hitl.js.map +1 -1
- package/dist/agents/middlewareAgent/middleware/index.cjs +6 -0
- package/dist/agents/middlewareAgent/middleware/index.cjs.map +1 -1
- package/dist/agents/middlewareAgent/middleware/index.d.cts +6 -4
- package/dist/agents/middlewareAgent/middleware/index.d.ts +6 -4
- package/dist/agents/middlewareAgent/middleware/index.js +5 -1
- package/dist/agents/middlewareAgent/middleware/index.js.map +1 -1
- package/dist/agents/middlewareAgent/middleware/promptCaching.cjs +24 -8
- package/dist/agents/middlewareAgent/middleware/promptCaching.cjs.map +1 -1
- package/dist/agents/middlewareAgent/middleware/promptCaching.d.cts +51 -6
- package/dist/agents/middlewareAgent/middleware/promptCaching.d.cts.map +1 -1
- package/dist/agents/middlewareAgent/middleware/promptCaching.d.ts +51 -6
- package/dist/agents/middlewareAgent/middleware/promptCaching.d.ts.map +1 -1
- package/dist/agents/middlewareAgent/middleware/promptCaching.js +24 -8
- package/dist/agents/middlewareAgent/middleware/promptCaching.js.map +1 -1
- package/dist/agents/middlewareAgent/middleware/summarization.cjs +24 -12
- package/dist/agents/middlewareAgent/middleware/summarization.cjs.map +1 -1
- package/dist/agents/middlewareAgent/middleware/summarization.d.cts +5 -3
- package/dist/agents/middlewareAgent/middleware/summarization.d.cts.map +1 -1
- package/dist/agents/middlewareAgent/middleware/summarization.d.ts +11 -9
- package/dist/agents/middlewareAgent/middleware/summarization.d.ts.map +1 -1
- package/dist/agents/middlewareAgent/middleware/summarization.js +25 -13
- package/dist/agents/middlewareAgent/middleware/summarization.js.map +1 -1
- package/dist/agents/middlewareAgent/middleware.cjs +8 -5
- package/dist/agents/middlewareAgent/middleware.cjs.map +1 -1
- package/dist/agents/middlewareAgent/middleware.d.cts +67 -7
- package/dist/agents/middlewareAgent/middleware.d.cts.map +1 -1
- package/dist/agents/middlewareAgent/middleware.d.ts +67 -7
- package/dist/agents/middlewareAgent/middleware.d.ts.map +1 -1
- package/dist/agents/middlewareAgent/middleware.js +8 -5
- package/dist/agents/middlewareAgent/middleware.js.map +1 -1
- package/dist/agents/middlewareAgent/nodes/AfterModalNode.cjs +2 -2
- package/dist/agents/middlewareAgent/nodes/AfterModalNode.cjs.map +1 -1
- package/dist/agents/middlewareAgent/nodes/AfterModalNode.js +2 -2
- package/dist/agents/middlewareAgent/nodes/AfterModalNode.js.map +1 -1
- package/dist/agents/middlewareAgent/nodes/AgentNode.cjs +117 -68
- package/dist/agents/middlewareAgent/nodes/AgentNode.cjs.map +1 -1
- package/dist/agents/middlewareAgent/nodes/AgentNode.js +121 -72
- package/dist/agents/middlewareAgent/nodes/AgentNode.js.map +1 -1
- package/dist/agents/middlewareAgent/nodes/BeforeModalNode.cjs +2 -2
- package/dist/agents/middlewareAgent/nodes/BeforeModalNode.cjs.map +1 -1
- package/dist/agents/middlewareAgent/nodes/BeforeModalNode.js +2 -2
- package/dist/agents/middlewareAgent/nodes/BeforeModalNode.js.map +1 -1
- package/dist/agents/middlewareAgent/nodes/middleware.cjs +42 -17
- package/dist/agents/middlewareAgent/nodes/middleware.cjs.map +1 -1
- package/dist/agents/middlewareAgent/nodes/middleware.js +42 -18
- package/dist/agents/middlewareAgent/nodes/middleware.js.map +1 -1
- package/dist/agents/middlewareAgent/nodes/utils.cjs +25 -11
- package/dist/agents/middlewareAgent/nodes/utils.cjs.map +1 -1
- package/dist/agents/middlewareAgent/nodes/utils.js +26 -12
- package/dist/agents/middlewareAgent/nodes/utils.js.map +1 -1
- package/dist/agents/middlewareAgent/types.d.cts +153 -66
- package/dist/agents/middlewareAgent/types.d.cts.map +1 -1
- package/dist/agents/middlewareAgent/types.d.ts +153 -66
- package/dist/agents/middlewareAgent/types.d.ts.map +1 -1
- package/dist/agents/model.cjs +13 -0
- package/dist/agents/model.cjs.map +1 -0
- package/dist/agents/model.js +11 -0
- package/dist/agents/model.js.map +1 -0
- package/dist/agents/nodes/AgentNode.cjs +56 -28
- package/dist/agents/nodes/AgentNode.cjs.map +1 -1
- package/dist/agents/nodes/AgentNode.js +57 -29
- package/dist/agents/nodes/AgentNode.js.map +1 -1
- package/dist/agents/nodes/ToolNode.cjs +1 -1
- package/dist/agents/nodes/ToolNode.cjs.map +1 -1
- package/dist/agents/nodes/ToolNode.d.cts +1 -1
- package/dist/agents/nodes/ToolNode.js +1 -1
- package/dist/agents/nodes/ToolNode.js.map +1 -1
- package/dist/agents/responses.cjs +52 -10
- package/dist/agents/responses.cjs.map +1 -1
- package/dist/agents/responses.d.cts +12 -20
- package/dist/agents/responses.d.cts.map +1 -1
- package/dist/agents/responses.d.ts +12 -20
- package/dist/agents/responses.d.ts.map +1 -1
- package/dist/agents/responses.js +52 -11
- package/dist/agents/responses.js.map +1 -1
- package/dist/agents/types.d.cts +2 -3
- package/dist/agents/types.d.cts.map +1 -1
- package/dist/agents/types.d.ts +2 -3
- package/dist/agents/types.d.ts.map +1 -1
- package/dist/agents/utils.cjs +5 -23
- package/dist/agents/utils.cjs.map +1 -1
- package/dist/agents/utils.js +2 -19
- package/dist/agents/utils.js.map +1 -1
- package/dist/chains/query_constructor/index.d.cts +1 -1
- package/dist/chains/summarization/load.d.ts +2 -2
- package/dist/chains/summarization/load.d.ts.map +1 -1
- package/dist/embeddings/cache_backed.d.ts +1 -1
- package/dist/evaluation/comparison/pairwise.d.cts.map +1 -1
- package/dist/evaluation/comparison/pairwise.d.ts.map +1 -1
- package/dist/evaluation/criteria/criteria.d.cts.map +1 -1
- package/dist/evaluation/criteria/criteria.d.ts.map +1 -1
- package/dist/index.cjs +38 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -4
- package/dist/index.d.ts +11 -4
- package/dist/index.js +18 -4
- package/dist/index.js.map +1 -1
- package/dist/langchain-core/dist/messages/base.d.cts.map +1 -1
- package/dist/langchain-core/dist/messages/content/index.d.cts +20 -1
- package/dist/langchain-core/dist/messages/content/index.d.cts.map +1 -1
- package/dist/langchain-core/dist/messages/content/tools.d.cts +67 -6
- package/dist/langchain-core/dist/messages/content/tools.d.cts.map +1 -1
- package/dist/langchain-core/dist/messages/message.d.cts +6 -6
- package/dist/langchain-core/dist/messages/message.d.cts.map +1 -1
- package/dist/langchain-core/dist/utils/types/index.d.cts.map +1 -1
- package/dist/libs/langchain-core/dist/messages/base.d.ts.map +1 -1
- package/dist/libs/langchain-core/dist/messages/content/index.d.ts +20 -1
- package/dist/libs/langchain-core/dist/messages/content/index.d.ts.map +1 -1
- package/dist/libs/langchain-core/dist/messages/content/tools.d.ts +67 -6
- package/dist/libs/langchain-core/dist/messages/content/tools.d.ts.map +1 -1
- package/dist/libs/langchain-core/dist/messages/message.d.ts +6 -6
- package/dist/libs/langchain-core/dist/messages/message.d.ts.map +1 -1
- package/dist/libs/langchain-core/dist/utils/types/index.d.ts.map +1 -1
- package/dist/load/import_map.cjs +1 -1
- package/dist/load/import_map.js +1 -1
- package/dist/output_parsers/structured.d.cts +1 -1
- package/dist/tools/fs.d.cts +1 -1
- package/dist/tools/retriever.d.cts +1 -1
- package/package.json +7 -7
|
@@ -2,16 +2,19 @@ const require_rolldown_runtime = require('../../../_virtual/rolldown_runtime.cjs
|
|
|
2
2
|
const require_middleware = require('../middleware.cjs');
|
|
3
3
|
const __langchain_core_messages = require_rolldown_runtime.__toESM(require("@langchain/core/messages"));
|
|
4
4
|
const __langchain_langgraph = require_rolldown_runtime.__toESM(require("@langchain/langgraph"));
|
|
5
|
+
const __langchain_core_utils_types = require_rolldown_runtime.__toESM(require("@langchain/core/utils/types"));
|
|
5
6
|
const zod_v3 = require_rolldown_runtime.__toESM(require("zod/v3"));
|
|
6
|
-
const uuid = require_rolldown_runtime.__toESM(require("uuid"));
|
|
7
7
|
|
|
8
8
|
//#region src/agents/middlewareAgent/middleware/hitl.ts
|
|
9
|
+
const ToolConfigSchema = zod_v3.z.object({
|
|
10
|
+
allowAccept: zod_v3.z.boolean().optional(),
|
|
11
|
+
allowEdit: zod_v3.z.boolean().optional(),
|
|
12
|
+
allowRespond: zod_v3.z.boolean().optional(),
|
|
13
|
+
description: zod_v3.z.string().optional()
|
|
14
|
+
});
|
|
9
15
|
const contextSchema = zod_v3.z.object({
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
description: zod_v3.z.string().optional()
|
|
13
|
-
})).default({}),
|
|
14
|
-
messagePrefix: zod_v3.z.string().default("Tool execution requires approval")
|
|
16
|
+
interruptOn: zod_v3.z.record(zod_v3.z.union([zod_v3.z.boolean(), ToolConfigSchema])).optional(),
|
|
17
|
+
descriptionPrefix: zod_v3.z.string().default("Tool execution requires approval")
|
|
15
18
|
});
|
|
16
19
|
/**
|
|
17
20
|
* Creates a Human-in-the-Loop (HITL) middleware for tool approval and oversight.
|
|
@@ -20,6 +23,37 @@ const contextSchema = zod_v3.z.object({
|
|
|
20
23
|
* capabilities before execution. It enables selective approval workflows where certain tools
|
|
21
24
|
* require human intervention while others can execute automatically.
|
|
22
25
|
*
|
|
26
|
+
* A invocation result that has been interrupted by the middleware will have a `__interrupt__`
|
|
27
|
+
* property that contains the interrupt request. You can loop over the request to determine
|
|
28
|
+
* which tools were interrupted, and how to handle them separately.
|
|
29
|
+
*
|
|
30
|
+
* ```ts
|
|
31
|
+
* import { type ToolApprovalRequest, type HumanInTheLoopMiddlewareHumanResponse } from "langchain/middleware";
|
|
32
|
+
* import { type Interrupt } from "langchain";
|
|
33
|
+
*
|
|
34
|
+
* const result = await agent.invoke(request);
|
|
35
|
+
* const interruptRequest = initialResult.__interrupt__?.[0] as Interrupt<
|
|
36
|
+
* ToolApprovalRequest[]
|
|
37
|
+
* >;
|
|
38
|
+
* const resume: HumanInTheLoopMiddlewareHumanResponse[] =
|
|
39
|
+
* interruptRequest.value.map((request) => {
|
|
40
|
+
* if (request.action === "calculator") {
|
|
41
|
+
* return { id: request.toolCallId, type: "accept" };
|
|
42
|
+
* } else if (request.action === "write_file") {
|
|
43
|
+
* return {
|
|
44
|
+
* id: request.toolCallId,
|
|
45
|
+
* type: "edit",
|
|
46
|
+
* args: { filename: "safe.txt", content: "Safe content" },
|
|
47
|
+
* };
|
|
48
|
+
* }
|
|
49
|
+
*
|
|
50
|
+
* throw new Error(`Unknown action: ${request.action}`);
|
|
51
|
+
* });
|
|
52
|
+
*
|
|
53
|
+
* // Resume with approval
|
|
54
|
+
* await agent.invoke(new Command({ resume }), config);
|
|
55
|
+
* ```
|
|
56
|
+
*
|
|
23
57
|
* ## Features
|
|
24
58
|
*
|
|
25
59
|
* - **Selective Tool Approval**: Configure which tools require human approval
|
|
@@ -36,10 +70,12 @@ const contextSchema = zod_v3.z.object({
|
|
|
36
70
|
* - `response`: Provide a manual response instead of executing the tool
|
|
37
71
|
*
|
|
38
72
|
* @param options - Configuration options for the middleware
|
|
39
|
-
* @param options.
|
|
40
|
-
* @param options.
|
|
41
|
-
* @param options.
|
|
42
|
-
* @param options.
|
|
73
|
+
* @param options.interruptOn - Per-tool configuration mapping tool names to their settings
|
|
74
|
+
* @param options.interruptOn[toolName].allowAccept - Whether the human can approve the current action without changes
|
|
75
|
+
* @param options.interruptOn[toolName].allowEdit - Whether the human can reject the current action with feedback
|
|
76
|
+
* @param options.interruptOn[toolName].allowRespond - Whether the human can approve the current action with edited content
|
|
77
|
+
* @param options.interruptOn[toolName].description - Custom approval message for the tool
|
|
78
|
+
* @param options.messagePrefix - Default prefix for approval messages (default: "Tool execution requires approval"). Only used for tools that do not define a custom `description` in their ToolConfig.
|
|
43
79
|
*
|
|
44
80
|
* @returns A middleware instance that can be passed to `createAgent`
|
|
45
81
|
*
|
|
@@ -50,14 +86,15 @@ const contextSchema = zod_v3.z.object({
|
|
|
50
86
|
* import { createAgent } from "langchain";
|
|
51
87
|
*
|
|
52
88
|
* const hitlMiddleware = humanInTheLoopMiddleware({
|
|
53
|
-
*
|
|
89
|
+
* interruptOn: {
|
|
90
|
+
* // Interrupt write_file tool and allow edits or accepts
|
|
54
91
|
* "write_file": {
|
|
55
|
-
*
|
|
92
|
+
* allowEdit: true,
|
|
93
|
+
* allowAccept: true,
|
|
56
94
|
* description: "⚠️ File write operation requires approval"
|
|
57
95
|
* },
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* }
|
|
96
|
+
* // Auto-approve read_file tool
|
|
97
|
+
* "read_file": false
|
|
61
98
|
* }
|
|
62
99
|
* });
|
|
63
100
|
*
|
|
@@ -71,6 +108,7 @@ const contextSchema = zod_v3.z.object({
|
|
|
71
108
|
* @example
|
|
72
109
|
* Handling approval requests
|
|
73
110
|
* ```typescript
|
|
111
|
+
* import { type HumanInTheLoopRequest, type Interrupt } from "langchain/middleware";
|
|
74
112
|
* import { Command } from "@langchain/langgraph";
|
|
75
113
|
*
|
|
76
114
|
* // Initial agent invocation
|
|
@@ -79,15 +117,14 @@ const contextSchema = zod_v3.z.object({
|
|
|
79
117
|
* }, config);
|
|
80
118
|
*
|
|
81
119
|
* // Check if agent is paused for approval
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
* const requests = task?.interrupts?.[0]?.value;
|
|
120
|
+
* if (result.__interrupt__) {
|
|
121
|
+
* const interruptRequest = initialResult.__interrupt__?.[0] as Interrupt<
|
|
122
|
+
* HumanInTheLoopRequest[]
|
|
123
|
+
* >;
|
|
87
124
|
*
|
|
88
125
|
* // Show tool call details to user
|
|
89
|
-
* console.log("Tool:",
|
|
90
|
-
* console.log("
|
|
126
|
+
* console.log("Tool:", interruptRequest.value[0].actionRequest);
|
|
127
|
+
* console.log("Allowed actions:", interruptRequest.value[0].config);
|
|
91
128
|
*
|
|
92
129
|
* // Resume with approval
|
|
93
130
|
* await agent.invoke(
|
|
@@ -112,12 +149,13 @@ const contextSchema = zod_v3.z.object({
|
|
|
112
149
|
* })
|
|
113
150
|
*
|
|
114
151
|
* // Skip tool and terminate agent
|
|
115
|
-
* new Command({ resume: [{ type: "
|
|
152
|
+
* new Command({ resume: [{ type: "response" }] })
|
|
116
153
|
*
|
|
117
154
|
* // Provide manual response
|
|
118
155
|
* new Command({
|
|
119
156
|
* resume: [{
|
|
120
157
|
* type: "response",
|
|
158
|
+
* // this must be a string
|
|
121
159
|
* args: "File operation not allowed in demo mode"
|
|
122
160
|
* }]
|
|
123
161
|
* })
|
|
@@ -127,16 +165,16 @@ const contextSchema = zod_v3.z.object({
|
|
|
127
165
|
* Production use case with database operations
|
|
128
166
|
* ```typescript
|
|
129
167
|
* const hitlMiddleware = humanInTheLoopMiddleware({
|
|
130
|
-
*
|
|
168
|
+
* interruptOn: {
|
|
131
169
|
* "execute_sql": {
|
|
132
|
-
*
|
|
170
|
+
* allowAccept: true,
|
|
171
|
+
* allowEdit: true,
|
|
172
|
+
* allowRespond: true,
|
|
133
173
|
* description: "🚨 SQL query requires DBA approval\nPlease review for safety and performance"
|
|
134
174
|
* },
|
|
135
|
-
* "read_schema":
|
|
136
|
-
* requireApproval: false // Reading metadata is safe
|
|
137
|
-
* },
|
|
175
|
+
* "read_schema": false // Reading metadata is safe
|
|
138
176
|
* "delete_records": {
|
|
139
|
-
*
|
|
177
|
+
* allowAccept: true,
|
|
140
178
|
* description: "⛔ DESTRUCTIVE OPERATION - Requires manager approval"
|
|
141
179
|
* }
|
|
142
180
|
* },
|
|
@@ -155,77 +193,115 @@ const contextSchema = zod_v3.z.object({
|
|
|
155
193
|
* @see {@link Command} for resuming interrupted execution
|
|
156
194
|
* @public
|
|
157
195
|
*/
|
|
158
|
-
function humanInTheLoopMiddleware(options
|
|
196
|
+
function humanInTheLoopMiddleware(options) {
|
|
159
197
|
return require_middleware.createMiddleware({
|
|
160
198
|
name: "HumanInTheLoopMiddleware",
|
|
161
199
|
contextSchema,
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
...
|
|
166
|
-
|
|
200
|
+
afterModelJumpTo: ["model"],
|
|
201
|
+
afterModel: async (state, runtime) => {
|
|
202
|
+
const config = (0, __langchain_core_utils_types.interopParse)(contextSchema, {
|
|
203
|
+
...options,
|
|
204
|
+
...runtime.context || {}
|
|
205
|
+
});
|
|
206
|
+
if (!config) return;
|
|
167
207
|
const { messages } = state;
|
|
168
208
|
if (!messages.length) return;
|
|
169
|
-
|
|
170
|
-
if
|
|
209
|
+
/**
|
|
210
|
+
* Don't do anything if the last message isn't an AI message with tool calls.
|
|
211
|
+
*/
|
|
212
|
+
const lastMessage = [...messages].reverse().find((msg) => __langchain_core_messages.AIMessage.isInstance(msg));
|
|
213
|
+
if (!lastMessage || !lastMessage.tool_calls?.length) return;
|
|
214
|
+
/**
|
|
215
|
+
* If the user omits the interruptOn config, we don't do anything.
|
|
216
|
+
*/
|
|
217
|
+
if (!config.interruptOn) return;
|
|
218
|
+
const resolvedToolConfigs = {};
|
|
219
|
+
for (const [toolName, toolConfig] of Object.entries(config.interruptOn)) if (typeof toolConfig === "boolean") {
|
|
220
|
+
if (toolConfig === true) resolvedToolConfigs[toolName] = {
|
|
221
|
+
allowAccept: true,
|
|
222
|
+
allowEdit: true,
|
|
223
|
+
allowRespond: true
|
|
224
|
+
};
|
|
225
|
+
} else resolvedToolConfigs[toolName] = toolConfig;
|
|
171
226
|
const interruptToolCalls = [];
|
|
172
227
|
const autoApprovedToolCalls = [];
|
|
173
|
-
for (const toolCall of lastMessage.tool_calls)
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
};
|
|
179
|
-
const toolConfig = config.toolConfigs[normalizedToolCall.name];
|
|
180
|
-
if (toolConfig?.requireApproval) interruptToolCalls.push(normalizedToolCall);
|
|
181
|
-
else autoApprovedToolCalls.push(normalizedToolCall);
|
|
182
|
-
}
|
|
228
|
+
for (const toolCall of lastMessage.tool_calls) if (toolCall.name in resolvedToolConfigs) interruptToolCalls.push(toolCall);
|
|
229
|
+
else autoApprovedToolCalls.push(toolCall);
|
|
230
|
+
/**
|
|
231
|
+
* No interrupt tool calls, so we can just return.
|
|
232
|
+
*/
|
|
183
233
|
if (!interruptToolCalls.length) return;
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
const
|
|
187
|
-
const description = toolConfig?.description || `${config.messagePrefix}\n\nTool: ${toolCall.name}\nArgs: ${JSON.stringify(toolCall.args, null, 2)}`;
|
|
234
|
+
const hitlRequests = interruptToolCalls.map((toolCall) => {
|
|
235
|
+
const toolConfig = resolvedToolConfigs[toolCall.name];
|
|
236
|
+
const description = toolConfig.description || `${config.descriptionPrefix}\n\nTool: ${toolCall.name}\nArgs: ${JSON.stringify(toolCall.args, null, 2)}`;
|
|
188
237
|
return {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
238
|
+
actionRequest: {
|
|
239
|
+
action: toolCall.name,
|
|
240
|
+
args: toolCall.args
|
|
241
|
+
},
|
|
242
|
+
config: toolConfig,
|
|
192
243
|
description
|
|
193
244
|
};
|
|
194
245
|
});
|
|
195
|
-
const responses = await (0, __langchain_langgraph.interrupt)(
|
|
196
|
-
|
|
197
|
-
|
|
246
|
+
const responses = await (0, __langchain_langgraph.interrupt)(hitlRequests);
|
|
247
|
+
if (responses.length !== interruptToolCalls.length) throw new Error(`Number of human responses (${responses.length}) does not match number of hanging tool calls (${interruptToolCalls.length}).`);
|
|
248
|
+
const approvedToolCalls = [...autoApprovedToolCalls];
|
|
249
|
+
const artificialToolMessages = [];
|
|
250
|
+
for (const [i, response] of responses.entries()) {
|
|
198
251
|
const toolCall = interruptToolCalls[i];
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
content: typeof response.args === "string" ? response.args : "",
|
|
213
|
-
tool_call_id: toolCall.id
|
|
214
|
-
});
|
|
215
|
-
return {
|
|
216
|
-
messages: [...state.messages, toolMessage],
|
|
217
|
-
jump_to: "model"
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
default: throw new Error(`Unknown response type: ${response.type}`);
|
|
252
|
+
const toolConfig = resolvedToolConfigs[toolCall.name];
|
|
253
|
+
if (response.type === "accept" && toolConfig?.allowAccept) {
|
|
254
|
+
approvedToolCalls.push(toolCall);
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
if (response.type === "edit" && toolConfig?.allowEdit) {
|
|
258
|
+
const edited = response.args;
|
|
259
|
+
approvedToolCalls.push({
|
|
260
|
+
id: toolCall.id,
|
|
261
|
+
name: edited.action,
|
|
262
|
+
args: edited.args
|
|
263
|
+
});
|
|
264
|
+
continue;
|
|
221
265
|
}
|
|
266
|
+
if (response.type === "response" && toolConfig?.allowRespond) {
|
|
267
|
+
const content = response.args ?? `User rejected the tool call for \`${toolCall.name}\` with id ${toolCall.id}`;
|
|
268
|
+
/**
|
|
269
|
+
* Providing a meaningful error message for this case that should never happen.
|
|
270
|
+
*/
|
|
271
|
+
if (!toolCall.id) throw new Error(`Can't provide custom tool response for tool call without an ID: ${toolCall.name}!\nThis use case is not expected to happen, please report this as a bug.`);
|
|
272
|
+
/**
|
|
273
|
+
* ToolMessage expects a string, so we need to throw an error if it's not a string
|
|
274
|
+
* as we currently have no way to proper type responses from users through the
|
|
275
|
+
* Command object.
|
|
276
|
+
*/
|
|
277
|
+
if (typeof content !== "string") throw new Error(`Tool call response for "${toolCall.name}" must be a string, got ${typeof content}`);
|
|
278
|
+
artificialToolMessages.push(new __langchain_core_messages.ToolMessage({
|
|
279
|
+
content,
|
|
280
|
+
name: toolCall.name,
|
|
281
|
+
tool_call_id: toolCall.id,
|
|
282
|
+
status: "error"
|
|
283
|
+
}));
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
const allowedActions = [
|
|
287
|
+
toolConfig?.allowAccept && "accept",
|
|
288
|
+
toolConfig?.allowEdit && "edit",
|
|
289
|
+
toolConfig?.allowRespond && "response"
|
|
290
|
+
].filter(Boolean).join("\", \"");
|
|
291
|
+
throw new Error(`Unexpected human response: ${JSON.stringify(response)}. Response action '${response.type}' is not allowed for tool '${toolCall.name}'. Expected one of: "${allowedActions}", based on the tool's configuration.`);
|
|
222
292
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
293
|
+
/**
|
|
294
|
+
* Replace the tool calls with the approved tool calls
|
|
295
|
+
*/
|
|
296
|
+
if (__langchain_core_messages.AIMessage.isInstance(lastMessage)) lastMessage.tool_calls = lastMessage.tool_calls?.map((tc) => {
|
|
297
|
+
const approvedToolCall = approvedToolCalls.find((atc) => atc.id === tc.id);
|
|
298
|
+
return approvedToolCall ?? tc;
|
|
227
299
|
});
|
|
228
|
-
return { messages: [...state.messages
|
|
300
|
+
if (approvedToolCalls.length > 0) return { messages: [...state.messages, ...artificialToolMessages] };
|
|
301
|
+
return {
|
|
302
|
+
jumpTo: "model",
|
|
303
|
+
messages: [...state.messages, ...artificialToolMessages]
|
|
304
|
+
};
|
|
229
305
|
}
|
|
230
306
|
});
|
|
231
307
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hitl.cjs","names":["z","options: z.input<typeof contextSchema>","createMiddleware","interruptToolCalls: ToolCall[]","autoApprovedToolCalls: ToolCall[]","normalizedToolCall: ToolCall","requests: ToolApprovalRequest[]","ToolMessage","AIMessage"],"sources":["../../../../src/agents/middlewareAgent/middleware/hitl.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { z } from \"zod/v3\";\nimport { v4 as uuid } from \"uuid\";\nimport { AIMessage, ToolMessage, isAIMessage } from \"@langchain/core/messages\";\nimport { interrupt } from \"@langchain/langgraph\";\n\nimport { createMiddleware } from \"../middleware.js\";\nimport type { ToolCall } from \"../types.js\";\nimport { HumanResponse } from \"../../interrupt.js\";\n\n/**\n * Interrupt request for tool approval\n */\ninterface ToolApprovalRequest {\n action: string;\n args: Record<string, any>;\n toolCallId: string;\n description?: string;\n}\n\nconst contextSchema = z.object({\n toolConfigs: z\n .record(\n z.object({\n requireApproval: z.boolean().optional(),\n description: z.string().optional(),\n })\n )\n .default({}),\n messagePrefix: z.string().default(\"Tool execution requires approval\"),\n});\n\n/**\n * Creates a Human-in-the-Loop (HITL) middleware for tool approval and oversight.\n *\n * This middleware intercepts tool calls made by an AI agent and provides human oversight\n * capabilities before execution. It enables selective approval workflows where certain tools\n * require human intervention while others can execute automatically.\n *\n * ## Features\n *\n * - **Selective Tool Approval**: Configure which tools require human approval\n * - **Multiple Response Types**: Accept, edit, ignore, or manually respond to tool calls\n * - **Asynchronous Workflow**: Uses LangGraph's interrupt mechanism for non-blocking approval\n * - **Custom Approval Messages**: Provide context-specific descriptions for approval requests\n *\n * ## Response Types\n *\n * When a tool requires approval, the human operator can respond with:\n * - `accept`: Execute the tool with original arguments\n * - `edit`: Modify the tool arguments before execution\n * - `ignore`: Skip the tool and terminate the agent\n * - `response`: Provide a manual response instead of executing the tool\n *\n * @param options - Configuration options for the middleware\n * @param options.toolConfigs - Per-tool configuration mapping tool names to their settings\n * @param options.toolConfigs[toolName].requireApproval - Whether the tool requires human approval\n * @param options.toolConfigs[toolName].description - Custom approval message for the tool\n * @param options.messagePrefix - Default prefix for approval messages (default: \"Tool execution requires approval\")\n *\n * @returns A middleware instance that can be passed to `createAgent`\n *\n * @example\n * Basic usage with selective tool approval\n * ```typescript\n * import { humanInTheLoopMiddleware } from \"langchain/middleware\";\n * import { createAgent } from \"langchain\";\n *\n * const hitlMiddleware = humanInTheLoopMiddleware({\n * toolConfigs: {\n * \"write_file\": {\n * requireApproval: true,\n * description: \"⚠️ File write operation requires approval\"\n * },\n * \"read_file\": {\n * requireApproval: false // Safe operation, no approval needed\n * }\n * }\n * });\n *\n * const agent = createAgent({\n * model: \"openai:gpt-4\",\n * tools: [writeFileTool, readFileTool],\n * middleware: [hitlMiddleware]\n * });\n * ```\n *\n * @example\n * Handling approval requests\n * ```typescript\n * import { Command } from \"@langchain/langgraph\";\n *\n * // Initial agent invocation\n * const result = await agent.invoke({\n * messages: [new HumanMessage(\"Write 'Hello' to output.txt\")]\n * }, config);\n *\n * // Check if agent is paused for approval\n * const state = await agent.graph.getState(config);\n * if (state.next?.length > 0) {\n * // Get interrupt details\n * const task = state.tasks?.[0];\n * const requests = task?.interrupts?.[0]?.value;\n *\n * // Show tool call details to user\n * console.log(\"Tool:\", requests[0].action);\n * console.log(\"Args:\", requests[0].args);\n *\n * // Resume with approval\n * await agent.invoke(\n * new Command({ resume: [{ type: \"accept\" }] }),\n * config\n * );\n * }\n * ```\n *\n * @example\n * Different response types\n * ```typescript\n * // Accept the tool call as-is\n * new Command({ resume: [{ type: \"accept\" }] })\n *\n * // Edit the tool arguments\n * new Command({\n * resume: [{\n * type: \"edit\",\n * args: { action: \"write_file\", args: { filename: \"safe.txt\", content: \"Modified\" } }\n * }]\n * })\n *\n * // Skip tool and terminate agent\n * new Command({ resume: [{ type: \"ignore\" }] })\n *\n * // Provide manual response\n * new Command({\n * resume: [{\n * type: \"response\",\n * args: \"File operation not allowed in demo mode\"\n * }]\n * })\n * ```\n *\n * @example\n * Production use case with database operations\n * ```typescript\n * const hitlMiddleware = humanInTheLoopMiddleware({\n * toolConfigs: {\n * \"execute_sql\": {\n * requireApproval: true,\n * description: \"🚨 SQL query requires DBA approval\\nPlease review for safety and performance\"\n * },\n * \"read_schema\": {\n * requireApproval: false // Reading metadata is safe\n * },\n * \"delete_records\": {\n * requireApproval: true,\n * description: \"⛔ DESTRUCTIVE OPERATION - Requires manager approval\"\n * }\n * },\n * messagePrefix: \"Database operation pending approval\"\n * });\n * ```\n *\n * @remarks\n * - Tool calls are processed in the order they appear in the AI message\n * - Auto-approved tools execute immediately without interruption\n * - Multiple tools requiring approval are bundled into a single interrupt\n * - The middleware operates in the `afterModel` phase, intercepting before tool execution\n * - Requires a checkpointer to maintain state across interruptions\n *\n * @see {@link createAgent} for agent creation\n * @see {@link Command} for resuming interrupted execution\n * @public\n */\nexport function humanInTheLoopMiddleware(\n options: z.input<typeof contextSchema> = {}\n) {\n return createMiddleware({\n name: \"HumanInTheLoopMiddleware\",\n contextSchema,\n afterModel: async (state, runtime, controls) => {\n const config = { ...contextSchema.parse(options), ...runtime.context };\n const { messages } = state;\n\n if (!messages.length) {\n return;\n }\n\n const lastMessage = messages[messages.length - 1];\n\n // Check if it's an AI message with tool calls\n if (!isAIMessage(lastMessage) || !lastMessage.tool_calls?.length) {\n return;\n }\n\n // Separate tool calls that need interrupts from those that don't\n const interruptToolCalls: ToolCall[] = [];\n const autoApprovedToolCalls: ToolCall[] = [];\n\n for (const toolCall of lastMessage.tool_calls) {\n // Ensure tool call has an ID\n const normalizedToolCall: ToolCall = {\n id: toolCall.id || uuid(),\n name: toolCall.name,\n args: toolCall.args,\n };\n\n const toolConfig = config.toolConfigs[normalizedToolCall.name];\n\n if (toolConfig?.requireApproval) {\n interruptToolCalls.push(normalizedToolCall);\n } else {\n autoApprovedToolCalls.push(normalizedToolCall);\n }\n }\n\n // If no interrupts needed, return early\n if (!interruptToolCalls.length) {\n return;\n }\n\n const approvedToolCalls = [...autoApprovedToolCalls];\n\n // Process tool calls that need interrupts\n const requests: ToolApprovalRequest[] = interruptToolCalls.map(\n (toolCall) => {\n const toolConfig = config.toolConfigs[toolCall.name];\n const description =\n toolConfig?.description ||\n `${config.messagePrefix}\\n\\nTool: ${\n toolCall.name\n }\\nArgs: ${JSON.stringify(toolCall.args, null, 2)}`;\n\n return {\n action: toolCall.name,\n args: toolCall.args,\n toolCallId: toolCall.id,\n description,\n };\n }\n );\n\n // Interrupt and wait for human responses\n const responses = (await interrupt(requests)) as HumanResponse[];\n\n // Process responses\n for (let i = 0; i < responses.length; i++) {\n const response = responses[i];\n const toolCall = interruptToolCalls[i];\n\n switch (response.type) {\n case \"accept\":\n approvedToolCalls.push(toolCall);\n break;\n\n case \"edit\":\n // For edit, args is an ActionRequest with updated args\n if (\n response.args &&\n typeof response.args === \"object\" &&\n \"args\" in response.args\n ) {\n approvedToolCalls.push({\n ...toolCall,\n args: (\n response.args as { action: string; args: Record<string, any> }\n ).args,\n });\n }\n break;\n\n case \"ignore\":\n // Skip to end - terminate the agent\n return controls.terminate();\n\n case \"response\": {\n // Return manual tool response and jump back to model\n // For response, args is a string\n const toolMessage = new ToolMessage({\n content: typeof response.args === \"string\" ? response.args : \"\",\n tool_call_id: toolCall.id,\n });\n return {\n messages: [...state.messages, toolMessage],\n jump_to: \"model\",\n };\n }\n default:\n throw new Error(`Unknown response type: ${(response as any).type}`);\n }\n }\n\n // Update the last message with approved tool calls\n const updatedMessage = new AIMessage({\n content: lastMessage.content,\n tool_calls: approvedToolCalls,\n id: lastMessage.id,\n });\n\n // Replace the last message with the updated one\n return {\n messages: [...state.messages.slice(0, -1), updatedMessage],\n };\n },\n });\n}\n"],"mappings":";;;;;;;;AAoBA,MAAM,gBAAgBA,SAAE,OAAO;CAC7B,aAAaA,SACV,OACCA,SAAE,OAAO;EACP,iBAAiBA,SAAE,SAAS,CAAC,UAAU;EACvC,aAAaA,SAAE,QAAQ,CAAC,UAAU;CACnC,EAAC,CACH,CACA,QAAQ,CAAE,EAAC;CACd,eAAeA,SAAE,QAAQ,CAAC,QAAQ,mCAAmC;AACtE,EAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgJF,SAAgB,yBACdC,UAAyC,CAAE,GAC3C;AACA,QAAOC,oCAAiB;EACtB,MAAM;EACN;EACA,YAAY,OAAO,OAAO,SAAS,aAAa;GAC9C,MAAM,SAAS;IAAE,GAAG,cAAc,MAAM,QAAQ;IAAE,GAAG,QAAQ;GAAS;GACtE,MAAM,EAAE,UAAU,GAAG;AAErB,OAAI,CAAC,SAAS,OACZ;GAGF,MAAM,cAAc,SAAS,SAAS,SAAS;AAG/C,OAAI,4CAAa,YAAY,IAAI,CAAC,YAAY,YAAY,OACxD;GAIF,MAAMC,qBAAiC,CAAE;GACzC,MAAMC,wBAAoC,CAAE;AAE5C,QAAK,MAAM,YAAY,YAAY,YAAY;IAE7C,MAAMC,qBAA+B;KACnC,IAAI,SAAS,oBAAY;KACzB,MAAM,SAAS;KACf,MAAM,SAAS;IAChB;IAED,MAAM,aAAa,OAAO,YAAY,mBAAmB;AAEzD,QAAI,YAAY,iBACd,mBAAmB,KAAK,mBAAmB;SAE3C,sBAAsB,KAAK,mBAAmB;GAEjD;AAGD,OAAI,CAAC,mBAAmB,OACtB;GAGF,MAAM,oBAAoB,CAAC,GAAG,qBAAsB;GAGpD,MAAMC,WAAkC,mBAAmB,IACzD,CAAC,aAAa;IACZ,MAAM,aAAa,OAAO,YAAY,SAAS;IAC/C,MAAM,cACJ,YAAY,eACZ,GAAG,OAAO,cAAc,UAAU,EAChC,SAAS,KACV,QAAQ,EAAE,KAAK,UAAU,SAAS,MAAM,MAAM,EAAE,EAAE;AAErD,WAAO;KACL,QAAQ,SAAS;KACjB,MAAM,SAAS;KACf,YAAY,SAAS;KACrB;IACD;GACF,EACF;GAGD,MAAM,YAAa,2CAAgB,SAAS;AAG5C,QAAK,IAAI,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK;IACzC,MAAM,WAAW,UAAU;IAC3B,MAAM,WAAW,mBAAmB;AAEpC,YAAQ,SAAS,MAAjB;KACE,KAAK;MACH,kBAAkB,KAAK,SAAS;AAChC;KAEF,KAAK;AAEH,UACE,SAAS,QACT,OAAO,SAAS,SAAS,YACzB,UAAU,SAAS,MAEnB,kBAAkB,KAAK;OACrB,GAAG;OACH,MACE,SAAS,KACT;MACH,EAAC;AAEJ;KAEF,KAAK,SAEH,QAAO,SAAS,WAAW;KAE7B,KAAK,YAAY;MAGf,MAAM,cAAc,IAAIC,sCAAY;OAClC,SAAS,OAAO,SAAS,SAAS,WAAW,SAAS,OAAO;OAC7D,cAAc,SAAS;MACxB;AACD,aAAO;OACL,UAAU,CAAC,GAAG,MAAM,UAAU,WAAY;OAC1C,SAAS;MACV;KACF;KACD,QACE,OAAM,IAAI,MAAM,CAAC,uBAAuB,EAAG,SAAiB,MAAM;IACrE;GACF;GAGD,MAAM,iBAAiB,IAAIC,oCAAU;IACnC,SAAS,YAAY;IACrB,YAAY;IACZ,IAAI,YAAY;GACjB;AAGD,UAAO,EACL,UAAU,CAAC,GAAG,MAAM,SAAS,MAAM,GAAG,GAAG,EAAE,cAAe,EAC3D;EACF;CACF,EAAC;AACH"}
|
|
1
|
+
{"version":3,"file":"hitl.cjs","names":["z","options: NonNullable<HumanInTheLoopMiddlewareConfig>","createMiddleware","AIMessage","resolvedToolConfigs: Record<string, ToolConfig>","interruptToolCalls: ToolCall[]","autoApprovedToolCalls: ToolCall[]","hitlRequests: HumanInTheLoopRequest[]","approvedToolCalls: ToolCall[]","artificialToolMessages: ToolMessage[]","ToolMessage"],"sources":["../../../../src/agents/middlewareAgent/middleware/hitl.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { z } from \"zod/v3\";\nimport { AIMessage, ToolMessage } from \"@langchain/core/messages\";\nimport {\n InferInteropZodInput,\n interopParse,\n} from \"@langchain/core/utils/types\";\nimport { interrupt } from \"@langchain/langgraph\";\n\nimport { createMiddleware } from \"../middleware.js\";\n\nconst ToolConfigSchema = z.object({\n /**\n * Whether the human can approve the current action without changes\n */\n allowAccept: z.boolean().optional(),\n /**\n * Whether the human can reject the current action with feedback\n */\n allowEdit: z.boolean().optional(),\n /**\n * Whether the human can approve the current action with edited content\n */\n allowRespond: z.boolean().optional(),\n /**\n * The description attached to the request for human input\n */\n description: z.string().optional(),\n});\n\ntype ToolConfigSchema = z.input<typeof ToolConfigSchema>;\ntype ToolCall = NonNullable<AIMessage[\"tool_calls\"]>[number];\n\n/**\n * Represents information about an interrupt.\n */\nexport interface Interrupt<TValue = unknown> {\n /**\n * The ID of the interrupt.\n */\n id: string;\n /**\n * The requests for human input.\n */\n value: TValue;\n}\n\n/**\n * Configuration that defines which reviewer response types are permitted during a human interrupt.\n * These flags control what the human reviewer may do (e.g., accept/edit/respond),\n * not the tool action the agent has requested.\n */\nexport interface HumanInTheLoopConfig\n extends Omit<ToolConfigSchema, \"description\"> {}\n\n/**\n * Describes the agent-requested tool action (name and arguments).\n * This is what the AI intends to execute, subject to human review.\n */\nexport interface ActionRequest {\n /**\n * The tool/action name requested by the agent (e.g., \"send_email\").\n */\n action: string;\n /**\n * Arguments for the requested tool call (e.g., {\"a\": 1, \"b\": 2}).\n */\n args: Record<string, any>;\n}\n\n/**\n * Represents an interrupt triggered by the graph that requires human intervention\n * to approve, edit, or respond to an agent-requested tool action.\n *\n * @example\n * ```ts\n * const hitlRequest: HumanInTheLoopRequest = {\n * actionRequest: { action: \"Approve XYZ action\", args: { ... } },\n * config: { allowAccept: true, allowEdit: true, allowRespond: true },\n * description: \"Please review the command before execution\"\n * };\n * response = interrupt([request])[0]\n * ```\n */\nexport interface HumanInTheLoopRequest {\n /**\n * The agent-requested tool action to be reviewed.\n */\n actionRequest: ActionRequest;\n /**\n * Which reviewer responses are allowed (accept/edit/respond).\n */\n config: HumanInTheLoopConfig;\n /**\n * Optional human-facing description shown in the approval prompt.\n */\n description?: string;\n}\n\n/**\n * Response when a human approves the agent-requested action.\n */\nexport interface AcceptPayload {\n type: \"accept\";\n}\n\n/**\n * Response when a human provides a manual response instead of executing\n * the agent-requested action.\n */\nexport interface ResponsePayload {\n type: \"response\";\n args?: string;\n}\n\n/**\n * Response when a human edits the agent-requested action (tool name and/or args).\n */\nexport interface EditPayload {\n type: \"edit\";\n args: ActionRequest;\n}\n\nexport type HumanInTheLoopMiddlewareHumanResponse =\n | AcceptPayload\n | ResponsePayload\n | EditPayload;\n\n/**\n * Configuration for a tool requiring human in the loop.\n */\nexport interface ToolConfig extends HumanInTheLoopConfig {\n /**\n * Human-facing description shown in the approval request.\n */\n description?: string;\n}\n\nconst contextSchema = z.object({\n /**\n * Mapping of tool name to allowed reviewer responses.\n * If a tool doesn't have an entry, it's auto-approved by default.\n *\n * - `true` -> pause for approval and allow accept/edit/respond\n * - `false` -> auto-approve (no human review)\n * - `ToolConfig` -> explicitly specify which reviewer responses are allowed for this tool\n */\n interruptOn: z.record(z.union([z.boolean(), ToolConfigSchema])).optional(),\n /**\n * Prefix used when constructing human-facing approval messages.\n * Provides context about the tool call being reviewed; does not change the underlying action.\n *\n * Note: This prefix is only applied for tools that do not provide a custom\n * `description` via their {@link ToolConfig}. If a tool specifies a custom\n * `description`, that per-tool text is used and this prefix is ignored.\n */\n descriptionPrefix: z.string().default(\"Tool execution requires approval\"),\n});\nexport type HumanInTheLoopMiddlewareConfig = InferInteropZodInput<\n typeof contextSchema\n>;\n\n/**\n * Creates a Human-in-the-Loop (HITL) middleware for tool approval and oversight.\n *\n * This middleware intercepts tool calls made by an AI agent and provides human oversight\n * capabilities before execution. It enables selective approval workflows where certain tools\n * require human intervention while others can execute automatically.\n *\n * A invocation result that has been interrupted by the middleware will have a `__interrupt__`\n * property that contains the interrupt request. You can loop over the request to determine\n * which tools were interrupted, and how to handle them separately.\n *\n * ```ts\n * import { type ToolApprovalRequest, type HumanInTheLoopMiddlewareHumanResponse } from \"langchain/middleware\";\n * import { type Interrupt } from \"langchain\";\n *\n * const result = await agent.invoke(request);\n * const interruptRequest = initialResult.__interrupt__?.[0] as Interrupt<\n * ToolApprovalRequest[]\n * >;\n * const resume: HumanInTheLoopMiddlewareHumanResponse[] =\n * interruptRequest.value.map((request) => {\n * if (request.action === \"calculator\") {\n * return { id: request.toolCallId, type: \"accept\" };\n * } else if (request.action === \"write_file\") {\n * return {\n * id: request.toolCallId,\n * type: \"edit\",\n * args: { filename: \"safe.txt\", content: \"Safe content\" },\n * };\n * }\n *\n * throw new Error(`Unknown action: ${request.action}`);\n * });\n *\n * // Resume with approval\n * await agent.invoke(new Command({ resume }), config);\n * ```\n *\n * ## Features\n *\n * - **Selective Tool Approval**: Configure which tools require human approval\n * - **Multiple Response Types**: Accept, edit, ignore, or manually respond to tool calls\n * - **Asynchronous Workflow**: Uses LangGraph's interrupt mechanism for non-blocking approval\n * - **Custom Approval Messages**: Provide context-specific descriptions for approval requests\n *\n * ## Response Types\n *\n * When a tool requires approval, the human operator can respond with:\n * - `accept`: Execute the tool with original arguments\n * - `edit`: Modify the tool arguments before execution\n * - `ignore`: Skip the tool and terminate the agent\n * - `response`: Provide a manual response instead of executing the tool\n *\n * @param options - Configuration options for the middleware\n * @param options.interruptOn - Per-tool configuration mapping tool names to their settings\n * @param options.interruptOn[toolName].allowAccept - Whether the human can approve the current action without changes\n * @param options.interruptOn[toolName].allowEdit - Whether the human can reject the current action with feedback\n * @param options.interruptOn[toolName].allowRespond - Whether the human can approve the current action with edited content\n * @param options.interruptOn[toolName].description - Custom approval message for the tool\n * @param options.messagePrefix - Default prefix for approval messages (default: \"Tool execution requires approval\"). Only used for tools that do not define a custom `description` in their ToolConfig.\n *\n * @returns A middleware instance that can be passed to `createAgent`\n *\n * @example\n * Basic usage with selective tool approval\n * ```typescript\n * import { humanInTheLoopMiddleware } from \"langchain/middleware\";\n * import { createAgent } from \"langchain\";\n *\n * const hitlMiddleware = humanInTheLoopMiddleware({\n * interruptOn: {\n * // Interrupt write_file tool and allow edits or accepts\n * \"write_file\": {\n * allowEdit: true,\n * allowAccept: true,\n * description: \"⚠️ File write operation requires approval\"\n * },\n * // Auto-approve read_file tool\n * \"read_file\": false\n * }\n * });\n *\n * const agent = createAgent({\n * model: \"openai:gpt-4\",\n * tools: [writeFileTool, readFileTool],\n * middleware: [hitlMiddleware]\n * });\n * ```\n *\n * @example\n * Handling approval requests\n * ```typescript\n * import { type HumanInTheLoopRequest, type Interrupt } from \"langchain/middleware\";\n * import { Command } from \"@langchain/langgraph\";\n *\n * // Initial agent invocation\n * const result = await agent.invoke({\n * messages: [new HumanMessage(\"Write 'Hello' to output.txt\")]\n * }, config);\n *\n * // Check if agent is paused for approval\n * if (result.__interrupt__) {\n * const interruptRequest = initialResult.__interrupt__?.[0] as Interrupt<\n * HumanInTheLoopRequest[]\n * >;\n *\n * // Show tool call details to user\n * console.log(\"Tool:\", interruptRequest.value[0].actionRequest);\n * console.log(\"Allowed actions:\", interruptRequest.value[0].config);\n *\n * // Resume with approval\n * await agent.invoke(\n * new Command({ resume: [{ type: \"accept\" }] }),\n * config\n * );\n * }\n * ```\n *\n * @example\n * Different response types\n * ```typescript\n * // Accept the tool call as-is\n * new Command({ resume: [{ type: \"accept\" }] })\n *\n * // Edit the tool arguments\n * new Command({\n * resume: [{\n * type: \"edit\",\n * args: { action: \"write_file\", args: { filename: \"safe.txt\", content: \"Modified\" } }\n * }]\n * })\n *\n * // Skip tool and terminate agent\n * new Command({ resume: [{ type: \"response\" }] })\n *\n * // Provide manual response\n * new Command({\n * resume: [{\n * type: \"response\",\n * // this must be a string\n * args: \"File operation not allowed in demo mode\"\n * }]\n * })\n * ```\n *\n * @example\n * Production use case with database operations\n * ```typescript\n * const hitlMiddleware = humanInTheLoopMiddleware({\n * interruptOn: {\n * \"execute_sql\": {\n * allowAccept: true,\n * allowEdit: true,\n * allowRespond: true,\n * description: \"🚨 SQL query requires DBA approval\\nPlease review for safety and performance\"\n * },\n * \"read_schema\": false // Reading metadata is safe\n * \"delete_records\": {\n * allowAccept: true,\n * description: \"⛔ DESTRUCTIVE OPERATION - Requires manager approval\"\n * }\n * },\n * messagePrefix: \"Database operation pending approval\"\n * });\n * ```\n *\n * @remarks\n * - Tool calls are processed in the order they appear in the AI message\n * - Auto-approved tools execute immediately without interruption\n * - Multiple tools requiring approval are bundled into a single interrupt\n * - The middleware operates in the `afterModel` phase, intercepting before tool execution\n * - Requires a checkpointer to maintain state across interruptions\n *\n * @see {@link createAgent} for agent creation\n * @see {@link Command} for resuming interrupted execution\n * @public\n */\nexport function humanInTheLoopMiddleware(\n options: NonNullable<HumanInTheLoopMiddlewareConfig>\n) {\n return createMiddleware({\n name: \"HumanInTheLoopMiddleware\",\n contextSchema,\n afterModelJumpTo: [\"model\"],\n afterModel: async (state, runtime) => {\n const config = interopParse(contextSchema, {\n ...options,\n ...(runtime.context || {}),\n });\n if (!config) {\n return;\n }\n\n const { messages } = state;\n if (!messages.length) {\n return;\n }\n\n /**\n * Don't do anything if the last message isn't an AI message with tool calls.\n */\n const lastMessage = [...messages]\n .reverse()\n .find((msg) => AIMessage.isInstance(msg)) as AIMessage;\n if (!lastMessage || !lastMessage.tool_calls?.length) {\n return;\n }\n\n /**\n * If the user omits the interruptOn config, we don't do anything.\n */\n if (!config.interruptOn) {\n return;\n }\n\n // Resolve per-tool configs (boolean true -> all actions allowed; false -> auto-approve)\n const resolvedToolConfigs: Record<string, ToolConfig> = {};\n for (const [toolName, toolConfig] of Object.entries(config.interruptOn)) {\n if (typeof toolConfig === \"boolean\") {\n if (toolConfig === true) {\n resolvedToolConfigs[toolName] = {\n allowAccept: true,\n allowEdit: true,\n allowRespond: true,\n };\n }\n } else {\n resolvedToolConfigs[toolName] = toolConfig;\n }\n }\n\n const interruptToolCalls: ToolCall[] = [];\n const autoApprovedToolCalls: ToolCall[] = [];\n\n for (const toolCall of lastMessage.tool_calls) {\n if (toolCall.name in resolvedToolConfigs) {\n interruptToolCalls.push(toolCall);\n } else {\n autoApprovedToolCalls.push(toolCall);\n }\n }\n\n /**\n * No interrupt tool calls, so we can just return.\n */\n if (!interruptToolCalls.length) {\n return;\n }\n\n const hitlRequests: HumanInTheLoopRequest[] = interruptToolCalls.map(\n (toolCall) => {\n const toolConfig = resolvedToolConfigs[toolCall.name]!;\n const description =\n toolConfig.description ||\n `${config.descriptionPrefix}\\n\\nTool: ${\n toolCall.name\n }\\nArgs: ${JSON.stringify(toolCall.args, null, 2)}`;\n return {\n actionRequest: { action: toolCall.name, args: toolCall.args },\n config: toolConfig,\n description,\n };\n }\n );\n\n const responses = (await interrupt(\n hitlRequests\n )) as HumanInTheLoopMiddlewareHumanResponse[];\n\n if (responses.length !== interruptToolCalls.length) {\n throw new Error(\n `Number of human responses (${responses.length}) does not match number of hanging tool calls (${interruptToolCalls.length}).`\n );\n }\n\n const approvedToolCalls: ToolCall[] = [...autoApprovedToolCalls];\n const artificialToolMessages: ToolMessage[] = [];\n\n for (const [i, response] of responses.entries()) {\n const toolCall = interruptToolCalls[i]!;\n const toolConfig = resolvedToolConfigs[toolCall.name]!;\n\n if (response.type === \"accept\" && toolConfig?.allowAccept) {\n approvedToolCalls.push(toolCall);\n continue;\n }\n\n if (response.type === \"edit\" && toolConfig?.allowEdit) {\n const edited = response.args;\n approvedToolCalls.push({\n id: toolCall.id,\n name: edited.action,\n args: edited.args,\n });\n continue;\n }\n\n if (response.type === \"response\" && toolConfig?.allowRespond) {\n const content =\n response.args ??\n `User rejected the tool call for \\`${toolCall.name}\\` with id ${toolCall.id}`;\n\n /**\n * Providing a meaningful error message for this case that should never happen.\n */\n if (!toolCall.id) {\n throw new Error(\n `Can't provide custom tool response for tool call without an ID: ${toolCall.name}!\\n` +\n \"This use case is not expected to happen, please report this as a bug.\"\n );\n }\n\n /**\n * ToolMessage expects a string, so we need to throw an error if it's not a string\n * as we currently have no way to proper type responses from users through the\n * Command object.\n */\n if (typeof content !== \"string\") {\n throw new Error(\n `Tool call response for \"${\n toolCall.name\n }\" must be a string, got ${typeof content}`\n );\n }\n\n artificialToolMessages.push(\n new ToolMessage({\n content,\n name: toolCall.name,\n tool_call_id: toolCall.id,\n status: \"error\",\n })\n );\n continue;\n }\n\n const allowedActions = [\n toolConfig?.allowAccept && \"accept\",\n toolConfig?.allowEdit && \"edit\",\n toolConfig?.allowRespond && \"response\",\n ]\n .filter(Boolean)\n .join('\", \"');\n throw new Error(\n `Unexpected human response: ${JSON.stringify(\n response\n )}. Response action '${response.type}' is not allowed for tool '${\n toolCall.name\n }'. Expected one of: \"${allowedActions}\", based on the tool's configuration.`\n );\n }\n\n /**\n * Replace the tool calls with the approved tool calls\n */\n if (AIMessage.isInstance(lastMessage)) {\n lastMessage.tool_calls = lastMessage.tool_calls?.map((tc) => {\n const approvedToolCall = approvedToolCalls.find(\n (atc) => atc.id === tc.id\n );\n return approvedToolCall ?? tc;\n });\n }\n\n if (approvedToolCalls.length > 0) {\n return { messages: [...state.messages, ...artificialToolMessages] };\n }\n\n return {\n jumpTo: \"model\",\n messages: [...state.messages, ...artificialToolMessages],\n };\n },\n });\n}\n"],"mappings":"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"}
|