Skip to content

Commit d6692bb

Browse files
use interoparse
1 parent d42d299 commit d6692bb

File tree

6 files changed

+67
-55
lines changed

6 files changed

+67
-55
lines changed

libs/langchain/src/agents/middlewareAgent/middleware.ts

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type {
44
InteropZodDefault,
55
InteropZodOptional,
66
InferInteropZodInput,
7+
InferInteropZodOutput,
78
} from "@langchain/core/utils/types";
89
import type {
910
AgentMiddleware,
@@ -104,11 +105,11 @@ export function createMiddleware<
104105
(TSchema extends InteropZodObject ? InferInteropZodInput<TSchema> : {}) &
105106
AgentBuiltInState,
106107
TContextSchema extends InteropZodObject
107-
? InferInteropZodInput<TContextSchema>
108+
? InferInteropZodOutput<TContextSchema>
108109
: TContextSchema extends InteropZodDefault
109-
? InferInteropZodInput<TContextSchema>
110+
? InferInteropZodOutput<TContextSchema>
110111
: TContextSchema extends InteropZodOptional
111-
? Partial<InferInteropZodInput<TContextSchema>>
112+
? Partial<InferInteropZodOutput<TContextSchema>>
112113
: never
113114
>
114115
) => Promise<ModelRequest | void> | ModelRequest | void;
@@ -129,11 +130,11 @@ export function createMiddleware<
129130
(TSchema extends InteropZodObject ? InferInteropZodInput<TSchema> : {}) &
130131
AgentBuiltInState,
131132
TContextSchema extends InteropZodObject
132-
? InferInteropZodInput<TContextSchema>
133+
? InferInteropZodOutput<TContextSchema>
133134
: TContextSchema extends InteropZodDefault
134-
? InferInteropZodInput<TContextSchema>
135+
? InferInteropZodOutput<TContextSchema>
135136
: TContextSchema extends InteropZodOptional
136-
? Partial<InferInteropZodInput<TContextSchema>>
137+
? Partial<InferInteropZodOutput<TContextSchema>>
137138
: never
138139
>
139140
) =>
@@ -168,11 +169,11 @@ export function createMiddleware<
168169
(TSchema extends InteropZodObject ? InferInteropZodInput<TSchema> : {}) &
169170
AgentBuiltInState,
170171
TContextSchema extends InteropZodObject
171-
? InferInteropZodInput<TContextSchema>
172+
? InferInteropZodOutput<TContextSchema>
172173
: TContextSchema extends InteropZodDefault
173-
? InferInteropZodInput<TContextSchema>
174+
? InferInteropZodOutput<TContextSchema>
174175
: TContextSchema extends InteropZodOptional
175-
? Partial<InferInteropZodInput<TContextSchema>>
176+
? Partial<InferInteropZodOutput<TContextSchema>>
176177
: never
177178
>
178179
) =>
@@ -211,11 +212,11 @@ export function createMiddleware<
211212
: {}) &
212213
AgentBuiltInState,
213214
TContextSchema extends InteropZodObject
214-
? InferInteropZodInput<TContextSchema>
215+
? InferInteropZodOutput<TContextSchema>
215216
: TContextSchema extends InteropZodDefault
216-
? InferInteropZodInput<TContextSchema>
217+
? InferInteropZodOutput<TContextSchema>
217218
: TContextSchema extends InteropZodOptional
218-
? Partial<InferInteropZodInput<TContextSchema>>
219+
? Partial<InferInteropZodOutput<TContextSchema>>
219220
: never
220221
>
221222
)
@@ -233,11 +234,11 @@ export function createMiddleware<
233234
: {}) &
234235
AgentBuiltInState,
235236
TContextSchema extends InteropZodObject
236-
? InferInteropZodInput<TContextSchema>
237+
? InferInteropZodOutput<TContextSchema>
237238
: TContextSchema extends InteropZodDefault
238-
? InferInteropZodInput<TContextSchema>
239+
? InferInteropZodOutput<TContextSchema>
239240
: TContextSchema extends InteropZodOptional
240-
? Partial<InferInteropZodInput<TContextSchema>>
241+
? Partial<InferInteropZodOutput<TContextSchema>>
241242
: never
242243
>
243244
)
@@ -255,11 +256,11 @@ export function createMiddleware<
255256
: {}) &
256257
AgentBuiltInState,
257258
TContextSchema extends InteropZodObject
258-
? InferInteropZodInput<TContextSchema>
259+
? InferInteropZodOutput<TContextSchema>
259260
: TContextSchema extends InteropZodDefault
260-
? InferInteropZodInput<TContextSchema>
261+
? InferInteropZodOutput<TContextSchema>
261262
: TContextSchema extends InteropZodOptional
262-
? Partial<InferInteropZodInput<TContextSchema>>
263+
? Partial<InferInteropZodOutput<TContextSchema>>
263264
: never
264265
>
265266
)

libs/langchain/src/agents/middlewareAgent/middleware/hitl.ts

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { AIMessage, ToolMessage } from "@langchain/core/messages";
44
import {
55
InferInteropZodInput,
66
InteropZodObject,
7+
interopParse,
78
} from "@langchain/core/utils/types";
89
import { interrupt } from "@langchain/langgraph";
910

@@ -137,28 +138,26 @@ export interface ToolConfig extends HumanInTheLoopConfig {
137138
description?: string;
138139
}
139140

140-
const contextSchema = z
141-
.object({
142-
/**
143-
* Mapping of tool name to allowed reviewer responses.
144-
* If a tool doesn't have an entry, it's auto-approved by default.
145-
*
146-
* - `true` -> pause for approval and allow accept/edit/respond
147-
* - `false` -> auto-approve (no human review)
148-
* - `ToolConfig` -> explicitly specify which reviewer responses are allowed for this tool
149-
*/
150-
interruptOn: z.record(z.union([z.boolean(), ToolConfigSchema])).default({}),
151-
/**
152-
* Prefix used when constructing human-facing approval messages.
153-
* Provides context about the tool call being reviewed; does not change the underlying action.
154-
*
155-
* Note: This prefix is only applied for tools that do not provide a custom
156-
* `description` via their {@link ToolConfig}. If a tool specifies a custom
157-
* `description`, that per-tool text is used and this prefix is ignored.
158-
*/
159-
descriptionPrefix: z.string().default("Tool execution requires approval"),
160-
})
161-
.optional();
141+
const contextSchema = z.object({
142+
/**
143+
* Mapping of tool name to allowed reviewer responses.
144+
* If a tool doesn't have an entry, it's auto-approved by default.
145+
*
146+
* - `true` -> pause for approval and allow accept/edit/respond
147+
* - `false` -> auto-approve (no human review)
148+
* - `ToolConfig` -> explicitly specify which reviewer responses are allowed for this tool
149+
*/
150+
interruptOn: z.record(z.union([z.boolean(), ToolConfigSchema])),
151+
/**
152+
* Prefix used when constructing human-facing approval messages.
153+
* Provides context about the tool call being reviewed; does not change the underlying action.
154+
*
155+
* Note: This prefix is only applied for tools that do not provide a custom
156+
* `description` via their {@link ToolConfig}. If a tool specifies a custom
157+
* `description`, that per-tool text is used and this prefix is ignored.
158+
*/
159+
descriptionPrefix: z.string().default("Tool execution requires approval"),
160+
});
162161
export type HumanInTheLoopMiddlewareConfig = InferInteropZodInput<
163162
typeof contextSchema
164163
>;
@@ -341,7 +340,7 @@ export type HumanInTheLoopMiddlewareConfig = InferInteropZodInput<
341340
* @public
342341
*/
343342
export function humanInTheLoopMiddleware(
344-
options: HumanInTheLoopMiddlewareConfig = {}
343+
options: HumanInTheLoopMiddlewareConfig
345344
): AgentMiddleware<
346345
undefined,
347346
InteropZodObject,
@@ -352,7 +351,7 @@ export function humanInTheLoopMiddleware(
352351
contextSchema,
353352
afterModelJumpTo: ["model"],
354353
afterModel: async (state, runtime) => {
355-
const config = contextSchema.parse({ ...options, ...runtime.context });
354+
const config = interopParse(contextSchema, { ...options, ...runtime.context });
356355
if (!config) {
357356
return;
358357
}

libs/langchain/src/agents/middlewareAgent/middleware/summarization.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ import {
1111
isAIMessage,
1212
} from "@langchain/core/messages";
1313
import { BaseLanguageModel } from "@langchain/core/language_models/base";
14+
import {
15+
interopParse,
16+
InferInteropZodOutput,
17+
} from "@langchain/core/utils/types";
1418
import { REMOVE_ALL_MESSAGES } from "@langchain/langgraph";
1519
import { createMiddleware } from "../middleware.js";
1620

@@ -130,7 +134,10 @@ export function summarizationMiddleware(
130134
name: "SummarizationMiddleware",
131135
contextSchema,
132136
beforeModel: async (state, runtime) => {
133-
const config = { ...contextSchema.parse(options), ...runtime.context };
137+
const config = {
138+
...interopParse(contextSchema, options),
139+
...runtime.context,
140+
} as InferInteropZodOutput<typeof contextSchema>;
134141
const { messages } = state;
135142

136143
// Ensure all messages have IDs

libs/langchain/src/agents/middlewareAgent/nodes/AgentNode.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { type BaseChatModelCallOptions } from "@langchain/core/language_models/c
1414
import {
1515
InteropZodObject,
1616
getSchemaDescription,
17+
interopParse,
1718
} from "@langchain/core/utils/types";
1819
import type { ToolCall } from "@langchain/core/messages/tool";
1920

@@ -584,9 +585,9 @@ export class AgentNode<
584585
const middlewareList = this.#options.modifyModelRequestHookMiddleware;
585586
for (const [middleware, getMiddlewareState] of middlewareList) {
586587
// Merge context with default context of middleware
587-
const context =
588-
middleware.contextSchema?.parse(config?.context || {}) ??
589-
config?.context;
588+
const context = middleware.contextSchema
589+
? interopParse(middleware.contextSchema, config?.context || {})
590+
: config?.context;
590591

591592
// Create runtime
592593
const runtime: Runtime<unknown, unknown> = {

libs/langchain/src/agents/middlewareAgent/nodes/middleware.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
/* eslint-disable no-instanceof/no-instanceof */
33
import { z } from "zod/v3";
44
import { LangGraphRunnableConfig, Command } from "@langchain/langgraph";
5+
import { interopParse } from "@langchain/core/utils/types";
56

67
import { RunnableCallable } from "../../RunnableCallable.js";
78
import type {
@@ -44,24 +45,28 @@ export abstract class MiddlewareNode<
4445
*/
4546
let filteredContext = {} as TContextSchema;
4647
/**
47-
* Check both config.context and config.configurable.context
48+
* Parse context using middleware's contextSchema to apply defaults and validation
4849
*/
49-
if (this.middleware.contextSchema && config?.context) {
50+
if (this.middleware.contextSchema) {
5051
/**
5152
* Extract only the fields relevant to this middleware's schema
5253
*/
5354
const schemaShape = this.middleware.contextSchema?.shape;
5455
if (schemaShape) {
5556
const relevantContext: Record<string, unknown> = {};
56-
for (const key of Object.keys(schemaShape)) {
57-
if (key in config.context) {
58-
relevantContext[key] = config.context[key];
57+
if (config?.context) {
58+
for (const key of Object.keys(schemaShape)) {
59+
if (key in config.context) {
60+
relevantContext[key] = config.context[key];
61+
}
5962
}
6063
}
6164
/**
62-
* Parse to apply defaults and validation
65+
* Parse to apply defaults and validation, even if relevantContext is empty
66+
* This will throw if required fields are missing and no defaults exist
6367
*/
64-
filteredContext = this.middleware.contextSchema.parse(
68+
filteredContext = interopParse(
69+
this.middleware.contextSchema,
6570
relevantContext
6671
) as TContextSchema;
6772
}

libs/langchain/src/agents/middlewareAgent/types.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any */
2-
import { z } from "zod/v3";
32
import type {
43
InteropZodObject,
54
InteropZodDefault,
@@ -276,7 +275,7 @@ export type InferMiddlewareContext<T extends AgentMiddleware<any, any, any>> =
276275
export type InferMiddlewareContextInput<
277276
T extends AgentMiddleware<any, any, any>
278277
> = T extends AgentMiddleware<any, infer C, any>
279-
? C extends z.ZodOptional<infer Inner>
278+
? C extends InteropZodOptional<infer Inner>
280279
? InteropZodInput<Inner> | undefined
281280
: C extends InteropZodObject
282281
? InteropZodInput<C>

0 commit comments

Comments
 (0)