mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-09-08 18:57:52 +02:00
[docs] LangGraph / LangGraph Platform docs updates (#4479)
Main changes made: - Add top level horizontal tabs - Reorganize the sidenav - Build out README/index page - Consolidate how-tos under each section - Remove duplicate content --------- Signed-off-by: William Fu-Hinthorn <13333726+hinthornw@users.noreply.github.com> Co-authored-by: Tat Dat Duong <david@duong.cz> Co-authored-by: Sydney Runkle <sydneymarierunkle@gmail.com> Co-authored-by: William Fu-Hinthorn <13333726+hinthornw@users.noreply.github.com> Co-authored-by: Vadym Barda <vadym@langchain.dev> Co-authored-by: Eugene Yurtsev <eyurtsev@gmail.com> Co-authored-by: ccurme <chester.curme@gmail.com> Co-authored-by: Andrew Nguonly <andrewnguonly@users.noreply.github.com> Co-authored-by: David Asamu <david.asamu@langchain.dev> Co-authored-by: infra <mukil@langchain.dev> Co-authored-by: Arjun Natarajan <arjun@langchain.dev>
This commit is contained in:
co-authored by
Tat Dat Duong
Sydney Runkle
William Fu-Hinthorn
Vadym Barda
Eugene Yurtsev
ccurme
Andrew Nguonly
David Asamu
infra
Arjun Natarajan
parent
909a4591a8
commit
3055c4b9cc
@@ -0,0 +1,311 @@
|
||||
# Customize state
|
||||
|
||||
In this tutorial, you will add additional fields to the state to define complex behavior without relying on the message list. The chatbot will use its search tool to find specific information and forward them to a human for review.
|
||||
|
||||
!!! note
|
||||
|
||||
This tutorial builds on [Add human-in-the-loop controls](./4-human-in-the-loop.md).
|
||||
|
||||
## 1. Add keys to the state
|
||||
|
||||
Update the chatbot to research the birthday of an entity by adding `name` and `birthday` keys to the state:
|
||||
|
||||
```python
|
||||
from typing import Annotated
|
||||
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from langgraph.graph.message import add_messages
|
||||
|
||||
|
||||
class State(TypedDict):
|
||||
messages: Annotated[list, add_messages]
|
||||
# highlight-next-line
|
||||
name: str
|
||||
# highlight-next-line
|
||||
birthday: str
|
||||
```
|
||||
|
||||
Adding this information to the state makes it easily accessible by other graph nodes (like a downstream node that stores or processes the information), as well as the graph's persistence layer.
|
||||
|
||||
## 2. Update the state inside the tool
|
||||
|
||||
Now, populate the state keys inside of the `human_assistance` tool. This allows a human to review the information before it is stored in the state. Use [`Command`](../../concepts/low_level.md#using-inside-tools) to issue a state update from inside the tool.
|
||||
|
||||
``` python
|
||||
from langchain_core.messages import ToolMessage
|
||||
from langchain_core.tools import InjectedToolCallId, tool
|
||||
|
||||
from langgraph.types import Command, interrupt
|
||||
|
||||
@tool
|
||||
# Note that because we are generating a ToolMessage for a state update, we
|
||||
# generally require the ID of the corresponding tool call. We can use
|
||||
# LangChain's InjectedToolCallId to signal that this argument should not
|
||||
# be revealed to the model in the tool's schema.
|
||||
def human_assistance(
|
||||
name: str, birthday: str, tool_call_id: Annotated[str, InjectedToolCallId]
|
||||
) -> str:
|
||||
"""Request assistance from a human."""
|
||||
human_response = interrupt(
|
||||
{
|
||||
"question": "Is this correct?",
|
||||
"name": name,
|
||||
"birthday": birthday,
|
||||
},
|
||||
)
|
||||
# If the information is correct, update the state as-is.
|
||||
if human_response.get("correct", "").lower().startswith("y"):
|
||||
verified_name = name
|
||||
verified_birthday = birthday
|
||||
response = "Correct"
|
||||
# Otherwise, receive information from the human reviewer.
|
||||
else:
|
||||
verified_name = human_response.get("name", name)
|
||||
verified_birthday = human_response.get("birthday", birthday)
|
||||
response = f"Made a correction: {human_response}"
|
||||
|
||||
# This time we explicitly update the state with a ToolMessage inside
|
||||
# the tool.
|
||||
state_update = {
|
||||
"name": verified_name,
|
||||
"birthday": verified_birthday,
|
||||
"messages": [ToolMessage(response, tool_call_id=tool_call_id)],
|
||||
}
|
||||
# We return a Command object in the tool to update our state.
|
||||
return Command(update=state_update)
|
||||
```
|
||||
|
||||
The rest of the graph stays the same.
|
||||
|
||||
## 3. Prompt the chatbot
|
||||
|
||||
Prompt the chatbot to look up the "birthday" of the LangGraph library and direct the chatbot to reach out to the `human_assistance` tool once it has the required information. By setting `name` and `birthday` in the arguments for the tool, you force the chatbot to generate proposals for these fields.
|
||||
|
||||
```python
|
||||
user_input = (
|
||||
"Can you look up when LangGraph was released? "
|
||||
"When you have the answer, use the human_assistance tool for review."
|
||||
)
|
||||
config = {"configurable": {"thread_id": "1"}}
|
||||
|
||||
events = graph.stream(
|
||||
{"messages": [{"role": "user", "content": user_input}]},
|
||||
config,
|
||||
stream_mode="values",
|
||||
)
|
||||
for event in events:
|
||||
if "messages" in event:
|
||||
event["messages"][-1].pretty_print()
|
||||
```
|
||||
|
||||
```
|
||||
================================ Human Message =================================
|
||||
|
||||
Can you look up when LangGraph was released? When you have the answer, use the human_assistance tool for review.
|
||||
================================== Ai Message ==================================
|
||||
|
||||
[{'text': "Certainly! I'll start by searching for information about LangGraph's release date using the Tavily search function. Then, I'll use the human_assistance tool for review.", 'type': 'text'}, {'id': 'toolu_01JoXQPgTVJXiuma8xMVwqAi', 'input': {'query': 'LangGraph release date'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]
|
||||
Tool Calls:
|
||||
tavily_search_results_json (toolu_01JoXQPgTVJXiuma8xMVwqAi)
|
||||
Call ID: toolu_01JoXQPgTVJXiuma8xMVwqAi
|
||||
Args:
|
||||
query: LangGraph release date
|
||||
================================= Tool Message =================================
|
||||
Name: tavily_search_results_json
|
||||
|
||||
[{"url": "https://blog.langchain.dev/langgraph-cloud/", "content": "We also have a new stable release of LangGraph. By LangChain 6 min read Jun 27, 2024 (Oct '24) Edit: Since the launch of LangGraph Cloud, we now have multiple deployment options alongside LangGraph Studio - which now fall under LangGraph Platform. LangGraph Cloud is synonymous with our Cloud SaaS deployment option."}, {"url": "https://changelog.langchain.com/announcements/langgraph-cloud-deploy-at-scale-monitor-carefully-iterate-boldly", "content": "LangChain - Changelog | ☁ 🚀 LangGraph Cloud: Deploy at scale, monitor LangChain LangSmith LangGraph LangChain LangSmith LangGraph LangChain LangSmith LangGraph LangChain Changelog Sign up for our newsletter to stay up to date DATE: The LangChain Team LangGraph LangGraph Cloud ☁ 🚀 LangGraph Cloud: Deploy at scale, monitor carefully, iterate boldly DATE: June 27, 2024 AUTHOR: The LangChain Team LangGraph Cloud is now in closed beta, offering scalable, fault-tolerant deployment for LangGraph agents. LangGraph Cloud also includes a new playground-like studio for debugging agent failure modes and quick iteration: Join the waitlist today for LangGraph Cloud. And to learn more, read our blog post announcement or check out our docs. Subscribe By clicking subscribe, you accept our privacy policy and terms and conditions."}]
|
||||
================================== Ai Message ==================================
|
||||
|
||||
[{'text': "Based on the search results, it appears that LangGraph was already in existence before June 27, 2024, when LangGraph Cloud was announced. However, the search results don't provide a specific release date for the original LangGraph. \n\nGiven this information, I'll use the human_assistance tool to review and potentially provide more accurate information about LangGraph's initial release date.", 'type': 'text'}, {'id': 'toolu_01JDQAV7nPqMkHHhNs3j3XoN', 'input': {'name': 'Assistant', 'birthday': '2023-01-01'}, 'name': 'human_assistance', 'type': 'tool_use'}]
|
||||
Tool Calls:
|
||||
human_assistance (toolu_01JDQAV7nPqMkHHhNs3j3XoN)
|
||||
Call ID: toolu_01JDQAV7nPqMkHHhNs3j3XoN
|
||||
Args:
|
||||
name: Assistant
|
||||
birthday: 2023-01-01
|
||||
```
|
||||
|
||||
We've hit the `interrupt` in the `human_assistance` tool again.
|
||||
|
||||
## 4. Add human assistance
|
||||
|
||||
The chatbot failed to identify the correct date, so supply it with information:
|
||||
|
||||
```python
|
||||
human_command = Command(
|
||||
resume={
|
||||
"name": "LangGraph",
|
||||
"birthday": "Jan 17, 2024",
|
||||
},
|
||||
)
|
||||
|
||||
events = graph.stream(human_command, config, stream_mode="values")
|
||||
for event in events:
|
||||
if "messages" in event:
|
||||
event["messages"][-1].pretty_print()
|
||||
```
|
||||
|
||||
```
|
||||
================================== Ai Message ==================================
|
||||
|
||||
[{'text': "Based on the search results, it appears that LangGraph was already in existence before June 27, 2024, when LangGraph Cloud was announced. However, the search results don't provide a specific release date for the original LangGraph. \n\nGiven this information, I'll use the human_assistance tool to review and potentially provide more accurate information about LangGraph's initial release date.", 'type': 'text'}, {'id': 'toolu_01JDQAV7nPqMkHHhNs3j3XoN', 'input': {'name': 'Assistant', 'birthday': '2023-01-01'}, 'name': 'human_assistance', 'type': 'tool_use'}]
|
||||
Tool Calls:
|
||||
human_assistance (toolu_01JDQAV7nPqMkHHhNs3j3XoN)
|
||||
Call ID: toolu_01JDQAV7nPqMkHHhNs3j3XoN
|
||||
Args:
|
||||
name: Assistant
|
||||
birthday: 2023-01-01
|
||||
================================= Tool Message =================================
|
||||
Name: human_assistance
|
||||
|
||||
Made a correction: {'name': 'LangGraph', 'birthday': 'Jan 17, 2024'}
|
||||
================================== Ai Message ==================================
|
||||
|
||||
Thank you for the human assistance. I can now provide you with the correct information about LangGraph's release date.
|
||||
|
||||
LangGraph was initially released on January 17, 2024. This information comes from the human assistance correction, which is more accurate than the search results I initially found.
|
||||
|
||||
To summarize:
|
||||
1. LangGraph's original release date: January 17, 2024
|
||||
2. LangGraph Cloud announcement: June 27, 2024
|
||||
|
||||
It's worth noting that LangGraph had been in development and use for some time before the LangGraph Cloud announcement, but the official initial release of LangGraph itself was on January 17, 2024.
|
||||
```
|
||||
|
||||
Note that these fields are now reflected in the state:
|
||||
|
||||
```python
|
||||
snapshot = graph.get_state(config)
|
||||
|
||||
{k: v for k, v in snapshot.values.items() if k in ("name", "birthday")}
|
||||
```
|
||||
|
||||
```
|
||||
{'name': 'LangGraph', 'birthday': 'Jan 17, 2024'}
|
||||
```
|
||||
|
||||
This makes them easily accessible to downstream nodes (e.g., a node that further processes or stores the information).
|
||||
|
||||
## 5. Manually update the state
|
||||
|
||||
LangGraph gives a high degree of control over the application state. For instance, at any point (including when interrupted), you can manually override a key using `graph.update_state`:
|
||||
|
||||
``` python
|
||||
graph.update_state(config, {"name": "LangGraph (library)"})
|
||||
```
|
||||
|
||||
```
|
||||
{'configurable': {'thread_id': '1',
|
||||
'checkpoint_ns': '',
|
||||
'checkpoint_id': '1efd4ec5-cf69-6352-8006-9278f1730162'}}
|
||||
```
|
||||
|
||||
## 6. View the new value
|
||||
|
||||
If you call `graph.get_state`, you can see the new value is reflected:
|
||||
|
||||
``` python
|
||||
snapshot = graph.get_state(config)
|
||||
|
||||
{k: v for k, v in snapshot.values.items() if k in ("name", "birthday")}
|
||||
```
|
||||
|
||||
```
|
||||
{'name': 'LangGraph (library)', 'birthday': 'Jan 17, 2024'}
|
||||
```
|
||||
|
||||
Manual state updates will [generate a trace](https://smith.langchain.com/public/7ebb7827-378d-49fe-9f6c-5df0e90086c8/r) in LangSmith. If desired, they can also be used to [control human-in-the-loop workflows](../../how-tos/human_in_the_loop/edit-graph-state.md). Use of the `interrupt` function is generally recommended instead, as it allows data to be transmitted in a human-in-the-loop interaction independently of state updates.
|
||||
|
||||
**Congratulations!** You've added custom keys to the state to facilitate a more complex workflow, and learned how to generate state updates from inside tools.
|
||||
|
||||
Check out the code snippet below to review the graph from this tutorial:
|
||||
|
||||
{!snippets/chat_model_tabs.md!}
|
||||
|
||||
<!---
|
||||
```python
|
||||
from langchain.chat_models import init_chat_model
|
||||
|
||||
llm = init_chat_model("anthropic:claude-3-5-sonnet-latest")
|
||||
```
|
||||
-->
|
||||
|
||||
```python
|
||||
from typing import Annotated
|
||||
|
||||
from langchain_tavily import TavilySearch
|
||||
from langchain_core.messages import ToolMessage
|
||||
from langchain_core.tools import InjectedToolCallId, tool
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.graph import StateGraph, START, END
|
||||
from langgraph.graph.message import add_messages
|
||||
from langgraph.prebuilt import ToolNode, tools_condition
|
||||
from langgraph.types import Command, interrupt
|
||||
|
||||
class State(TypedDict):
|
||||
messages: Annotated[list, add_messages]
|
||||
name: str
|
||||
birthday: str
|
||||
|
||||
@tool
|
||||
def human_assistance(
|
||||
name: str, birthday: str, tool_call_id: Annotated[str, InjectedToolCallId]
|
||||
) -> str:
|
||||
"""Request assistance from a human."""
|
||||
human_response = interrupt(
|
||||
{
|
||||
"question": "Is this correct?",
|
||||
"name": name,
|
||||
"birthday": birthday,
|
||||
},
|
||||
)
|
||||
if human_response.get("correct", "").lower().startswith("y"):
|
||||
verified_name = name
|
||||
verified_birthday = birthday
|
||||
response = "Correct"
|
||||
else:
|
||||
verified_name = human_response.get("name", name)
|
||||
verified_birthday = human_response.get("birthday", birthday)
|
||||
response = f"Made a correction: {human_response}"
|
||||
|
||||
state_update = {
|
||||
"name": verified_name,
|
||||
"birthday": verified_birthday,
|
||||
"messages": [ToolMessage(response, tool_call_id=tool_call_id)],
|
||||
}
|
||||
return Command(update=state_update)
|
||||
|
||||
|
||||
tool = TavilySearch(max_results=2)
|
||||
tools = [tool, human_assistance]
|
||||
llm_with_tools = llm.bind_tools(tools)
|
||||
|
||||
def chatbot(state: State):
|
||||
message = llm_with_tools.invoke(state["messages"])
|
||||
assert(len(message.tool_calls) <= 1)
|
||||
return {"messages": [message]}
|
||||
|
||||
graph_builder = StateGraph(State)
|
||||
graph_builder.add_node("chatbot", chatbot)
|
||||
|
||||
tool_node = ToolNode(tools=tools)
|
||||
graph_builder.add_node("tools", tool_node)
|
||||
|
||||
graph_builder.add_conditional_edges(
|
||||
"chatbot",
|
||||
tools_condition,
|
||||
)
|
||||
graph_builder.add_edge("tools", "chatbot")
|
||||
graph_builder.add_edge(START, "chatbot")
|
||||
|
||||
memory = MemorySaver()
|
||||
graph = graph_builder.compile(checkpointer=memory)
|
||||
```
|
||||
|
||||
## Next steps
|
||||
|
||||
There's one more concept to review before finishing the LangGraph basics tutorials: connecting `checkpointing` and `state updates` to [time travel](./6-time-travel.md).
|
||||
|
||||
Reference in New Issue
Block a user