Skip to content

Commit cc12263

Browse files
authored
feat(langgraph): add type bag pattern for GraphNode and ConditionalEdgeRouter (#1918)
1 parent 9f34c8c commit cc12263

File tree

10 files changed

+1199
-117
lines changed

10 files changed

+1199
-117
lines changed

.changeset/type-bag-pattern.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
---
2+
"@langchain/langgraph": patch
3+
---
4+
5+
Add type bag pattern for `GraphNode` and `ConditionalEdgeRouter` type utilities.
6+
7+
**New types:**
8+
- `GraphNodeTypes<InputSchema, OutputSchema, ContextSchema, Nodes>` - Type bag interface for GraphNode
9+
- `GraphNodeReturnValue<Update, Nodes>` - Return type helper for node functions
10+
- `ConditionalEdgeRouterTypes<InputSchema, ContextSchema, Nodes>` - Type bag interface for ConditionalEdgeRouter
11+
12+
**Usage:**
13+
14+
Both `GraphNode` and `ConditionalEdgeRouter` now support two patterns:
15+
16+
1. **Single schema** (backward compatible):
17+
```typescript
18+
const node: GraphNode<typeof AgentState, MyContext, "agent" | "tool"> = ...
19+
```
20+
21+
2. **Type bag pattern** (new):
22+
```typescript
23+
const node: GraphNode<{
24+
InputSchema: typeof InputSchema;
25+
OutputSchema: typeof OutputSchema;
26+
ContextSchema: typeof ContextSchema;
27+
Nodes: "agent" | "tool";
28+
}> = (state, runtime) => {
29+
// state type inferred from InputSchema
30+
// return type validated against OutputSchema
31+
// runtime.configurable type inferred from ContextSchema
32+
return { answer: "response" };
33+
};
34+
```
35+
36+
The type bag pattern enables nodes that receive a subset of state fields and return different fields, with full type safety.

libs/langgraph-core/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"dependencies": {
3636
"@langchain/langgraph-checkpoint": "workspace:^",
3737
"@langchain/langgraph-sdk": "workspace:~",
38+
"@standard-schema/spec": "1.1.0",
3839
"uuid": "^10.0.0"
3940
},
4041
"peerDependencies": {
@@ -55,7 +56,6 @@
5556
"@langchain/openai": "^1.0.0",
5657
"@langchain/scripts": ">=0.1.3 <0.2.0",
5758
"@langchain/tavily": "^1.0.0",
58-
"@standard-schema/spec": "1.1.0",
5959
"@swc/core": "^1.3.90",
6060
"@testing-library/dom": "^10.4.0",
6161
"@tsconfig/recommended": "^1.0.3",
@@ -191,4 +191,4 @@
191191
"files": [
192192
"dist/"
193193
]
194-
}
194+
}

libs/langgraph-core/src/graph/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,8 @@ export type {
2828
ExtractStateType,
2929
ExtractUpdateType,
3030
GraphNode,
31+
GraphNodeTypes,
32+
GraphNodeReturnValue,
3133
ConditionalEdgeRouter,
34+
ConditionalEdgeRouterTypes,
3235
} from "./types.js";

libs/langgraph-core/src/graph/state.ts

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -416,8 +416,7 @@ export class StateGraph<
416416
N,
417417
InterruptType,
418418
WriterType
419-
>,
420-
contextSchema?: C | AnnotationRoot<ToStateDefinition<C>>
419+
>
421420
);
422421

423422
/** @deprecated Use `Annotation.Root`, `StateSchema`, or Zod schemas instead. */
@@ -434,7 +433,14 @@ export class StateGraph<
434433
options?:
435434
| C
436435
| AnnotationRoot<ToStateDefinition<C>>
437-
| StateGraphOptions<I, O, C, N, InterruptType, WriterType>
436+
| StateGraphOptions<
437+
I,
438+
O,
439+
C extends ContextSchemaInit ? C : undefined,
440+
N,
441+
InterruptType,
442+
WriterType
443+
>
438444
) {
439445
super();
440446

@@ -523,13 +529,14 @@ export class StateGraph<
523529
): StateGraphInit<StateDefinitionInit, I, O, C> {
524530
// Check if already StateGraphInit format
525531
if (isStateGraphInit(stateOrInit)) {
526-
// Merge any 2nd arg options
532+
// Second arg can be either a direct context schema or an options object
527533
if (isInteropZodObject(options) || AnnotationRoot.isInstance(options)) {
528534
return {
529535
...stateOrInit,
530536
context: options as C,
531537
};
532538
}
539+
// Merge any 2nd arg options
533540
const opts = options as StateGraphOptions<I, O> | undefined;
534541
return {
535542
...stateOrInit,
@@ -547,7 +554,7 @@ export class StateGraph<
547554
// Second arg can be either a direct context schema or an options object
548555
if (isInteropZodObject(options) || AnnotationRoot.isInstance(options)) {
549556
return {
550-
state: stateOrInit as StateDefinitionInit,
557+
state: stateOrInit,
551558
context: options as C,
552559
};
553560
}
@@ -725,6 +732,31 @@ export class StateGraph<
725732
MergeReturnType<NodeReturnType, { [key in K]: NodeOutput }>
726733
>;
727734

735+
override addNode<
736+
K extends string,
737+
InputSchema extends StateDefinitionInit,
738+
NodeOutput extends U = U
739+
>(
740+
key: K,
741+
action: NodeAction<
742+
ExtractStateType<InputSchema>,
743+
NodeOutput,
744+
C,
745+
InterruptType,
746+
WriterType
747+
>,
748+
options: StateGraphAddNodeOptions<N | K, InputSchema>
749+
): StateGraph<
750+
SD,
751+
S,
752+
U,
753+
N | K,
754+
I,
755+
O,
756+
C,
757+
MergeReturnType<NodeReturnType, { [key in K]: NodeOutput }>
758+
>;
759+
728760
override addNode<K extends string, NodeInput = S, NodeOutput extends U = U>(
729761
key: K,
730762
action: NodeAction<NodeInput, NodeOutput, C, InterruptType, WriterType>,

0 commit comments

Comments
 (0)