I am trying to use Annotation
to define my state
in Langgraph
since it seems to be the way they are doing in v0.2
. I dont seem to able to set a default value or get the state value from intial state i am passing inside the invoke. I am trying to use the case where we dont have to use a reducer
since it is will automatically replace the value with the last value. Does anyone know what I am doing wrong?
import { END, START, StateGraph, Annotation } from "@langchain/langgraph";
import { AIMessage, BaseMessage } from "@langchain/core/messages";
// Define the state structure with channels
const ConfigState = Annotation.Root({
greeting: Annotation<string>({ default: "Bonjour!" }), // Set the default value here
messages: Annotation<BaseMessage[]>({
reducer: (left: BaseMessage[], right: BaseMessage | BaseMessage[]) => {
if (Array.isArray(right)) {
return left.concat(right);
}
return left.concat([right]);
},
default: () => [],
}),
});
console.log(ConfigState);
const callModel1 = async (state: typeof ConfigState.State) => {
console.log(`-----message inside callModel1 @ start-----: `, state.greeting);
return { greeting: "Hola!" }; // Update the greeting with the new value
};
const callModel2 = async (state: typeof ConfigState.State) => {
console.log(`-----message inside callModel2 @ start-----: `, state.greeting);
return { greeting: "Hi!" }; // Update the greeting with the new value
};
// Create the workflow with the main node and end node
const workflow = new StateGraph(ConfigState)
.addNode("agent", callModel1)
.addNode("agent2", callModel2)
.addEdge(START, "agent")
.addEdge("agent", "agent2")
.addEdge("agent2", END); // Connect the agent node to the END node
const initialState = { greeting: "Hello!" }; // Define your initial state here
const result = await workflow.compile().invoke({ state: initialState }); // Pass the initial state when invoking the workflow
console.log(result);
results:
AnnotationRoot {
lc_graph_name: 'AnnotationRoot',
spec: {
greeting: LastValue {
ValueType: undefined,
UpdateType: undefined,
lc_graph_name: 'LastValue',
value: undefined
},
messages: BinaryOperatorAggregate {
ValueType: undefined,
UpdateType: undefined,
lc_graph_name: 'BinaryOperatorAggregate',
value: [],
operator: [Function: reducer],
initialValueFactory: [Function: default]
}
}
}
-----message inside callModel1 @ start-----: null
-----message inside callModel2 @ start-----: Hola!
{ greeting: 'Hi!', messages: [] }
We cannot use Annotation<string>()
with a default value since it uses LastValue
in the background which does not accept a default value. We can only pass values in the state. To get the same functionality, we need to extend Annotation by creating pseudo reducer function and pass the default
// Helper function to create an Annotation with default value behavior
function AnnotationWithDefault<T>(defaultValue: T) {
return Annotation<T>({
value: (currentValue: T, update?: T) => update || currentValue, // Prioritize update value
default: () => defaultValue, // Set default value
});
}
we can then do :
// Define the state structure with a default value for greeting
const GraphAnnotation = Annotation.Root({
greeting: AnnotationWithDefault("Hola!"), // Only pass the default value
});