Skip to content

Commit

Permalink
define state value
Browse files Browse the repository at this point in the history
  • Loading branch information
bracesproul committed Aug 22, 2024
1 parent f8311ca commit 7109824
Showing 1 changed file with 172 additions and 3 deletions.
175 changes: 172 additions & 3 deletions examples/how-tos/define-state.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,173 @@
"\n",
"## Setup\n",
"\n",
"This guide requires installing the `@langchain/langgraph` package:\n",
"This guide requires installing the `@langchain/langgraph`, and `@langchain/core` packages:\n",
"\n",
"```bash\n",
"npm install @langchain/langgraph\n",
"npm install @langchain/langgraph @langchain/core\n",
"```\n",
"\n",
"## Getting started\n",
"\n",
"The `Annotation` function is the recommended way to define your graph state for new `StateGraph` graphs. The `Annotation` function has a `Root` function which is called to define the state. Then, each field of the object represents a channel in the graph. Each channel can optionally have `reducer` and `default` functions passed in. For more information on `reducers`, see the [reducers conceptual guide](https://langchain-ai.github.io/langgraphjs/concepts/low_level/#reducers). The below example shows how to define a simple graph state with one field `messages`."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import { BaseMessage } from \"@langchain/core/messages\";\n",
"import { Annotation } from \"@langchain/langgraph\";\n",
"\n",
"const GraphAnnotation = Annotation.Root({\n",
" messages: Annotation<BaseMessage[]>({\n",
" reducer: (currentState, updateValue) => currentState.concat(updateValue),\n",
" default: () => [],\n",
" })\n",
"});"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Each annotation value can contain a `reducer`, and `default` factory functions, however this is optional. Say you want to define a graph state with fields `question` and `answer` which do not need a reducer or default value. You can define the graph state as follows:"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"const QuestionAnswerAnnotation = Annotation.Root({\n",
" question: Annotation<string>,\n",
" answer: Annotation<string>,\n",
"});"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Above, all we're doing is defining the channels, and then passing the un-instantiated `Annotation` function as the value. It is important to note we always pass in the TypeScript type of each channel as the first generics argument to `Annotation`. Doing this ensures our graph state is type safe, and we can get the proper types when defining our nodes. Below shows how you can extract the typings from the `Annotation` function:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"type QuestionAnswerAnnotationType = typeof QuestionAnswerAnnotation.State;"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This is equivalent to the following type:\n",
"\n",
"```typescript\n",
"type QuestionAnswerAnnotationType = {\n",
" question: string;\n",
" answer: string;\n",
"}\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Merging states\n",
"\n",
"If you have two graph state annotations, you can merge the two into a single annotation by using the `spec` value:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"const MergedAnnotation = Annotation.Root({\n",
" ...QuestionAnswerAnnotation.spec,\n",
" ...GraphAnnotation.spec,\n",
"})"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The type of the merged annotation is the intersection of the two annotations:\n",
"\n",
"```typescript\n",
"type MergedAnnotation = {\n",
" messages: BaseMessage[];\n",
" question: string;\n",
" answer: string;\n",
"}\n",
"```\n",
"\n",
"Finally, instantiating your graph using the annotations is as simple as passing the annotation to the `StateGraph` constructor:\n"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"import { StateGraph } from \"@langchain/langgraph\";\n",
"\n",
"const workflow = new StateGraph(MergedAnnotation);"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## State channels\n",
"\n",
"The `Annotation` function is a convince wrapper around the low level implementation of how states are defined in LangGraph. Defining state using the `channels` object (which is what `Annotation` is a wrapper of) is still possible, although not recommended for most cases. The below example shows how to implement a graph using this pattern:"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"import { StateGraph } from \"@langchain/langgraph\";\n",
"\n",
"interface WorkflowChannelsState {\n",
" messages: BaseMessage[];\n",
" question: string;\n",
" answer: string;\n",
"}\n",
"\n",
"const workflowWithChannels = new StateGraph<WorkflowChannelsState>({\n",
" channels: {\n",
" messages: {\n",
" reducer: (currentState, updateValue) => currentState.concat(updateValue),\n",
" default: () => [],\n",
" },\n",
" question: null,\n",
" answer: null,\n",
" }\n",
"});"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Above, we set the value of `question` and `answer` to `null`, as it does not contain a default value. To set a default value, the channel should be implemented how the `messages` key is, with the `default` factory returing the default value. The `reducer` function is optional, and can be added to the channel object if needed."
]
}
],
"metadata": {
Expand All @@ -30,7 +191,15 @@
"name": "tslab"
},
"language_info": {
"name": "typescript"
"codemirror_mode": {
"mode": "typescript",
"name": "javascript",
"typescript": true
},
"file_extension": ".ts",
"mimetype": "text/typescript",
"name": "typescript",
"version": "3.7.2"
}
},
"nbformat": 4,
Expand Down

0 comments on commit 7109824

Please sign in to comment.