mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-19 22:25:44 +02:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2bcd48f497 | ||
|
|
ed8c91c39a | ||
|
|
6ca126f94d |
@@ -180,4 +180,3 @@ Chinook.db
|
||||
|
||||
.vercel
|
||||
.turbo
|
||||
.editorconfig
|
||||
|
||||
+1
-1
@@ -58,4 +58,4 @@ To delete cassettes for a notebook, you can run:
|
||||
|
||||
```bash
|
||||
rm cassettes/<notebook_name>*
|
||||
```
|
||||
```
|
||||
@@ -22,12 +22,6 @@ MANUAL_API_REFERENCES_LANGGRAPH = [
|
||||
"create_react_agent",
|
||||
"prebuilt",
|
||||
),
|
||||
(
|
||||
[],
|
||||
"langgraph.prebuilt.chat_agent_executor",
|
||||
"AgentState",
|
||||
"prebuilt",
|
||||
),
|
||||
(["langgraph.prebuilt"], "langgraph.prebuilt.tool_node", "ToolNode", "prebuilt"),
|
||||
(
|
||||
["langgraph.prebuilt"],
|
||||
@@ -69,18 +63,6 @@ MANUAL_API_REFERENCES_LANGGRAPH = [
|
||||
([], "langgraph.checkpoint.sqlite", "SqliteSaver", "checkpoints"),
|
||||
([], "langgraph.checkpoint.postgres.aio", "AsyncPostgresSaver", "checkpoints"),
|
||||
([], "langgraph.checkpoint.postgres", "PostgresSaver", "checkpoints"),
|
||||
# other prebuilts
|
||||
(["langgraph_supervisor"], "langgraph_supervisor.supervisor", "create_supervisor", "supervisor"),
|
||||
(["langgraph_supervisor"], "langgraph_supervisor.handoff", "create_handoff_tool", "supervisor"),
|
||||
([], "langgraph_supervisor.handoff", "create_forward_message_tool", "supervisor"),
|
||||
(["langgraph_swarm"], "langgraph_swarm.swarm", "create_swarm", "swarm"),
|
||||
(["langgraph_swarm"], "langgraph_swarm.swarm", "add_active_agent_router", "swarm"),
|
||||
(["langgraph_swarm"], "langgraph_swarm.swarm", "SwarmState", "swarm"),
|
||||
(["langgraph_swarm"], "langgraph_swarm.handoff", "create_handoff_tool", "swarm"),
|
||||
([], "langchain_mcp_adapters.client", "MultiServerMCPClient", "mcp"),
|
||||
([], "langchain_mcp_adapters.tools", "load_mcp_tools", "mcp"),
|
||||
([], "langchain_mcp_adapters.prompts", "load_mcp_prompt", "mcp"),
|
||||
([], "langchain_mcp_adapters.resources", "load_mcp_resources", "mcp"),
|
||||
]
|
||||
|
||||
WELL_KNOWN_LANGGRAPH_OBJECTS = {
|
||||
@@ -162,9 +144,7 @@ def get_imports(code: str, path: str) -> List[ImportInformation]:
|
||||
for found_import in found_imports:
|
||||
module = found_import["source"]
|
||||
|
||||
if module.startswith("langchain_mcp_adapters"):
|
||||
package_ecosystem = "langgraph"
|
||||
elif module.startswith("langchain"):
|
||||
if module.startswith("langchain"):
|
||||
# Handles things like `langchain` or `langchain_anthropic`
|
||||
package_ecosystem = "langchain"
|
||||
elif module.startswith("langgraph"):
|
||||
@@ -277,7 +257,7 @@ def update_markdown_with_imports(markdown: str, path: str) -> str:
|
||||
f'<a href="{imp["docs"]}">{imp["imported"]}</a>' for imp in imports
|
||||
)
|
||||
# Return the code block with prepended API reference links
|
||||
return f"{indent}<sup><i>API Reference: {api_links}</i></sup>\n\n{original_code_block}"
|
||||
return f"{indent}API Reference: {api_links}\n\n{original_code_block}"
|
||||
|
||||
# Apply the replace_code_block function to all matches in the markdown
|
||||
updated_markdown = code_block_pattern.sub(replace_code_block, markdown)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import ast
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Literal
|
||||
|
||||
import nbformat
|
||||
@@ -25,7 +26,7 @@ def _uses_input(source: str) -> bool:
|
||||
|
||||
|
||||
def _rewrite_cell_magic(code: str) -> str:
|
||||
"""Process a code block that uses cell magic.
|
||||
"""Process a code block that uses cell magic.:w
|
||||
|
||||
- Lines starting with "%%capture" are ignored.
|
||||
- Lines starting with "%pip" are rewritten by removing the leading "%" character.
|
||||
@@ -51,14 +52,10 @@ def _rewrite_cell_magic(code: str) -> str:
|
||||
if stripped.startswith("%%capture"):
|
||||
continue
|
||||
# Rewrite %pip lines by dropping the '%'
|
||||
elif stripped.startswith("%") or stripped.startswith("!"):
|
||||
# Drop the leading '%' character and then drop all leading whitespace
|
||||
stripped = stripped.lstrip("%! \t")
|
||||
# Check if the line starts with "pip"
|
||||
if stripped.startswith("pip"):
|
||||
rewritten_lines.append(stripped)
|
||||
else:
|
||||
raise NotImplementedError(f"Unhandled line: {line}")
|
||||
elif stripped.startswith("%pip"):
|
||||
# Drop the leading '%' character
|
||||
rewritten_lines.append(stripped[1:])
|
||||
# Anything else is not supported
|
||||
else:
|
||||
raise NotImplementedError(f"Unhandled line: {line}")
|
||||
|
||||
@@ -220,24 +217,6 @@ def _convert_links_in_markdown(markdown: str) -> str:
|
||||
)
|
||||
|
||||
|
||||
class HideCellTagPreprocessor(Preprocessor):
|
||||
"""
|
||||
Removes cells that have '# hide-cell' at the beginning of the cell content.
|
||||
This allows authors to include cells in the notebook that should not
|
||||
appear in the generated markdown output.
|
||||
"""
|
||||
|
||||
def preprocess(self, nb, resources):
|
||||
# Filter out cells with the '# hide-cell' comment at the beginning
|
||||
nb.cells = [
|
||||
cell
|
||||
for cell in nb.cells
|
||||
if not (cell.source.strip().startswith("# hide-cell"))
|
||||
]
|
||||
|
||||
return nb, resources
|
||||
|
||||
|
||||
class EscapePreprocessor(Preprocessor):
|
||||
def __init__(self, markdown_exec_migration: bool = False, **kwargs) -> None:
|
||||
super().__init__(**kwargs)
|
||||
@@ -268,10 +247,13 @@ class EscapePreprocessor(Preprocessor):
|
||||
)
|
||||
cell.metadata["exec"] = is_exec
|
||||
|
||||
# For markdown exec migration we'll re-write cell magic as bash commands
|
||||
if source.startswith("%%"):
|
||||
cell.source = _rewrite_cell_magic(source)
|
||||
cell.metadata["language"] = "shell"
|
||||
if self.markdown_exec_migration:
|
||||
# For markdown exec migration we'll re-write cell magic as bash commands
|
||||
if source.startswith("%%"):
|
||||
cell.source = _rewrite_cell_magic(source)
|
||||
cell.metadata["language"] = "shell"
|
||||
|
||||
cell.metadata["has_output"] = _has_output(source)
|
||||
|
||||
# Remove noqa comments
|
||||
cell.source = re.sub(r"#\s*noqa.*$", "", cell.source, flags=re.MULTILINE)
|
||||
@@ -359,7 +341,6 @@ class ExtractAttachmentsPreprocessor(Preprocessor):
|
||||
|
||||
exporter = MarkdownExporter(
|
||||
preprocessors=[
|
||||
HideCellTagPreprocessor,
|
||||
EscapePreprocessor,
|
||||
ExtractAttachmentsPreprocessor,
|
||||
],
|
||||
@@ -371,7 +352,7 @@ exporter = MarkdownExporter(
|
||||
|
||||
|
||||
def convert_notebook(
|
||||
notebook_path: str,
|
||||
notebook_path: Path,
|
||||
mode: Literal["markdown", "exec"] = "markdown",
|
||||
) -> str:
|
||||
with open(notebook_path) as f:
|
||||
|
||||
@@ -1,18 +1,5 @@
|
||||
{% extends 'markdown/index.md.j2' %}
|
||||
|
||||
{% block input %}{# cell.metadata.language is an addition of our docs pipeline. #}
|
||||
```{%- if 'language' in cell.metadata -%}
|
||||
{{ cell.metadata.language }}
|
||||
{%- elif 'magics_language' in cell.metadata -%}
|
||||
{{ cell.metadata.magics_language }}
|
||||
{%- elif 'name' in nb.metadata.get('language_info', {}) -%}
|
||||
{{ nb.metadata.language_info.name }}
|
||||
{%- endif %}
|
||||
{{ cell.source }}
|
||||
```
|
||||
{% endblock input %}
|
||||
|
||||
|
||||
{%- block traceback_line -%}
|
||||
```output
|
||||
{{ line.rstrip() | strip_ansi }}
|
||||
@@ -21,13 +8,13 @@
|
||||
|
||||
{%- block stream -%}
|
||||
```output
|
||||
{{ output.text.rstrip() | strip_ansi }}
|
||||
{{ output.text.rstrip() }}
|
||||
```
|
||||
{%- endblock stream -%}
|
||||
|
||||
{%- block data_text scoped -%}
|
||||
```output
|
||||
{{ output.data['text/plain'].rstrip() | strip_ansi }}
|
||||
{{ output.data['text/plain'].rstrip() }}
|
||||
```
|
||||
{%- endblock data_text -%}
|
||||
|
||||
|
||||
@@ -31,15 +31,8 @@ REDIRECT_MAP = {
|
||||
"cloud/concepts/api.md": "concepts/langgraph_server.md",
|
||||
"cloud/concepts/cloud.md": "concepts/langgraph_cloud.md",
|
||||
"cloud/faq/studio.md": "concepts/langgraph_studio.md#studio-faqs",
|
||||
# prebuit redirects
|
||||
"how-tos/create-react-agent.ipynb": "agents/agents.md#basic-configuration",
|
||||
"how-tos/create-react-agent-memory.ipynb": "agents/memory.md",
|
||||
"how-tos/create-react-agent-system-prompt.ipynb": "agents/context.md#prompts",
|
||||
"how-tos/create-react-agent-hitl.ipynb": "agents/human-in-the-loop.md",
|
||||
"how-tos/create-react-agent-structured-output.ipynb": "agents/agents.md#structured-output",
|
||||
# misc
|
||||
"prebuilt.md": "agents/prebuilt.md",
|
||||
"reference/prebuilt.md": "reference/agents.md"
|
||||
"prebuilt.md": "agents/prebuilt.md"
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
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
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
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
|
||||
@@ -0,0 +1 @@
|
||||
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
|
||||
+1
@@ -0,0 +1 @@
|
||||
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
|
||||
+1
@@ -0,0 +1 @@
|
||||
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
|
||||
+1
@@ -0,0 +1 @@
|
||||
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
|
||||
+1
@@ -0,0 +1 @@
|
||||
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
|
||||
+1
@@ -0,0 +1 @@
|
||||
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
|
||||
+1
@@ -0,0 +1 @@
|
||||
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
|
||||
+1
@@ -0,0 +1 @@
|
||||
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
|
||||
@@ -0,0 +1 @@
|
||||
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
|
||||
@@ -0,0 +1 @@
|
||||
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
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
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
|
||||
@@ -0,0 +1 @@
|
||||
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
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
eNptksFuEzEQhsWbrHrgQOPdzSat2qAIRVXUSjSqIIEL4mC8w67BaxvbGxJuhJ6R/AakibJV1AJCiAtU4siBF8jb4N0UCZUc7X/8zzf/eFIMQWkq+K0Lyg0oTIw7aDspFLzKQZvTRQYmFfHssDuY5YqujlJjpG4FAZbU1xk1qc8wT0iKKfeJyIIYG6zB6HuMOrVdv81xBu3+g+PtTgLcbD8ELV0LmD8T8fhnkQKOHcLp8pEGhaoS+7V0rLyRHAehX/fr0d6nDiEgDepyImLKE3uZvKGy5sXwnGEDi7VsP2MpGSW4nCN4oQVfHgjOoZrLLl8CSIQZHcK5uuZ4t9AGm1xP5s4Xfv8qMtAaJ3B2cv8v3PuPzsM4MnQMPDGpnUYLR+uis0U+pEQo/mWEJE4or/oiIwxmdhp+L6G0RuVzJRjqMCZeoxNFXaX98GOjerTuaad3vt3Qe3hUBmRnu2E4dzGDvRqkec2Ldr0+SC8Ko6ZX32lFjVa05x32Bpv9e9U+S/+rG3p3JIWGfwCKDjOoPyR25aeN9lar2Wxs3fUy3I529qMwDGtpA0X7G4RLgkkKiKyN7TkXqLqZPabYLt06vUSIhMHK24h4oCB2aVPMtJ0blcMFuc7fjCX8v+LqK711e1TuX5w9efoH8Hwd7A==
|
||||
@@ -0,0 +1 @@
|
||||
eNrtVE1rFEEQxX8yDOJBd3ZnZ3cTMxIkSFDQIJroRaTp9FRmWnu72+6eTVbJwZirwvgLTDZkJfiFiBcVvOnBPxB/jTWTXT/WqOBN8Djzuqpevap6G8MeGMuVPPKESweGMocf9uHG0MCtHKzb3O2Cy1QyODu/NMgN3z+aOadt3GhQzeu2y11WF1SmLKNc1pnqNrhcUTvLKum/H2ZAE0y/uXfFggnmUpCueFW+ruIC3W+E9Wa9GZ18PscYaBfMS6YSLtPiaXqb65qXwIqgDnYP4OIF1VpwRkuOjRtWyb0zSkqoOBd7NwF0QAXvwWMDVmMbcG/XOupyu7GDeeHTx2EXrKUpbF88PyZ3/xnmcMgsuAAydVkxaHc6u8gXhSmGeY8zZeTbkoC1QfnUKBHMCaFWg3MHGYqt428OxS8annJZPHo9gS7QtVKMYjAVhjsJ9le8W8rymhdNeYugvSiM2l6zE0etODrpnV1YOrz6QjWXsvq7CXx+TSsL39EbzgkXLPZYsV/PWrN+3G63/FNel85GnZkoDMNa1gqimUOAp4yyDAJ2kLh4LFVQ/Rlc5bTYw9F5qVKpgH3vUIpnDCSoLKfCFjvO5PCEjbR2fQ0/j7Nam7s4M4M78PnIhzv+aDv92A/r0/XWtF/zMQJwtATWNDdVLHG8C34scyFq/jJ1LCMYj8tLsNwKT/34jm8ZFUByTW5ZfhsIVkhTMH7cLNv/hkqXGZTNEsFxRRGeGoOJWpVEQle7/rfoNqJluvHrKtfXH2S578D6cRTOTDc7Ubhe87nEhZQMCO51aktieBR4dg4I5QQvzvQJSLosIPHjUrGar0xKGJKqOk24HYErKCqiNlOrxDlBcj4OcHjF2CEHQ5J8pFBC+1U1oWRaXggmaFdkM2Xc6EezjQQtUIP6TXBYVeam1WVay5QGUnLisser9sZMWsQ6ZfC6foxeX/+1mZz7k5ngdVALzp6uBJ1tHpO0C7OLly6cqNzkxOXRof9DhvNg0nC2okm7ebkWaIrWUdUNnHJUFFvh703mTxb134T+xoS2r13/Av44wVA=
|
||||
@@ -1,12 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
tags:
|
||||
- agent
|
||||
hide:
|
||||
- tags
|
||||
---
|
||||
|
||||
# Agents
|
||||
|
||||
## What is an agent?
|
||||
@@ -22,7 +13,7 @@ The LLM operates in a loop. In each iteration, it selects a tool to invoke, prov
|
||||
|
||||
## Basic configuration
|
||||
|
||||
Use [`create_react_agent`][langgraph.prebuilt.chat_agent_executor.create_react_agent] to instantiate an agent:
|
||||
Use [`create_react_agent`](https://python.langchain.com/docs/api_reference/langgraph.prebuilt.chat_agent_executor/#create-react-agent) to instantiate an agent:
|
||||
|
||||
```python
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
@@ -112,7 +103,7 @@ from langgraph.prebuilt import create_react_agent
|
||||
|
||||
# highlight-next-line
|
||||
def prompt(state: AgentState, config: RunnableConfig) -> list[AnyMessage]: # (1)!
|
||||
user_name = config["configurable"].get("user_name")
|
||||
user_name = config.get("configurable", {}).get("user_name")
|
||||
system_msg = f"You are a helpful assistant. Address the user as {user_name}."
|
||||
return [{"role": "system", "content": system_msg}] + state["messages"]
|
||||
|
||||
|
||||
+66
-15
@@ -1,12 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
tags:
|
||||
- agent
|
||||
hide:
|
||||
- tags
|
||||
---
|
||||
|
||||
# Context
|
||||
|
||||
Agents often require more than a list of messages to function effectively. They need **context**.
|
||||
@@ -83,7 +74,7 @@ agent.invoke({
|
||||
|
||||
For context that spans *across* conversations or sessions, LangGraph allows access to **long-term memory** via a `store`. This can be used to read or update persistent facts (e.g., user profiles, preferences, prior interactions). For more, see the [Memory guide](./memory.md).
|
||||
|
||||
## Customizing Prompts with Context { #prompts }
|
||||
## Customizing Prompts with Context
|
||||
|
||||
Prompts define how the agent behaves. To incorporate runtime context, you can dynamically generate prompts based on the agent's state or config.
|
||||
|
||||
@@ -107,7 +98,7 @@ Common use cases:
|
||||
config: RunnableConfig,
|
||||
) -> list[AnyMessage]:
|
||||
# highlight-next-line
|
||||
user_name = config["configurable"].get("user_name")
|
||||
user_name = config.get("configurable", {}).get("user_name")
|
||||
system_msg = f"You are a helpful assistant. User's name is {user_name}"
|
||||
return [{"role": "system", "content": system_msg}] + state["messages"]
|
||||
|
||||
@@ -162,7 +153,7 @@ Common use cases:
|
||||
})
|
||||
```
|
||||
|
||||
## Accessing Context in Tools { #tools }
|
||||
## Tools
|
||||
|
||||
Tools can access context through special parameter **annotations**.
|
||||
|
||||
@@ -183,7 +174,7 @@ Tools can access context through special parameter **annotations**.
|
||||
) -> str:
|
||||
"""Look up user info."""
|
||||
# highlight-next-line
|
||||
user_id = config["configurable"].get("user_id")
|
||||
user_id = config.get("configurable", {}).get("user_id")
|
||||
return "User is John Smith" if user_id == "user_123" else "Unknown user"
|
||||
|
||||
agent = create_react_agent(
|
||||
@@ -231,6 +222,66 @@ Tools can access context through special parameter **annotations**.
|
||||
})
|
||||
```
|
||||
|
||||
### Update Context from Tools
|
||||
|
||||
Tools can update agent's context (state and long-term memory) during execution. This is useful for persisting intermediate results or making information accessible to subsequent tools or prompts. See [Memory](./memory.md#read-short-term) guide for more information.
|
||||
## Update context from tools
|
||||
|
||||
Tools can modify the agent's state during execution. This is useful for persisting intermediate results or making information accessible to subsequent tools or prompts.
|
||||
|
||||
```python
|
||||
from typing import Annotated
|
||||
from langchain_core.tools import InjectedToolCallId
|
||||
from langchain_core.messages import ToolMessage
|
||||
from langgraph.prebuilt import InjectedState
|
||||
from langgraph.types import Command
|
||||
|
||||
class CustomState(AgentState):
|
||||
# highlight-next-line
|
||||
user_name: str
|
||||
|
||||
def get_user_info(
|
||||
# highlight-next-line
|
||||
tool_call_id: Annotated[str, InjectedToolCallId],
|
||||
# highlight-next-line
|
||||
config: RunnableConfig
|
||||
) -> Command:
|
||||
"""Look up user info."""
|
||||
# highlight-next-line
|
||||
user_id = config.get("configurable", {}).get("user_id")
|
||||
name = "John Smith" if user_id == "user_123" else "Unknown user"
|
||||
return Command(update={
|
||||
# highlight-next-line
|
||||
"user_name": name,
|
||||
# update the message history
|
||||
# highlight-next-line
|
||||
"messages": [
|
||||
ToolMessage(
|
||||
"Successfully looked up user information",
|
||||
# highlight-next-line
|
||||
tool_call_id=tool_call_id
|
||||
)
|
||||
]
|
||||
})
|
||||
|
||||
def greet(
|
||||
# highlight-next-line
|
||||
state: Annotated[CustomState, InjectedState]
|
||||
) -> str:
|
||||
"""Use this to greet the user once you found their info."""
|
||||
user_name = state["user_name"]
|
||||
return f"Hello {user_name}!"
|
||||
|
||||
agent = create_react_agent(
|
||||
model="anthropic:claude-3-7-sonnet-latest",
|
||||
tools=[get_user_info, greet],
|
||||
# highlight-next-line
|
||||
state_schema=CustomState
|
||||
)
|
||||
|
||||
agent.invoke(
|
||||
{"messages": [{"role": "user", "content": "greet the user"}]},
|
||||
# highlight-next-line
|
||||
config={"configurable": {"user_id": "user_123"}}
|
||||
)
|
||||
```
|
||||
|
||||
For more details, see [how to update state from tools](../how-tos/update-state-from-tools.ipynb).
|
||||
@@ -1,12 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
tags:
|
||||
- agent
|
||||
hide:
|
||||
- tags
|
||||
---
|
||||
|
||||
# Deployment
|
||||
|
||||
To deploy your LangGraph agent, create and configure a LangGraph app. This setup supports both local development and production deployments.
|
||||
|
||||
@@ -1,12 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
tags:
|
||||
- agent
|
||||
hide:
|
||||
- tags
|
||||
---
|
||||
|
||||
# Evals
|
||||
|
||||
To evaluate your agent's performance you can use `LangSmith` [evaluations](https://docs.smith.langchain.com/evaluation). You would need to first define an evaluator function to judge the results from an agent, such as final outputs or trajectory. Depending on your evaluation technique, this may or may not involve a reference output:
|
||||
|
||||
@@ -1,17 +1,6 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
tags:
|
||||
- human-in-the-loop
|
||||
- hil
|
||||
- agent
|
||||
hide:
|
||||
- tags
|
||||
---
|
||||
|
||||
# Human-in-the-loop
|
||||
|
||||
To review, edit and approve tool calls in an agent you can use LangGraph's built-in [Human-In-the-Loop (HIL)](../concepts/human_in_the_loop.md) features, specifically the [`interrupt()`][langgraph.types.interrupt] primitive.
|
||||
To review, edit and approve tool calls in an agent you can use LangGraph's built-in [human-in-the-loop](../concepts/human_in_the_loop.md) features, specifically the [`interrupt()`][langgraph.types.interrupt] primitive.
|
||||
|
||||
LangGraph allows you to pause execution **indefinitely** — for minutes, hours, or even days—until human input is received.
|
||||
|
||||
@@ -126,7 +115,7 @@ def add_human_in_the_loop(
|
||||
*,
|
||||
interrupt_config: HumanInterruptConfig = None,
|
||||
) -> BaseTool:
|
||||
"""Wrap a tool to support human-in-the-loop review."""
|
||||
"""Wrap a tool to support human-in-the-loop review."""
|
||||
if not isinstance(tool, BaseTool):
|
||||
tool = create_tool(tool)
|
||||
|
||||
@@ -235,4 +224,4 @@ for chunk in agent.stream(
|
||||
|
||||
## Additional resources
|
||||
|
||||
* [Human-in-the-loop in LangGraph](../concepts/human_in_the_loop.md)
|
||||
* [Human-in-the-loop in LangGraph](../concepts/human_in_the_loop.md)
|
||||
@@ -1,12 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
tags:
|
||||
- agent
|
||||
hide:
|
||||
- tags
|
||||
---
|
||||
|
||||
# MCP Integration
|
||||
|
||||
[Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) is an open protocol that standardizes how applications provide tools and context to language models. LangGraph agents can use tools defined on MCP servers through the `langchain-mcp-adapters` library.
|
||||
|
||||
+8
-169
@@ -1,12 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
tags:
|
||||
- agent
|
||||
hide:
|
||||
- tags
|
||||
---
|
||||
|
||||
# Memory
|
||||
|
||||
LangGraph supports two types of memory essential for building conversational agents:
|
||||
@@ -92,26 +83,15 @@ When the agent is invoked the second time with the same `thread_id`, the origina
|
||||
|
||||
If you're using [LangGraph Platform](./deployment.md), during deployment your checkpointer will be automatically configured to use a production-ready database.
|
||||
|
||||
### Manage message history
|
||||
|
||||
Long conversations can exceed the LLM's context window. Common solutions are:
|
||||
|
||||
* [Summarization](#summarize-message-history): Maintain a running summary of the conversation
|
||||
* [Trimming](#trim-message-history): Remove first or last N messages in the history
|
||||
|
||||
This allows the agent to keep track of the conversation without exceeding the LLM's context window.
|
||||
|
||||
To manage message history, specify `pre_model_hook` — a function ([node](../concepts/low_level.md#nodes)) that will always run before calling the language model.
|
||||
|
||||
#### Summarize message history
|
||||
### Message history summarization
|
||||
|
||||
<figure markdown="1">
|
||||
{: style="max-height:400px"}
|
||||
<figcaption>Long conversations can exceed the LLM's context window. A common solution is to maintain a running summary of the conversation. This allows the agent to keep track of the conversation without exceeding the LLM's context window.
|
||||
<figcaption>Message history can grow quickly and exceed the LLM's context window. A common solution is to maintain a running summary of the conversation. This allows the agent to keep track of the conversation without exceeding the LLM's context window.
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
To summarize message history, you can use [`pre_model_hook`][langgraph.prebuilt.chat_agent_executor.create_react_agent] with a prebuilt [`SummarizationNode`](https://langchain-ai.github.io/langmem/reference/short_term/#langmem.short_term.SummarizationNode):
|
||||
Long conversations can exceed the LLM's context window. To handle this, you can summarize older messages by specifying a [`pre_model_hook`][langgraph.prebuilt.chat_agent_executor.create_react_agent], such as the prebuilt [`SummarizationNode`](https://langchain-ai.github.io/langmem/reference/short_term/#langmem.short_term.SummarizationNode):
|
||||
|
||||
```python
|
||||
from langchain_anthropic import ChatAnthropic
|
||||
@@ -158,144 +138,8 @@ agent = create_react_agent(
|
||||
4. The `pre_model_hook` is set to the `SummarizationNode`. This node will summarize the message history before sending it to the LLM. The summarization node will automatically handle the summarization process and update the agent's state with the new summary. You can replace this with a custom implementation if you prefer. Please see the [create_react_agent][langgraph.prebuilt.chat_agent_executor.create_react_agent] API reference for more details.
|
||||
5. The `state_schema` is set to the `State` class, which is the custom state that contains an extra `context` key.
|
||||
|
||||
#### Trim message history
|
||||
|
||||
To trim message history, you can use [`pre_model_hook`][langgraph.prebuilt.chat_agent_executor.create_react_agent] with [`trim_messages`](https://python.langchain.com/api_reference/core/messages/langchain_core.messages.utils.trim_messages.html) function:
|
||||
|
||||
```python
|
||||
# highlight-next-line
|
||||
from langchain_core.messages.utils import (
|
||||
# highlight-next-line
|
||||
trim_messages,
|
||||
# highlight-next-line
|
||||
count_tokens_approximately
|
||||
# highlight-next-line
|
||||
)
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
|
||||
# This function will be called every time before the node that calls LLM
|
||||
def pre_model_hook(state):
|
||||
trimmed_messages = trim_messages(
|
||||
state["messages"],
|
||||
strategy="last",
|
||||
token_counter=count_tokens_approximately,
|
||||
max_tokens=384,
|
||||
start_on="human",
|
||||
end_on=("human", "tool"),
|
||||
)
|
||||
# highlight-next-line
|
||||
return {"llm_input_messages": trimmed_messages}
|
||||
|
||||
checkpointer = InMemorySaver()
|
||||
agent = create_react_agent(
|
||||
model,
|
||||
tools,
|
||||
# highlight-next-line
|
||||
pre_model_hook=pre_model_hook,
|
||||
checkpointer=checkpointer,
|
||||
)
|
||||
```
|
||||
|
||||
To learn more about using `pre_model_hook` for managing message history, see this [how-to guide](../how-tos/create-react-agent-manage-message-history.ipynb)
|
||||
|
||||
### Read in tools { #read-short-term }
|
||||
|
||||
LangGraph allows agent to access its short-term memory (state) inside the tools.
|
||||
|
||||
```python
|
||||
from typing import Annotated
|
||||
from langgraph.prebuilt import InjectedState, create_react_agent
|
||||
|
||||
class CustomState(AgentState):
|
||||
# highlight-next-line
|
||||
user_id: str
|
||||
|
||||
def get_user_info(
|
||||
# highlight-next-line
|
||||
state: Annotated[CustomState, InjectedState]
|
||||
) -> str:
|
||||
"""Look up user info."""
|
||||
# highlight-next-line
|
||||
user_id = state["user_id"]
|
||||
return "User is John Smith" if user_id == "user_123" else "Unknown user"
|
||||
|
||||
agent = create_react_agent(
|
||||
model="anthropic:claude-3-7-sonnet-latest",
|
||||
tools=[get_user_info],
|
||||
# highlight-next-line
|
||||
state_schema=CustomState,
|
||||
)
|
||||
|
||||
agent.invoke({
|
||||
"messages": "look up user information",
|
||||
# highlight-next-line
|
||||
"user_id": "user_123"
|
||||
})
|
||||
```
|
||||
|
||||
See the [Context](./context.md#__tabbed_2_2) guide for more information.
|
||||
|
||||
### Write from tools { #write-short-term }
|
||||
|
||||
To modify the agent's short-term memory (state) during execution, you can return state updates directly from the tools. This is useful for persisting intermediate results or making information accessible to subsequent tools or prompts.
|
||||
|
||||
```python
|
||||
from typing import Annotated
|
||||
from langchain_core.tools import InjectedToolCallId
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from langchain_core.messages import ToolMessage
|
||||
from langgraph.prebuilt import InjectedState, create_react_agent
|
||||
from langgraph.prebuilt.chat_agent_executor import AgentState
|
||||
from langgraph.types import Command
|
||||
|
||||
class CustomState(AgentState):
|
||||
# highlight-next-line
|
||||
user_name: str
|
||||
|
||||
def update_user_info(
|
||||
tool_call_id: Annotated[str, InjectedToolCallId],
|
||||
config: RunnableConfig
|
||||
) -> Command:
|
||||
"""Look up and update user info."""
|
||||
user_id = config["configurable"].get("user_id")
|
||||
name = "John Smith" if user_id == "user_123" else "Unknown user"
|
||||
# highlight-next-line
|
||||
return Command(update={
|
||||
# highlight-next-line
|
||||
"user_name": name,
|
||||
# update the message history
|
||||
"messages": [
|
||||
ToolMessage(
|
||||
"Successfully looked up user information",
|
||||
tool_call_id=tool_call_id
|
||||
)
|
||||
]
|
||||
})
|
||||
|
||||
def greet(
|
||||
# highlight-next-line
|
||||
state: Annotated[CustomState, InjectedState]
|
||||
) -> str:
|
||||
"""Use this to greet the user once you found their info."""
|
||||
user_name = state["user_name"]
|
||||
return f"Hello {user_name}!"
|
||||
|
||||
agent = create_react_agent(
|
||||
model="anthropic:claude-3-7-sonnet-latest",
|
||||
tools=[update_user_info, greet],
|
||||
# highlight-next-line
|
||||
state_schema=CustomState
|
||||
)
|
||||
|
||||
agent.invoke(
|
||||
{"messages": [{"role": "user", "content": "greet the user"}]},
|
||||
# highlight-next-line
|
||||
config={"configurable": {"user_id": "user_123"}}
|
||||
)
|
||||
```
|
||||
|
||||
For more details, see [how to update state from tools](../how-tos/update-state-from-tools.ipynb).
|
||||
|
||||
## Long-term memory
|
||||
|
||||
Use long-term memory to store user-specific or application-specific data across conversations. This is useful for applications like chatbots, where you want to remember user preferences or other information.
|
||||
@@ -305,10 +149,9 @@ To use long-term memory, you need to:
|
||||
1. [Configure a store](../how-tos/cross-thread-persistence.ipynb) to persist data across invocations.
|
||||
2. Use the [`get_store`][langgraph.config.get_store] function to access the store from within tools or prompts.
|
||||
|
||||
### Read { #read-long-term }
|
||||
### Reading
|
||||
|
||||
```python title="A tool the agent can use to look up user information"
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from langgraph.config import get_store
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
from langgraph.store.memory import InMemoryStore
|
||||
@@ -331,7 +174,7 @@ def get_user_info(config: RunnableConfig) -> str:
|
||||
# Same as that provided to `create_react_agent`
|
||||
# highlight-next-line
|
||||
store = get_store() # (6)!
|
||||
user_id = config["configurable"].get("user_id")
|
||||
user_id = config.get("configurable", {}).get("user_id")
|
||||
# highlight-next-line
|
||||
user_info = store.get(("users",), user_id) # (7)!
|
||||
return str(user_info.value) if user_info else "Unknown user"
|
||||
@@ -360,7 +203,7 @@ agent.invoke(
|
||||
7. The `get` method is used to retrieve data from the store. The first argument is the namespace, and the second argument is the key. This will return a `StoreValue` object, which contains the value and metadata about the value.
|
||||
8. The `store` is passed to the agent. This enables the agent to access the store when running tools. You can also use the `get_store` function to access the store from anywhere in your code.
|
||||
|
||||
### Write { #write-long-term }
|
||||
### Writing
|
||||
|
||||
```python title="Example of a tool that updates user information"
|
||||
from typing_extensions import TypedDict
|
||||
@@ -379,7 +222,7 @@ def save_user_info(user_info: UserInfo, config: RunnableConfig) -> str: # (3)!
|
||||
# Same as that provided to `create_react_agent`
|
||||
# highlight-next-line
|
||||
store = get_store() # (4)!
|
||||
user_id = config["configurable"].get("user_id")
|
||||
user_id = config.get("configurable", {}).get("user_id")
|
||||
# highlight-next-line
|
||||
store.put(("users",), user_id, user_info) # (5)!
|
||||
return "Successfully saved user info."
|
||||
@@ -409,10 +252,6 @@ store.get(("users",), "user_123").value
|
||||
5. The `put` method is used to store data in the store. The first argument is the namespace, and the second argument is the key. This will store the user information in the store.
|
||||
6. The `user_id` is passed in the config. This is used to identify the user whose information is being updated.
|
||||
|
||||
### Semantic search
|
||||
|
||||
LangGraph also allows you to [search](https://langchain-ai.github.io/langgraph/how-tos/memory/semantic-search/#using-in-create-react-agent) for items in long-term memory by semantic similarity.
|
||||
|
||||
### Prebuilt memory tools
|
||||
|
||||
**LangMem** is a LangChain-maintained library that offers tools for managing long-term memories in your agent. See the [LangMem documentation](https://langchain-ai.github.io/langmem/) for usage examples.
|
||||
@@ -420,4 +259,4 @@ LangGraph also allows you to [search](https://langchain-ai.github.io/langgraph/h
|
||||
|
||||
## Additional resources
|
||||
|
||||
* [Memory in LangGraph](../concepts/memory.md)
|
||||
* [Memory in LangGraph](../concepts/memory.md)
|
||||
@@ -1,14 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
tags:
|
||||
- anthropic
|
||||
- openai
|
||||
- agent
|
||||
hide:
|
||||
- tags
|
||||
---
|
||||
|
||||
# Models
|
||||
|
||||
This page describes how to configure the chat model used by an agent.
|
||||
@@ -74,72 +63,7 @@ agent = create_react_agent(
|
||||
|
||||
The example above uses `ChatAnthropic`, which is already supported by `init_chat_model`. This pattern is shown to illustrate how to manually instantiate a model not available through init_chat_model.
|
||||
|
||||
## Disable streaming
|
||||
|
||||
To disable streaming of the individual LLM tokens, set `disable_streaming=True` when initializing the model:
|
||||
|
||||
=== "`init_chat_model`"
|
||||
|
||||
```python
|
||||
from langchain.chat_models import init_chat_model
|
||||
|
||||
model = init_chat_model(
|
||||
"anthropic:claude-3-7-sonnet-latest",
|
||||
# highlight-next-line
|
||||
disable_streaming=True
|
||||
)
|
||||
```
|
||||
|
||||
=== "`ChatModel`"
|
||||
|
||||
```python
|
||||
from langchain_anthropic import ChatAnthropic
|
||||
|
||||
model = ChatAnthropic(
|
||||
model="claude-3-7-sonnet-latest",
|
||||
# highlight-next-line
|
||||
disable_streaming=True
|
||||
)
|
||||
```
|
||||
|
||||
Refer to the [API reference](https://python.langchain.com/api_reference/core/language_models/langchain_core.language_models.chat_models.BaseChatModel.html#langchain_core.language_models.chat_models.BaseChatModel.disable_streaming) for more information on `disable_streaming`
|
||||
|
||||
## Adding model fallbacks
|
||||
|
||||
You can add a fallback to a different model or a different LLM provider using `model.with_fallbacks([...])`:
|
||||
|
||||
=== "`init_chat_model`"
|
||||
|
||||
```python
|
||||
from langchain.chat_models import init_chat_model
|
||||
|
||||
model_with_fallbacks = (
|
||||
init_chat_model("anthropic:claude-3-5-haiku-latest")
|
||||
# highlight-next-line
|
||||
.with_fallbacks([
|
||||
init_chat_model("openai:gpt-4.1-mini"),
|
||||
])
|
||||
)
|
||||
```
|
||||
|
||||
=== "`ChatModel`"
|
||||
|
||||
```python
|
||||
from langchain_anthropic import ChatAnthropic
|
||||
from langchain_openai import ChatOpenAI
|
||||
|
||||
model_with_fallbacks = (
|
||||
ChatAnthropic(model="claude-3-5-haiku-latest")
|
||||
# highlight-next-line
|
||||
.with_fallbacks([
|
||||
ChatOpenAI(model="gpt-4.1-mini"),
|
||||
])
|
||||
)
|
||||
```
|
||||
|
||||
See this [guide](https://python.langchain.com/docs/how_to/fallbacks/#fallback-to-better-model) for more information on model fallbacks.
|
||||
|
||||
## Additional resources
|
||||
|
||||
- [Model integration directory](https://python.langchain.com/docs/integrations/chat/)
|
||||
- [Universal initialization with `init_chat_model`](https://python.langchain.com/docs/how_to/chat_models_universal_init/)
|
||||
- [Universal initialization with `init_chat_model`](https://python.langchain.com/docs/how_to/chat_models_universal_init/)
|
||||
@@ -1,12 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
tags:
|
||||
- agent
|
||||
hide:
|
||||
- tags
|
||||
---
|
||||
|
||||
# Multi-agent
|
||||
|
||||
A single agent might struggle if it needs to specialize in multiple domains or manage many tools. To tackle this, you can break your agent into smaller, independent agents and composing them into a [multi-agent system](../concepts/multi_agent.md).
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
---
|
||||
title: Overview
|
||||
search:
|
||||
boost: 2
|
||||
tags:
|
||||
- agent
|
||||
hide:
|
||||
- tags
|
||||
---
|
||||
|
||||
# Agent development with LangGraph
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
---
|
||||
tags:
|
||||
- agent
|
||||
hide:
|
||||
- tags
|
||||
---
|
||||
|
||||
# Community Agents
|
||||
|
||||
To share your project, simply open a Pull Request adding an entry for your package in our [packages.yml](https://github.com/langchain-ai/langgraph/blob/main/docs/_scripts/third_party_page/packages.yml) file.
|
||||
|
||||
@@ -1,12 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
tags:
|
||||
- agent
|
||||
hide:
|
||||
- tags
|
||||
---
|
||||
|
||||
# Running agents
|
||||
|
||||
|
||||
|
||||
@@ -1,12 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
tags:
|
||||
- agent
|
||||
hide:
|
||||
- tags
|
||||
---
|
||||
|
||||
# Streaming
|
||||
|
||||
Streaming is key to building responsive applications. There are a few types of data you’ll want to stream:
|
||||
@@ -212,12 +203,6 @@ You can specify multiple streaming modes by passing stream mode as a list: `stre
|
||||
print("\n")
|
||||
```
|
||||
|
||||
## Disable streaming
|
||||
|
||||
In some applications you might need to disable streaming of individual tokens for a given model. This is useful in [multi-agent](./multi-agent.md) systems to control which agents stream their output.
|
||||
|
||||
See the [Models](./models.md#disable-streaming) guide to learn how to disable streaming.
|
||||
|
||||
## Additional resources
|
||||
|
||||
* [Streaming in LangGraph](https://langchain-ai.github.io/langgraph/how-tos/streaming)
|
||||
|
||||
@@ -1,12 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
tags:
|
||||
- agent
|
||||
hide:
|
||||
- tags
|
||||
---
|
||||
|
||||
# Tools
|
||||
|
||||
[Tools](https://python.langchain.com/docs/concepts/tools/) are a way to encapsulate a function and its input schema in a way that can be passed to a chat model that supports tool calling. This allows the model to request the execution of this function with specific inputs.
|
||||
@@ -271,13 +262,6 @@ By default, the agent will catch all exceptions raised during tool calls and wil
|
||||
|
||||
See [API reference][langgraph.prebuilt.tool_node.ToolNode] for more information on different tool error handling options.
|
||||
|
||||
## Working with memory
|
||||
|
||||
LangGraph allows access to short-term and long-term memory from tools. See [Memory](./memory.md) guide for more information on:
|
||||
|
||||
* how to [read](./memory.md#read-short-term) from and [write](./memory.md#write-short-term) to **short-term** memory
|
||||
* how to [read](./memory.md#read-long-term) from and [write](./memory.md#write-long-term) to **long-term** memory
|
||||
|
||||
## Prebuilt tools
|
||||
|
||||
LangChain supports a wide range of prebuilt tool integrations for interacting with APIs, databases, file systems, web data, and more. These tools extend the functionality of agents and enable rapid development.
|
||||
|
||||
@@ -1,12 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
tags:
|
||||
- agent
|
||||
hide:
|
||||
- tags
|
||||
---
|
||||
|
||||
# UI
|
||||
|
||||
You can use a prebuilt chat UI for interacting with any LangGraph agent through the [Agent Chat UI](https://github.com/langchain-ai/agent-chat-ui). Using the [deployed version](https://agentchat.vercel.app) is the quickest way to get started, and allows you to interact with both local and deployed graphs.
|
||||
|
||||
@@ -107,13 +107,13 @@ After installing and authorizing LangChain's `hosted-langserve` GitHub app, repo
|
||||
All traffic from `LangGraph Platform` deployments created after January 6th 2025 will come through a NAT gateway.
|
||||
This NAT gateway will have several static ip addresses depending on the region you are deploying in. Refer to the table below for the list of IP addresses to whitelist:
|
||||
|
||||
| US | EU |
|
||||
|----------------|-----------------|
|
||||
| 35.197.29.146 | 34.90.213.236 |
|
||||
| 34.145.102.123 | 34.13.244.114 |
|
||||
| 34.169.45.153 | 34.32.180.189 |
|
||||
| 34.82.222.17 | 34.34.69.108 |
|
||||
| 35.227.171.135 | 34.32.145.240 |
|
||||
| 34.169.88.30 | 34.90.157.44 |
|
||||
| 34.19.93.202 | 34.141.242.180 |
|
||||
| 34.19.34.50 | 34.32.141.108 |
|
||||
| US | EU |
|
||||
|----------------|----------------|
|
||||
| 35.197.29.146 | 34.13.192.67 |
|
||||
| 34.145.102.123 | 34.147.105.64 |
|
||||
| 34.169.45.153 | 34.90.22.166 |
|
||||
| 34.82.222.17 | 34.147.36.213 |
|
||||
| 35.227.171.135 | 34.32.137.113 |
|
||||
| 34.169.88.30 | 34.91.238.184 |
|
||||
| 34.19.93.202 | 35.204.101.241 |
|
||||
| 34.19.34.50 | 35.204.48.32 |
|
||||
|
||||
@@ -12,13 +12,19 @@ Before deploying, review the [conceptual guide for the Self-Hosted Control Plane
|
||||
|
||||
helm repo add kedacore https://kedacore.github.io/charts
|
||||
helm install keda kedacore/keda --namespace keda --create-namespace
|
||||
1. Ingress Configuration
|
||||
1. You must set up an ingress for your LangSmith instance. All agents will be deployed as Kubernetes services behind this ingress.
|
||||
1. You can use this guide to [set up an ingress](https://docs.smith.langchain.com/self_hosting/configuration/ingress) for your instance.
|
||||
1. You have slack space in your cluster for multiple deployments. `Cluster-Autoscaler` is recommended to automatically provision new nodes.
|
||||
1. A valid Dynamic PV provisioner or PVs available on your cluster. You can verify this by running:
|
||||
|
||||
kubectl get storageclass
|
||||
1. Ingress Configuration (recommended)
|
||||
1. Install `Ingress Nginx` to serve as a reverse proxy for your deployment.
|
||||
|
||||
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
|
||||
helm repo update
|
||||
helm install ingress-nginx ingress-nginx/ingress-nginx
|
||||
|
||||
1. Provision a root domain that will suffix all domains for your workloads (e.g. `us.langgraph.app`).
|
||||
1. Provision wildcard certificates to terminate TLS for your deployments.
|
||||
1. Note: If this step is skipped, you will need to provision domains/certs for each of your deployments.
|
||||
|
||||
1. You have slack space in your cluster for multiple deployments. `Cluster-Autoscaler` is recommended to automatically provision new nodes.
|
||||
|
||||
## Setup
|
||||
|
||||
@@ -38,12 +44,13 @@ Before deploying, review the [conceptual guide for the Self-Hosted Control Plane
|
||||
pullPolicy: IfNotPresent
|
||||
tag: "aa9dff4"
|
||||
|
||||
1. In your `values.yaml` file, enable the `langgraphPlatform` option. Note that you must also have a valid ingress setup:
|
||||
1. In your `values.yaml` file, enable the `langgraphPlatform` option.
|
||||
|
||||
config:
|
||||
langgraphPlatform:
|
||||
enabled: true
|
||||
langgraphPlatformLicenseKey: "YOUR_LANGGRAPH_PLATFORM_LICENSE_KEY"
|
||||
1. In your `values.yaml` file, configure the `hostBackendImage` and `operatorImage` options (if you need to mirror images)
|
||||
rootDomain: "YOUR_ROOT_DOMAIN"
|
||||
|
||||
1. You can also configure base templates for your agents by overriding the base templates [here](https://github.com/langchain-ai/helm/blob/main/charts/langsmith/values.yaml#L898).
|
||||
1. You create a deployment from the [Control Plane UI](../../concepts/langgraph_control_plane.md#control-plane-ui).
|
||||
|
||||
@@ -17,58 +17,8 @@ Here's how to customize the included and excluded headers:
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
The `include` and `exclude` lists accept exact header names or patterns using `*` to match any number of characters. For your security, no other regex patterns are supported.
|
||||
|
||||
## Using within your graph
|
||||
|
||||
You can access the included headers in your graph using the `config` argument of any node.
|
||||
|
||||
```python
|
||||
def my_node(state, config):
|
||||
organization_id = config["configurable"].get("x-organization-id")
|
||||
...
|
||||
```
|
||||
|
||||
Or by fetching from context (useful in tools and or within other nested functions).
|
||||
|
||||
```python
|
||||
from langgraph.config import get_config
|
||||
|
||||
def search_everything(query: str):
|
||||
organization_id = get_config()["configurable"].get("x-organization-id")
|
||||
...
|
||||
```
|
||||
|
||||
|
||||
You can even use this to dynamically compile the graph.
|
||||
|
||||
```python
|
||||
# my_graph.py.
|
||||
import contextlib
|
||||
|
||||
@contextlib.asynccontextmanager
|
||||
async def generate_agent(config):
|
||||
organization_id = config["configurable"].get("x-organization-id")
|
||||
if organization_id == "org1":
|
||||
graph = ...
|
||||
yield graph
|
||||
else:
|
||||
graph = ...
|
||||
yield graph
|
||||
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"graphs": {"agent": "my_grph.py:generate_agent"}
|
||||
}
|
||||
```
|
||||
|
||||
For more examples on how to use runtime configuration, check out the [configuration how-to](../../how-tos/configuration.ipynb).
|
||||
|
||||
### Opt-out of configurable headers
|
||||
|
||||
If you'd like to opt-out of configurable headers, you can simply set a wildcard pattern in the `exclude` list:
|
||||
|
||||
```json
|
||||
@@ -81,6 +31,4 @@ If you'd like to opt-out of configurable headers, you can simply set a wildcard
|
||||
}
|
||||
```
|
||||
|
||||
This will exclude all headers from being added to your run's configuration.
|
||||
|
||||
Note that exclusions take precedence over inclusions.
|
||||
This will exclude all headers from being added to your run's configuration.
|
||||
@@ -207,6 +207,18 @@ Behind the scenes, `LoadExternalComponent` will fetch the JS and CSS for the UI
|
||||
|
||||
## How-to guides
|
||||
|
||||
### Show loading UI when components are loading
|
||||
|
||||
You can provide a fallback UI to be rendered when the components are loading.
|
||||
|
||||
```tsx
|
||||
<LoadExternalComponent
|
||||
stream={thread}
|
||||
message={ui}
|
||||
fallback={<div>Loading...</div>}
|
||||
/>
|
||||
```
|
||||
|
||||
### Provide custom components on the client side
|
||||
|
||||
If you already have the components loaded in your client application, you can provide a map of such components to be rendered directly without fetching the UI code from LangGraph Platform.
|
||||
@@ -223,18 +235,6 @@ const clientComponents = {
|
||||
/>;
|
||||
```
|
||||
|
||||
### Show loading UI when components are loading
|
||||
|
||||
You can provide a fallback UI to be rendered when the components are loading.
|
||||
|
||||
```tsx
|
||||
<LoadExternalComponent
|
||||
stream={thread}
|
||||
message={ui}
|
||||
fallback={<div>Loading...</div>}
|
||||
/>
|
||||
```
|
||||
|
||||
### Customise the namespace of UI components.
|
||||
|
||||
By default `LoadExternalComponent` will use the `assistantId` from `useStream()` hook to fetch the code for UI components. You can customise this by providing a `namespace` prop to the `LoadExternalComponent` component.
|
||||
@@ -316,9 +316,9 @@ const WeatherComponent = (props: { city: string }) => {
|
||||
};
|
||||
```
|
||||
|
||||
### Streaming UI messages from the server
|
||||
### Streaming UI updates before the node execution is finished
|
||||
|
||||
You can stream UI messages before the node execution is finished by using the `onCustomEvent` callback of the `useStream()` hook. This is especially useful when updating the UI component as the LLM is generating the response.
|
||||
You can stream UI updates before the node execution is finished by using the `onCustomEvent` callback of the `useStream()` hook.
|
||||
|
||||
```tsx
|
||||
import { uiMessageReducer } from "@langchain/langgraph-sdk/react-ui";
|
||||
@@ -335,162 +335,6 @@ const { thread, submit } = useStream({
|
||||
});
|
||||
```
|
||||
|
||||
Then you can pushing updates to the UI component by calling `ui.push()` / `push_ui_message()` with the same ID as the UI message you wish to update.
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
from typing import Annotated, Sequence, TypedDict
|
||||
|
||||
from langchain_anthropic import ChatAnthropic
|
||||
from langchain_core.messages import AIMessage, AIMessageChunk, BaseMessage
|
||||
from langgraph.graph import StateGraph
|
||||
from langgraph.graph.message import add_messages
|
||||
from langgraph.graph.ui import AnyUIMessage, push_ui_message, ui_message_reducer
|
||||
|
||||
|
||||
class AgentState(TypedDict): # noqa: D101
|
||||
messages: Annotated[Sequence[BaseMessage], add_messages]
|
||||
ui: Annotated[Sequence[AnyUIMessage], ui_message_reducer]
|
||||
|
||||
|
||||
class CreateTextDocument(TypedDict):
|
||||
"""Prepare a document heading for the user."""
|
||||
|
||||
title: str
|
||||
|
||||
|
||||
async def writer_node(state: AgentState):
|
||||
model = ChatAnthropic(model="claude-3-5-sonnet-latest")
|
||||
message: AIMessage = await model.bind_tools(
|
||||
tools=[CreateTextDocument],
|
||||
tool_choice={"type": "tool", "name": "CreateTextDocument"},
|
||||
).ainvoke(state["messages"])
|
||||
|
||||
tool_call = next(
|
||||
(x["args"] for x in message.tool_calls if x["name"] == "CreateTextDocument"),
|
||||
None,
|
||||
)
|
||||
|
||||
if tool_call:
|
||||
ui_message = push_ui_message("writer", tool_call, message=message)
|
||||
ui_message_id = ui_message["id"]
|
||||
|
||||
# We're already streaming the LLM response to the client through UI messages
|
||||
# so we don't need to stream it again to the `messages` stream mode.
|
||||
content_stream = model.with_config({"tags": ["nostream"]}).astream(
|
||||
f"Create a document with the title: {tool_call['title']}"
|
||||
)
|
||||
|
||||
content: AIMessageChunk | None = None
|
||||
async for chunk in content_stream:
|
||||
content = content + chunk if content else chunk
|
||||
|
||||
push_ui_message(
|
||||
"writer",
|
||||
{"content": content.text()},
|
||||
id=ui_message_id,
|
||||
message=message,
|
||||
# Use `merge=rue` to merge props with the existing UI message
|
||||
merge=True,
|
||||
)
|
||||
|
||||
return {"messages": [message]}
|
||||
```
|
||||
|
||||
=== "JS"
|
||||
|
||||
```tsx
|
||||
import {
|
||||
Annotation,
|
||||
MessagesAnnotation,
|
||||
type LangGraphRunnableConfig,
|
||||
} from "@langchain/langgraph";
|
||||
import { z } from "zod";
|
||||
import { ChatAnthropic } from "@langchain/anthropic";
|
||||
import {
|
||||
typedUi,
|
||||
uiMessageReducer,
|
||||
} from "@langchain/langgraph-sdk/react-ui/server";
|
||||
import type { AIMessageChunk } from "@langchain/core/messages";
|
||||
|
||||
import type ComponentMap from "./ui";
|
||||
|
||||
const AgentState = Annotation.Root({
|
||||
...MessagesAnnotation.spec,
|
||||
ui: Annotation({ reducer: uiMessageReducer, default: () => [] }),
|
||||
});
|
||||
|
||||
async function writerNode(
|
||||
state: typeof AgentState.State,
|
||||
config: LangGraphRunnableConfig
|
||||
): Promise<typeof AgentState.Update> {
|
||||
const ui = typedUi<typeof ComponentMap>(config);
|
||||
|
||||
const model = new ChatAnthropic({ model: "claude-3-5-sonnet-latest" });
|
||||
const message = await model
|
||||
.bindTools(
|
||||
[
|
||||
{
|
||||
name: "create_text_document",
|
||||
description: "Prepare a document heading for the user.",
|
||||
schema: z.object({ title: z.string() }),
|
||||
},
|
||||
],
|
||||
{ tool_choice: { type: "tool", name: "create_text_document" } }
|
||||
)
|
||||
.invoke(state.messages);
|
||||
|
||||
type ToolCall = { name: "create_text_document"; args: { title: string } };
|
||||
const toolCall = message.tool_calls?.find(
|
||||
(tool): tool is ToolCall => tool.name === "create_text_document"
|
||||
);
|
||||
|
||||
if (toolCall) {
|
||||
const { id, name } = ui.push(
|
||||
{ name: "writer", props: { title: toolCall.args.title } },
|
||||
{ message }
|
||||
);
|
||||
|
||||
const contentStream = await model
|
||||
// We're already streaming the LLM response to the client through UI messages
|
||||
// so we don't need to stream it again to the `messages` stream mode.
|
||||
.withConfig({ tags: ["nostream"] })
|
||||
.stream(`Create a short poem with the topic: ${message.text}`);
|
||||
|
||||
let content: AIMessageChunk | undefined;
|
||||
for await (const chunk of contentStream) {
|
||||
content = content?.concat(chunk) ?? chunk;
|
||||
|
||||
ui.push(
|
||||
{ id, name, props: { content: content?.text } },
|
||||
// Use `merge: true` to merge props with the existing UI message
|
||||
{ message, merge: true }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return { messages: [message] };
|
||||
}
|
||||
```
|
||||
|
||||
=== "`ui.tsx`"
|
||||
|
||||
```tsx
|
||||
function WriterComponent(props: { title: string; content?: string }) {
|
||||
return (
|
||||
<article>
|
||||
<h2>{props.title}</h2>
|
||||
<p style={{ whiteSpace: "pre-wrap" }}>{props.content}</p>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
export default {
|
||||
weather: WriterComponent,
|
||||
};
|
||||
```
|
||||
|
||||
### Remove UI messages from state
|
||||
|
||||
Similar to how messages can be removed from the state by appending a RemoveMessage you can remove an UI message from the state by calling `remove_ui_message` / `ui.delete` with the ID of the UI message.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# How to use the interrupt option
|
||||
# Interrupt
|
||||
|
||||
This guide assumes knowledge of what double-texting is, which you can learn about in the [double-texting conceptual guide](../../concepts/double_texting.md).
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
# How to use the Rollback option
|
||||
|
||||
# Rollback
|
||||
|
||||
This guide assumes knowledge of what double-texting is, which you can learn about in the [double-texting conceptual guide](../../concepts/double_texting.md).
|
||||
|
||||
|
||||
@@ -42,10 +42,8 @@ The LangGraph CLI requires a JSON configuration file that follows this [schema](
|
||||
| <span style="white-space: nowrap;">`dependencies`</span> | **Required**. Array of dependencies for LangGraph Cloud API server. Dependencies can be one of the following: <ul><li>A single period (`"."`), which will look for local Python packages.</li><li>The directory path where `pyproject.toml`, `setup.py` or `requirements.txt` is located.</br></br>For example, if `requirements.txt` is located in the root of the project directory, specify `"./"`. If it's located in a subdirectory called `local_package`, specify `"./local_package"`. Do not specify the string `"requirements.txt"` itself.</li><li>A Python package name.</li></ul> |
|
||||
| <span style="white-space: nowrap;">`graphs`</span> | **Required**. Mapping from graph ID to path where the compiled graph or a function that makes a graph is defined. Example: <ul><li>`./your_package/your_file.py:variable`, where `variable` is an instance of `langgraph.graph.state.CompiledStateGraph`</li><li>`./your_package/your_file.py:make_graph`, where `make_graph` is a function that takes a config dictionary (`langchain_core.runnables.RunnableConfig`) and creates an instance of `langgraph.graph.state.StateGraph` / `langgraph.graph.state.CompiledStateGraph`.</li></ul> |
|
||||
| <span style="white-space: nowrap;">`auth`</span> | _(Added in v0.0.11)_ Auth configuration containing the path to your authentication handler. Example: `./your_package/auth.py:auth`, where `auth` is an instance of `langgraph_sdk.Auth`. See [authentication guide](../../concepts/auth.md) for details. |
|
||||
| <span style="white-space: nowrap;">`base_image`</span> | Optional. Base image to use for the LangGraph API server. Defaults to `langchain/langgraph-api` or `langchain/langgraphjs-api`. Use this to pin your builds to a particular version of the langgraph API, such as `"langchain/langgraph-server:0.2"`. See https://hub.docker.com/r/langchain/langgraph-server/tags for more details. (added in `langgraph-cli==0.2.8`) |
|
||||
| <span style="white-space: nowrap;">`env`</span> | Path to `.env` file or a mapping from environment variable to its value. |
|
||||
| <span style="white-space: nowrap;">`store`</span> | Configuration for adding semantic search and/or time-to-live (TTL) to the BaseStore. Contains the following fields: <ul><li>`index` (optional): Configuration for semantic search indexing with fields `embed`, `dims`, and optional `fields`.</li><li>`ttl` (optional): Configuration for item expiration. An object with optional fields: `refresh_on_read` (boolean, defaults to `true`), `default_ttl` (float, lifespan in **minutes**, defaults to no expiration), and `sweep_interval_minutes` (integer, how often to check for expired items, defaults to no sweeping).</li></ul> |
|
||||
| <span style="white-space: nowrap;">`ui`</span> | Optional. Named definitions of UI components emitted by the agent, each pointing to a JS/TS file. (added in `langgraph-cli==0.1.84`) |
|
||||
| <span style="white-space: nowrap;">`python_version`</span> | `3.11`, `3.12`, or `3.13`. Defaults to `3.11`. |
|
||||
| <span style="white-space: nowrap;">`node_version`</span> | Specify `node_version: 20` to use LangGraph.js. |
|
||||
| <span style="white-space: nowrap;">`pip_config_file`</span> | Path to `pip` config file. |
|
||||
|
||||
@@ -91,10 +91,6 @@ Database Connectivity:
|
||||
|
||||
- The custom Postgres instance must be accessible by the LangGraph Server. The user is responsible for ensuring connectivity.
|
||||
|
||||
## `LANGGRAPH_POSTGRES_POOL_MAX_SIZE`
|
||||
|
||||
Beginning with langgraph-api version `0.2.12`, the maximum size of the Postgres connection pool can be controlled using the `LANGGRAPH_POSTGRES_POOL_MAX_SIZE` environment variable. By setting this variable, you can determine the upper bound on the number of simultaneous connections the server will establish with the Postgres database. This is particularly useful for deployments where database resources are limited (or more available) or where you need to tune connection behavior for performance or scaling reasons. If not specified, the pool size defaults to 150 connections.
|
||||
|
||||
## `REDIS_URI_CUSTOM`
|
||||
|
||||
!!! info "Only for Self-Hosted Data Plane and Self-Hosted Control Plane"
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
---
|
||||
|
||||
# Agent architectures
|
||||
|
||||
Many LLM applications implement a particular control flow of steps before and / or after LLM calls. As an example, [RAG](https://github.com/langchain-ai/rag-from-scratch) performs retrieval of documents relevant to a user question, and passes those documents to an LLM in order to ground the model's response in the provided document context.
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
---
|
||||
|
||||
# Application Structure
|
||||
|
||||
!!! info "Prerequisites"
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
---
|
||||
|
||||
# Assistants
|
||||
|
||||
!!! info "Prerequisites"
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
---
|
||||
|
||||
# Authentication & Access Control
|
||||
|
||||
LangGraph Platform provides a flexible authentication and authorization system that can integrate with most authentication schemes.
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
---
|
||||
|
||||
# Breakpoints
|
||||
|
||||
Breakpoints pause graph execution at specific points and enable stepping through execution step by step. Breakpoints are powered by LangGraph's [**persistence layer**](./persistence.md), which saves the state after each graph step. Breakpoints can also be used to enable [**human-in-the-loop**](./human_in_the_loop.md) workflows, though we recommend using the [`interrupt` function](./human_in_the_loop.md#interrupt) for this purpose.
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
---
|
||||
|
||||
# Bring Your Own Cloud (BYOC)
|
||||
|
||||
!!! note Prerequisites
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
---
|
||||
|
||||
# Deployment Options
|
||||
|
||||
!!! info "Prerequisites"
|
||||
@@ -40,7 +35,7 @@ For more information, please see:
|
||||
|
||||
## Self-Hosted Data Plane
|
||||
|
||||
The [Self-Hosted Data Plane](./langgraph_self_hosted_data_plane.md) deployment option is a "hybrid" model for deployment where we manage the [control plane](./langgraph_control_plane.md) in our cloud and you manage the [data plane](./langgraph_data_plane.md) in your cloud. This option provides a way to securely manage your data plane infrastructure, while offloading control plane management to us.
|
||||
The [Self-Hosted Data Plane](./langgraph_self_hosted_data_plane.md) deployment option is a "hybrid" model for deployemnt where we manage the [control plane](./langgraph_control_plane.md) in our cloud and you manage the [data plane](./langgraph_data_plane.md) in your cloud. This option provides a way to securely manage your data plane infrastructure, while offloading control plane management to us.
|
||||
|
||||
Build a Docker image using the [LangGraph CLI](./langgraph_cli.md) and deploy your LangGraph Server from the [Control Plane UI](./langgraph_control_plane.md#control-plane-ui).
|
||||
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
---
|
||||
|
||||
# Double Texting
|
||||
|
||||
!!! info "Prerequisites"
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
---
|
||||
|
||||
# Durable Execution
|
||||
|
||||
**Durable execution** is a technique in which a process or workflow saves its progress at key points, allowing it to pause and later resume exactly where it left off. This is particularly useful in scenarios that require [human-in-the-loop](./human_in_the_loop.md), where users can inspect, validate, or modify the process before continuing, and in long-running tasks that might encounter interruptions or errors (e.g., calls to an LLM timing out). By preserving completed work, durable execution enables a process to resume without reprocessing previous steps -- even after a significant delay (e.g., a week later).
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
---
|
||||
|
||||
# FAQ
|
||||
|
||||
Common questions and their answers!
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
---
|
||||
|
||||
# Functional API
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
---
|
||||
|
||||
# Why LangGraph?
|
||||
|
||||
## LLM applications
|
||||
|
||||
@@ -1,13 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
tags:
|
||||
- human-in-the-loop
|
||||
- hil
|
||||
hide:
|
||||
- tags
|
||||
---
|
||||
|
||||
# Human-in-the-loop
|
||||
|
||||
!!! tip "This guide uses the new `interrupt` function."
|
||||
@@ -400,14 +390,24 @@ When using the `interrupt` function, the graph will pause at the interrupt and w
|
||||
|
||||
Graph execution can be resumed using the [Command](../reference/types.md#langgraph.types.Command) primitive which can be passed through the `invoke`, `ainvoke`, `stream` or `astream` methods.
|
||||
|
||||
The `Command` primitive provides a way to **pass a value** (such as user's input) to the `interrupt` via `Command(resume=value)`. Execution resumes from the beginning of the node where the `interrupt` was used, however, this time the `interrupt(...)` call will return the value passed in the `Command(resume=value)` instead of pausing the graph.
|
||||
The `Command` primitive provides several options to control and modify the graph's state during resumption:
|
||||
|
||||
```python
|
||||
# Resume graph execution with the user's input.
|
||||
graph.invoke(Command(resume={"age": "25"}), thread_config)
|
||||
```
|
||||
1. **Pass a value to the `interrupt`**: Provide data, such as a user's response, to the graph using `Command(resume=value)`. Execution resumes from the beginning of the node where the `interrupt` was used, however, this time the `interrupt(...)` call will return the value passed in the `Command(resume=value)` instead of pausing the graph.
|
||||
|
||||
By leveraging `Command`, you can resume graph execution and handle user inputs.
|
||||
```python
|
||||
# Resume graph execution with the user's input.
|
||||
graph.invoke(Command(resume={"age": "25"}), thread_config)
|
||||
```
|
||||
|
||||
2. **Update the graph state**: Modify the graph state using `Command(update=update)`. Note that resumption starts from the beginning of the node where the `interrupt` was used. Execution resumes from the beginning of the node where the `interrupt` was used, but with the updated state.
|
||||
|
||||
```python
|
||||
# Update the graph state and resume.
|
||||
# You must provide a `resume` value if using an `interrupt`.
|
||||
graph.invoke(Command(update={"foo": "bar"}, resume="Let's go!!!"), thread_config)
|
||||
```
|
||||
|
||||
By leveraging `Command`, you can resume graph execution, handle user inputs, and dynamically adjust the graph's state.
|
||||
|
||||
## How does resuming from an interrupt work?
|
||||
|
||||
@@ -440,22 +440,6 @@ Upon **resuming** the graph, the counter will be incremented a second time, resu
|
||||
The value of counter is: 2
|
||||
```
|
||||
|
||||
### Resuming multiple interrupts with one invocation
|
||||
|
||||
If you have multiple interrupts in the task queue, you can use `Command.resume` with a dictionary mapping
|
||||
of interrupt ids to resume values to resume multiple interrupts with a single `invoke` / `stream` call.
|
||||
|
||||
For example, once your graph has been interrupted (multiple times, theoretically) and is stalled:
|
||||
|
||||
```python
|
||||
resume_map = {
|
||||
i.interrupt_id: f"human input for prompt {i.value}"
|
||||
for i in parent.get_state(thread_config).interrupts
|
||||
}
|
||||
|
||||
parent_graph.invoke(Command(resume=resume_map), config=thread_config)
|
||||
```
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
### Side-effects
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
---
|
||||
title: Concepts
|
||||
description: Conceptual Guide for LangGraph
|
||||
search:
|
||||
boost: 0.5
|
||||
---
|
||||
|
||||
# Conceptual Guide
|
||||
@@ -75,7 +73,6 @@ The LangGraph Platform comprises several components that work together to suppor
|
||||
- [Cron Jobs](./langgraph_server.md#cron-jobs): Cron jobs are a way to schedule tasks to run at specific times in your LangGraph application.
|
||||
- [Double Texting](./double_texting.md): Double texting is a common issue in LLM applications where users may send multiple messages before the graph has finished running. This guide explains how to handle double texting with LangGraph Deploy.
|
||||
- [Authentication & Access Control](./auth.md): Learn about options for authentication and access control when deploying the LangGraph Platform.
|
||||
- [MCP Endpoint](./server-mcp.md): Expose your LangGraph agents as MCP tools using an MCP endpoint.
|
||||
|
||||
### Deployment Options
|
||||
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
---
|
||||
|
||||
# LangGraph CLI
|
||||
|
||||
!!! info "Prerequisites"
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
---
|
||||
|
||||
# Cloud SaaS (Beta)
|
||||
|
||||
To deploy a [LangGraph Server](../concepts/langgraph_server.md), follow the how-to guide for [how to deploy to Cloud SaaS](../cloud/deployment/cloud.md).
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
---
|
||||
|
||||
# LangGraph Control Plane
|
||||
|
||||
The term "control plane" is used broadly to refer to the Control Plane UI where users create and update [LangGraph Servers](./langgraph_server.md) (deployments) and the Control Plane APIs that support the UI experience.
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
---
|
||||
|
||||
# LangGraph Data Plane
|
||||
|
||||
The term "data plane" is used broadly to refer to [LangGraph Servers](./langgraph_server.md) (deployments), the corresponding infrastructure for each server, and the "listener" application that continuously polls for updates from the [LangGraph Control Plane](./langgraph_control_plane.md).
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
---
|
||||
|
||||
# Self-Hosted Control Plane (Beta)
|
||||
|
||||
To deploy a [LangGraph Server](../concepts/langgraph_server.md), follow the how-to guide for [how to deploy the Self-Hosted Control Plane](../cloud/deployment/self_hosted_control_plane.md).
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
---
|
||||
|
||||
# Self-Hosted Data Plane (Beta)
|
||||
|
||||
To deploy a [LangGraph Server](../concepts/langgraph_server.md), follow the how-to guide for [how to deploy the Self-Hosted Data Plane](../cloud/deployment/self_hosted_data_plane.md).
|
||||
|
||||
## Overview
|
||||
|
||||
LangGraph Platform's Self-Hosted Data Plane deployment option is a "hybrid" model for deployment where we manage the [control plane](./langgraph_control_plane.md) in our cloud and you manage the [data plane](./langgraph_data_plane.md) in your cloud.
|
||||
LangGraph Platform's Self-Hosted Data Plane deployment option is a "hybrid" model for deployemnt where we manage the [control plane](./langgraph_control_plane.md) in our cloud and you manage the [data plane](./langgraph_data_plane.md) in your cloud.
|
||||
|
||||
| | [Control Plane](../concepts/langgraph_control_plane.md) | [Data Plane](../concepts/langgraph_data_plane.md) |
|
||||
|-------------------|-------------------|------------|
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
---
|
||||
|
||||
# LangGraph Server
|
||||
|
||||
!!! info "Prerequisites"
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
---
|
||||
|
||||
# Standalone Container
|
||||
|
||||
To deploy a [LangGraph Server](../concepts/langgraph_server.md), follow the how-to guide for [how to deploy a Standalone Container](../cloud/deployment/standalone_container.md).
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
---
|
||||
|
||||
# LangGraph Studio
|
||||
|
||||
!!! info "Prerequisites"
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
---
|
||||
|
||||
# LangGraph Glossary
|
||||
|
||||
## Graphs
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
---
|
||||
|
||||
# Memory
|
||||
|
||||
## What is Memory?
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
---
|
||||
|
||||
# Multi-agent Systems
|
||||
|
||||
An [agent](./agentic_concepts.md#agent-architectures) is _a system that uses an LLM to decide the control flow of an application_. As you develop these systems, they might grow more complex over time, making them harder to manage and scale. For example, you might run into the following problems:
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
---
|
||||
|
||||
# Persistence
|
||||
|
||||
LangGraph has a built-in persistence layer, implemented through checkpointers. When you compile graph with a checkpointer, the checkpointer saves a `checkpoint` of the graph state at every super-step. Those checkpoints are saved to a `thread`, which can be accessed after graph execution. Because `threads` allow access to graph's state after execution, several powerful capabilities including human-in-the-loop, memory, time travel, and fault-tolerance are all possible. See [this how-to guide](../how-tos/persistence.ipynb) for an end-to-end example on how to add and use checkpointers with your graph. Below, we'll discuss each of these concepts in more detail.
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
---
|
||||
|
||||
# LangGraph Platform Plans
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
---
|
||||
|
||||
# LangGraph Platform Architecture
|
||||
|
||||

|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
---
|
||||
|
||||
# LangGraph's Runtime (Pregel)
|
||||
|
||||
[Pregel][langgraph.pregel.Pregel] implements LangGraph's runtime, managing the execution of LangGraph applications.
|
||||
@@ -27,7 +22,7 @@ Repeat until no **actors** are selected for execution, or a maximum number of st
|
||||
|
||||
## Actors
|
||||
|
||||
An **actor** is a `PregelNode`. It subscribes to channels, reads data from them, and writes data to them. It can be thought of as an **actor** in the Pregel algorithm. `PregelNodes` implement LangChain's Runnable interface.
|
||||
An **actor** is a [PregelNode][langgraph.pregel.read.PregelNode]. It subscribes to channels, reads data from them, and writes data to them. It can be thought of as an **actor** in the Pregel algorithm. [PregelNodes][langgraph.pregel.read.PregelNode] implement LangChain's Runnable interface.
|
||||
|
||||
## Channels
|
||||
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
---
|
||||
|
||||
# LangGraph Platform: Scalability & Resilience
|
||||
|
||||
LangGraph Platform is designed to scale horizontally with your workload. Each instance of the service is stateless, and keeps no resources in memory. The service is designed to gracefully handle new instances being added or removed, including hard shutdown cases.
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
---
|
||||
|
||||
# LangGraph SDK
|
||||
|
||||
!!! info "Prerequisites"
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
---
|
||||
|
||||
# Self-Hosted
|
||||
|
||||
!!! note Prerequisites
|
||||
|
||||
@@ -1,188 +0,0 @@
|
||||
---
|
||||
tags:
|
||||
- mcp
|
||||
- platform
|
||||
hide:
|
||||
- tags
|
||||
---
|
||||
|
||||
# MCP Endpoint
|
||||
|
||||
The **Model Context Protocol (MCP)** is an open protocol for describing tools and data sources in a model-agnostic format, enabling LLMs to discover
|
||||
and use them via a structured API.
|
||||
|
||||
[LangGraph Server](./langgraph_server.md) implements MCP using the [Streamable HTTP transport](https://spec.modelcontextprotocol.io/specification/2025-03-26/basic/transports/#streamable-http). This allows LangGraph **agents** to be exposed as **MCP tools**, making them usable with any MCP-compliant client supporting Streamable HTTP.
|
||||
|
||||
The MCP endpoint is available at:
|
||||
|
||||
```
|
||||
/mcp
|
||||
```
|
||||
|
||||
on [LangGraph Server](./langgraph_server.md).
|
||||
|
||||
## Requirements
|
||||
|
||||
To use MCP, ensure you have the following dependencies installed:
|
||||
|
||||
- `langgraph-api >= 0.2.3`
|
||||
- `langgraph-sdk >= 0.1.61`
|
||||
|
||||
Install them with:
|
||||
|
||||
```bash
|
||||
pip install "langgraph-api>=0.2.3" "langgraph-sdk>=0.1.61"
|
||||
```
|
||||
|
||||
## Exposing an agent as MCP tool
|
||||
|
||||
|
||||
When deployed, your agent will appear as a tool in the MCP endpoint
|
||||
with this configuration:
|
||||
|
||||
- **Tool name**: The agent's name.
|
||||
- **Tool description**: The agent's description.
|
||||
- **Tool input schema**: The agent's input schema.
|
||||
|
||||
### Setting name and description
|
||||
|
||||
You can set the name and description of your agent in `langgraph.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"graphs": {
|
||||
"my_agent": {
|
||||
"path": "./my_agent/agent.py:graph",
|
||||
"description": "A description of what the agent does"
|
||||
}
|
||||
},
|
||||
"env": ".env"
|
||||
}
|
||||
```
|
||||
|
||||
After deployment, you can update the name and description using the LangGraph SDK.
|
||||
|
||||
### Schema
|
||||
|
||||
Define clear, minimal input and output schemas to avoid exposing unnecessary internal complexity to the LLM.
|
||||
|
||||
The default [MessagesState](./low_level.md#messagesstate) uses `AnyMessage`, which supports many message types but is too general for direct LLM exposure.
|
||||
|
||||
Instead, define **custom agents or workflows** that use explicitly typed input and output structures.
|
||||
|
||||
For example, a workflow answering documentation questions might look like this:
|
||||
|
||||
```python
|
||||
from langgraph.graph import StateGraph, START, END
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
# Define input schema
|
||||
class InputState(TypedDict):
|
||||
question: str
|
||||
|
||||
# Define output schema
|
||||
class OutputState(TypedDict):
|
||||
answer: str
|
||||
|
||||
# Combine input and output
|
||||
class OverallState(InputState, OutputState):
|
||||
pass
|
||||
|
||||
# Define the processing node
|
||||
def answer_node(state: InputState):
|
||||
# Replace with actual logic and do something useful
|
||||
return {"answer": "bye", "question": state["question"]}
|
||||
|
||||
# Build the graph with explicit schemas
|
||||
builder = StateGraph(OverallState, input=InputState, output=OutputState)
|
||||
builder.add_node(answer_node)
|
||||
builder.add_edge(START, "answer_node")
|
||||
builder.add_edge("answer_node", END)
|
||||
graph = builder.compile()
|
||||
|
||||
# Run the graph
|
||||
print(graph.invoke({"question": "hi"}))
|
||||
```
|
||||
|
||||
For more details, see the [low-level concepts guide](https://langchain-ai.github.io/langgraph/concepts/low_level/#state).
|
||||
|
||||
|
||||
## Usage overview
|
||||
|
||||
To enable MCP:
|
||||
|
||||
- Upgrade to use langgraph-api>=0.2.3. If you are deploying LangGraph Platform, this will be done for you automatically if you create a new revision.
|
||||
- MCP tools (agents) will be automatically exposed.
|
||||
- Connect with any MCP-compliant client that supports Streamable HTTP.
|
||||
|
||||
|
||||
### Client
|
||||
|
||||
Use an MCP-compliant client to connect to the LangGraph server. The following examples show how to connect using different programming languages.
|
||||
|
||||
=== "JavaScript/TypeScript"
|
||||
|
||||
```bash
|
||||
npm install @modelcontextprotocol/sdk
|
||||
```
|
||||
|
||||
> **Note**
|
||||
> Replace `serverUrl` with your LangGraph server URL and configure authentication headers as needed.
|
||||
|
||||
```js
|
||||
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
||||
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
||||
|
||||
// Connects to the LangGraph MCP endpoint
|
||||
async function connectClient(url) {
|
||||
const baseUrl = new URL(url);
|
||||
const client = new Client({
|
||||
name: 'streamable-http-client',
|
||||
version: '1.0.0'
|
||||
});
|
||||
|
||||
const transport = new StreamableHTTPClientTransport(baseUrl);
|
||||
await client.connect(transport);
|
||||
|
||||
console.log("Connected using Streamable HTTP transport");
|
||||
console.log(JSON.stringify(await client.listTools(), null, 2));
|
||||
return client;
|
||||
}
|
||||
|
||||
const serverUrl = "http://localhost:2024/mcp";
|
||||
|
||||
connectClient(serverUrl)
|
||||
.then(() => {
|
||||
console.log("Client connected successfully");
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Failed to connect client:", error);
|
||||
});
|
||||
```
|
||||
|
||||
=== "Python"
|
||||
|
||||
No official MCP client is available for Python yet.
|
||||
|
||||
|
||||
## Session behavior
|
||||
|
||||
The current LangGraph MCP implementation does not support sessions. Each `/mcp` request is stateless and independent.
|
||||
|
||||
## Authentication
|
||||
|
||||
The `/mcp` endpoint uses the same authentication as the rest of the LangGraph API. Refer to the [authentication guide](./auth.md) for setup details.
|
||||
|
||||
## Disabling MCP
|
||||
|
||||
To disable the MCP endpoint, set `disable_mcp` to `true` in your `langgraph.json` configuration file:
|
||||
|
||||
```json
|
||||
{
|
||||
"http": {
|
||||
"disable_mcp": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This will prevent the server from exposing the `/mcp` endpoint.
|
||||
@@ -1,8 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
---
|
||||
|
||||
# Streaming
|
||||
|
||||
Building a responsive app for end-users? Real-time updates are key to keeping users engaged as your app progresses.
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
---
|
||||
|
||||
# Template Applications
|
||||
|
||||
Templates are open source reference applications designed to help you get started quickly when building with LangGraph. They provide working examples of common agentic workflows that can be customized to your needs.
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
---
|
||||
|
||||
# Time Travel ⏱️
|
||||
|
||||
!!! note "Prerequisites"
|
||||
|
||||
@@ -112,7 +112,7 @@ Assuming you are using JWT token authentication, you could access your deploymen
|
||||
const my_token = "your-token"; // In practice, you would generate a signed token with your auth provider
|
||||
const client = new Client({
|
||||
apiUrl: "http://localhost:2024",
|
||||
defaultHeaders: { Authorization: `Bearer ${my_token}` },
|
||||
headers: { Authorization: `Bearer ${my_token}` },
|
||||
});
|
||||
const threads = await client.threads.search();
|
||||
```
|
||||
|
||||
@@ -0,0 +1,376 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "992c4695-ec4f-428d-bd05-fb3b5fbd70f4",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# How to add human-in-the-loop processes to the prebuilt ReAct agent\n",
|
||||
"\n",
|
||||
"<div class=\"admonition tip\">\n",
|
||||
" <p class=\"admonition-title\">Prerequisites</p>\n",
|
||||
" <p>\n",
|
||||
" This guide assumes familiarity with the following:\n",
|
||||
" <ul>\n",
|
||||
" <li> \n",
|
||||
" <a href=\"https://langchain-ai.github.io/langgraph/concepts/human_in_the_loop/\">\n",
|
||||
" Human-in-the-loop\n",
|
||||
" </a>\n",
|
||||
" </li>\n",
|
||||
" <li>\n",
|
||||
" <a href=\"https://langchain-ai.github.io/langgraph/concepts/agentic_concepts/\">\n",
|
||||
" Agent Architectures\n",
|
||||
" </a> \n",
|
||||
" </li>\n",
|
||||
" <li>\n",
|
||||
" <a href=\"https://python.langchain.com/docs/concepts/chat_models/\">\n",
|
||||
" Chat Models\n",
|
||||
" </a>\n",
|
||||
" </li>\n",
|
||||
" <li>\n",
|
||||
" <a href=\"https://python.langchain.com/docs/concepts/tools/\">\n",
|
||||
" Tools\n",
|
||||
" </a>\n",
|
||||
" </li> \n",
|
||||
" </ul>\n",
|
||||
" </p>\n",
|
||||
"</div> \n",
|
||||
"\n",
|
||||
"This guide will show how to add human-in-the-loop processes to the prebuilt ReAct agent. Please see [this tutorial](../create-react-agent) for how to get started with the prebuilt ReAct agent\n",
|
||||
"\n",
|
||||
"You can add a a breakpoint before tools are called by passing `interrupt_before=[\"tools\"]` to `create_react_agent`. Note that you need to be using a checkpointer for this to work."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "7be3889f-3c17-4fa1-bd2b-84114a2c7247",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Setup\n",
|
||||
"\n",
|
||||
"First, let's install the required packages and set our API keys"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "a213e11a-5c62-4ddb-a707-490d91add383",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install -U langgraph langchain-openai"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "23a1885c-04ab-4750-aefa-105891fddf3e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def _set_env(var: str):\n",
|
||||
" if not os.environ.get(var):\n",
|
||||
" os.environ[var] = getpass.getpass(f\"{var}: \")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"_set_env(\"OPENAI_API_KEY\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "d4c5c054",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div class=\"admonition tip\">\n",
|
||||
" <p class=\"admonition-title\">Set up <a href=\"https://smith.langchain.com\">LangSmith</a> for LangGraph development</p>\n",
|
||||
" <p style=\"padding-top: 5px;\">\n",
|
||||
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
|
||||
" </p>\n",
|
||||
"</div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "03c0f089-070c-4cd4-87e0-6c51f2477b82",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "7a154152-973e-4b5d-aa13-48c617744a4c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# First we initialize the model we want to use.\n",
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"\n",
|
||||
"model = ChatOpenAI(model=\"gpt-4o\", temperature=0)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# For this tutorial we will use custom tool that returns pre-defined values for weather in two cities (NYC & SF)\n",
|
||||
"from typing import Literal\n",
|
||||
"\n",
|
||||
"from langchain_core.tools import tool\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@tool\n",
|
||||
"def get_weather(location: str):\n",
|
||||
" \"\"\"Use this to get weather information from a given location.\"\"\"\n",
|
||||
" if location.lower() in [\"nyc\", \"new york\"]:\n",
|
||||
" return \"It might be cloudy in nyc\"\n",
|
||||
" elif location.lower() in [\"sf\", \"san francisco\"]:\n",
|
||||
" return \"It's always sunny in sf\"\n",
|
||||
" else:\n",
|
||||
" raise AssertionError(\"Unknown Location\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"tools = [get_weather]\n",
|
||||
"\n",
|
||||
"# We need a checkpointer to enable human-in-the-loop patterns\n",
|
||||
"from langgraph.checkpoint.memory import MemorySaver\n",
|
||||
"\n",
|
||||
"memory = MemorySaver()\n",
|
||||
"\n",
|
||||
"# Define the graph\n",
|
||||
"\n",
|
||||
"from langgraph.prebuilt import create_react_agent\n",
|
||||
"\n",
|
||||
"graph = create_react_agent(\n",
|
||||
" model, tools=tools, interrupt_before=[\"tools\"], checkpointer=memory\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "00407425-506d-4ffd-9c86-987921d8c844",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Usage\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"id": "16636975-5f2d-4dc7-ab8e-d0bea0830a28",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def print_stream(stream):\n",
|
||||
" \"\"\"A utility to pretty print the stream.\"\"\"\n",
|
||||
" for s in stream:\n",
|
||||
" message = s[\"messages\"][-1]\n",
|
||||
" if isinstance(message, tuple):\n",
|
||||
" print(message)\n",
|
||||
" else:\n",
|
||||
" message.pretty_print()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"id": "9ffff6c3-a4f5-47c9-b51d-97caaee85cd6",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"================================\u001b[1m Human Message \u001b[0m=================================\n",
|
||||
"\n",
|
||||
"what is the weather in SF, CA?\n",
|
||||
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
||||
"Tool Calls:\n",
|
||||
" get_weather (call_YjOKDkgMGgUZUpKIasYk1AdK)\n",
|
||||
" Call ID: call_YjOKDkgMGgUZUpKIasYk1AdK\n",
|
||||
" Args:\n",
|
||||
" location: SF, CA\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from langchain_core.messages import HumanMessage\n",
|
||||
"\n",
|
||||
"config = {\"configurable\": {\"thread_id\": \"42\"}}\n",
|
||||
"inputs = {\"messages\": [(\"user\", \"what is the weather in SF, CA?\")]}\n",
|
||||
"\n",
|
||||
"print_stream(graph.stream(inputs, config, stream_mode=\"values\"))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "ca40a719",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We can verify that our graph stopped at the right place:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"id": "3decf001-7228-4ed5-8779-2b9ed98a74ea",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Next step: ('tools',)\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"snapshot = graph.get_state(config)\n",
|
||||
"print(\"Next step: \", snapshot.next)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "7de6ca78",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now we can either approve or edit the tool call before proceeding to the next node. If we wanted to approve the tool call, we would simply continue streaming the graph with `None` input. If we wanted to edit the tool call we need to update the state to have the correct tool call, and then after the update has been applied we can continue.\n",
|
||||
"\n",
|
||||
"We can try resuming and we will see an error arise:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"id": "740bbaeb",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
||||
"Tool Calls:\n",
|
||||
" get_weather (call_YjOKDkgMGgUZUpKIasYk1AdK)\n",
|
||||
" Call ID: call_YjOKDkgMGgUZUpKIasYk1AdK\n",
|
||||
" Args:\n",
|
||||
" location: SF, CA\n",
|
||||
"=================================\u001b[1m Tool Message \u001b[0m=================================\n",
|
||||
"Name: get_weather\n",
|
||||
"\n",
|
||||
"Error: AssertionError('Unknown Location')\n",
|
||||
" Please fix your mistakes.\n",
|
||||
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
||||
"Tool Calls:\n",
|
||||
" get_weather (call_CLu9ofeBhtWF2oheBspxXkfE)\n",
|
||||
" Call ID: call_CLu9ofeBhtWF2oheBspxXkfE\n",
|
||||
" Args:\n",
|
||||
" location: San Francisco, CA\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"print_stream(graph.stream(None, config, stream_mode=\"values\"))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "c1cf5950",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This error arose because our tool argument of \"San Francisco, CA\" is not a location our tool recognizes.\n",
|
||||
"\n",
|
||||
"Let's show how we would edit the tool call to search for \"San Francisco\" instead of \"San Francisco, CA\" - since our tool as written treats \"San Francisco, CA\" as an unknown location. We will update the state and then resume streaming the graph and should see no errors arise:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"id": "1c81ed9f",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'configurable': {'thread_id': '42',\n",
|
||||
" 'checkpoint_ns': '',\n",
|
||||
" 'checkpoint_id': '1ef801d1-5b93-6bb9-8004-a088af1f9cec'}}"
|
||||
]
|
||||
},
|
||||
"execution_count": 11,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"state = graph.get_state(config)\n",
|
||||
"\n",
|
||||
"last_message = state.values[\"messages\"][-1]\n",
|
||||
"last_message.tool_calls[0][\"args\"] = {\"location\": \"San Francisco\"}\n",
|
||||
"\n",
|
||||
"graph.update_state(config, {\"messages\": [last_message]})"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
"id": "83148e08-63e8-49e5-a08b-02dc907bed1d",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
||||
"Tool Calls:\n",
|
||||
" get_weather (call_CLu9ofeBhtWF2oheBspxXkfE)\n",
|
||||
" Call ID: call_CLu9ofeBhtWF2oheBspxXkfE\n",
|
||||
" Args:\n",
|
||||
" location: San Francisco\n",
|
||||
"=================================\u001b[1m Tool Message \u001b[0m=================================\n",
|
||||
"Name: get_weather\n",
|
||||
"\n",
|
||||
"It's always sunny in sf\n",
|
||||
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
||||
"\n",
|
||||
"The weather in San Francisco is currently sunny.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"print_stream(graph.stream(None, config, stream_mode=\"values\"))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "8202a5f9",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Fantastic! Our graph updated properly to query the weather in San Francisco and got the correct \"It's always sunny in sf\" response from the tool, and then responded to the user accordingly."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.12.3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -17,8 +17,8 @@
|
||||
"\n",
|
||||
"Message history can grow quickly and exceed LLM context window size, whether you're building chatbots with many conversation turns or agentic systems with numerous tool calls. There are several strategies for managing the message history:\n",
|
||||
"\n",
|
||||
"* [message trimming](#keep-the-original-message-history-unmodified) — remove first or last N messages in the history\n",
|
||||
"* [summarization](#summarizing-message-history) — summarize earlier messages in the history and replace them with a summary\n",
|
||||
"* [message trimming](#keep-the-original-message-history-unmodified) - remove first or last N messages in the history\n",
|
||||
"* [summarization](#summarizing-message-history) - summarize earlier messages in the history and replace them with a summary\n",
|
||||
"* custom strategies (e.g., message filtering, etc.)\n",
|
||||
"\n",
|
||||
"To manage message history in `create_react_agent`, you need to define a `pre_model_hook` function or [runnable](https://python.langchain.com/docs/concepts/runnables/) that takes graph state an returns a state update:\n",
|
||||
@@ -691,8 +691,7 @@
|
||||
"\n",
|
||||
"checkpointer = InMemorySaver()\n",
|
||||
"graph = create_react_agent(\n",
|
||||
" # limit the output size to ensure consistent behavior\n",
|
||||
" model.bind(max_tokens=256),\n",
|
||||
" model,\n",
|
||||
" tools,\n",
|
||||
" # highlight-next-line\n",
|
||||
" pre_model_hook=summarization_node,\n",
|
||||
|
||||
@@ -0,0 +1,291 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "992c4695-ec4f-428d-bd05-fb3b5fbd70f4",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# How to add thread-level memory to a ReAct Agent\n",
|
||||
"\n",
|
||||
"<div class=\"admonition tip\">\n",
|
||||
" <p class=\"admonition-title\">Prerequisites</p>\n",
|
||||
" <p>\n",
|
||||
" This guide assumes familiarity with the following:\n",
|
||||
" <ul>\n",
|
||||
" <li> \n",
|
||||
" <a href=\"https://langchain-ai.github.io/langgraph/concepts/persistence/\">\n",
|
||||
" LangGraph Persistence\n",
|
||||
" </a>\n",
|
||||
" </li>\n",
|
||||
" <li> \n",
|
||||
" <a href=\"https://langchain-ai.github.io/langgraph/concepts/persistence/#checkpointer-interface\">\n",
|
||||
" Checkpointer interface\n",
|
||||
" </a>\n",
|
||||
" </li>\n",
|
||||
" <li>\n",
|
||||
" <a href=\"https://langchain-ai.github.io/langgraph/concepts/agentic_concepts/\">\n",
|
||||
" Agent Architectures\n",
|
||||
" </a> \n",
|
||||
" </li>\n",
|
||||
" <li>\n",
|
||||
" <a href=\"https://python.langchain.com/docs/concepts/chat_models/\">\n",
|
||||
" Chat Models\n",
|
||||
" </a>\n",
|
||||
" </li>\n",
|
||||
" <li>\n",
|
||||
" <a href=\"https://python.langchain.com/docs/concepts/tools/\">\n",
|
||||
" Tools\n",
|
||||
" </a>\n",
|
||||
" </li>\n",
|
||||
" </ul>\n",
|
||||
" </p>\n",
|
||||
"</div> \n",
|
||||
"\n",
|
||||
"This guide will show how to add memory to the prebuilt ReAct agent. Please see [this tutorial](../create-react-agent) for how to get started with the prebuilt ReAct agent\n",
|
||||
"\n",
|
||||
"We can add memory to the agent, by passing a [checkpointer](https://langchain-ai.github.io/langgraph/reference/checkpoints/) to the [create_react_agent](https://langchain-ai.github.io/langgraph/reference/prebuilt/#langgraph.prebuilt.chat_agent_executor.create_react_agent) function."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "7be3889f-3c17-4fa1-bd2b-84114a2c7247",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Setup\n",
|
||||
"\n",
|
||||
"First, let's install the required packages and set our API keys"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "a213e11a-5c62-4ddb-a707-490d91add383",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install -U langgraph langchain-openai"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "23a1885c-04ab-4750-aefa-105891fddf3e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def _set_env(var: str):\n",
|
||||
" if not os.environ.get(var):\n",
|
||||
" os.environ[var] = getpass.getpass(f\"{var}: \")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"_set_env(\"OPENAI_API_KEY\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "87a00ce9",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div class=\"admonition tip\">\n",
|
||||
" <p class=\"admonition-title\">Set up <a href=\"https://smith.langchain.com\">LangSmith</a> for LangGraph development</p>\n",
|
||||
" <p style=\"padding-top: 5px;\">\n",
|
||||
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
|
||||
" </p>\n",
|
||||
"</div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "03c0f089-070c-4cd4-87e0-6c51f2477b82",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "7a154152-973e-4b5d-aa13-48c617744a4c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# First we initialize the model we want to use.\n",
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"\n",
|
||||
"model = ChatOpenAI(model=\"gpt-4o\", temperature=0)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# For this tutorial we will use custom tool that returns pre-defined values for weather in two cities (NYC & SF)\n",
|
||||
"\n",
|
||||
"from langchain_core.tools import tool\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@tool\n",
|
||||
"def get_weather(location: str) -> str:\n",
|
||||
" \"\"\"Use this to get weather information.\"\"\"\n",
|
||||
" if any([city in location.lower() for city in [\"nyc\", \"new york city\"]]):\n",
|
||||
" return \"It might be cloudy in nyc\"\n",
|
||||
" elif any([city in location.lower() for city in [\"sf\", \"san francisco\"]]):\n",
|
||||
" return \"It's always sunny in sf\"\n",
|
||||
" else:\n",
|
||||
" return f\"I am not sure what the weather is in {location}\"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"tools = [get_weather]\n",
|
||||
"\n",
|
||||
"# We can add \"chat memory\" to the graph with LangGraph's checkpointer\n",
|
||||
"# to retain the chat context between interactions\n",
|
||||
"from langgraph.checkpoint.memory import MemorySaver\n",
|
||||
"\n",
|
||||
"memory = MemorySaver()\n",
|
||||
"\n",
|
||||
"# Define the graph\n",
|
||||
"\n",
|
||||
"from langgraph.prebuilt import create_react_agent\n",
|
||||
"\n",
|
||||
"graph = create_react_agent(model, tools=tools, checkpointer=memory)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "00407425-506d-4ffd-9c86-987921d8c844",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Usage\n",
|
||||
"\n",
|
||||
"Let's interact with it multiple times to show that it can remember"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "16636975-5f2d-4dc7-ab8e-d0bea0830a28",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def print_stream(stream):\n",
|
||||
" for s in stream:\n",
|
||||
" message = s[\"messages\"][-1]\n",
|
||||
" if isinstance(message, tuple):\n",
|
||||
" print(message)\n",
|
||||
" else:\n",
|
||||
" message.pretty_print()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"id": "9ffff6c3-a4f5-47c9-b51d-97caaee85cd6",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"================================\u001b[1m Human Message \u001b[0m=================================\n",
|
||||
"\n",
|
||||
"What's the weather in NYC?\n",
|
||||
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
||||
"Tool Calls:\n",
|
||||
" get_weather (call_xM1suIq26KXvRFqJIvLVGfqG)\n",
|
||||
" Call ID: call_xM1suIq26KXvRFqJIvLVGfqG\n",
|
||||
" Args:\n",
|
||||
" city: nyc\n",
|
||||
"=================================\u001b[1m Tool Message \u001b[0m=================================\n",
|
||||
"Name: get_weather\n",
|
||||
"\n",
|
||||
"It might be cloudy in nyc\n",
|
||||
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
||||
"\n",
|
||||
"The weather in NYC might be cloudy.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"config = {\"configurable\": {\"thread_id\": \"1\"}}\n",
|
||||
"inputs = {\"messages\": [(\"user\", \"What's the weather in NYC?\")]}\n",
|
||||
"\n",
|
||||
"print_stream(graph.stream(inputs, config=config, stream_mode=\"values\"))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "838a043f-90ad-4e69-9d1d-6e22db2c346c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Notice that when we pass the same thread ID, the chat history is preserved."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"id": "187479f9-32fa-4611-9487-cf816ba2e147",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"================================\u001b[1m Human Message \u001b[0m=================================\n",
|
||||
"\n",
|
||||
"What's it known for?\n",
|
||||
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
||||
"\n",
|
||||
"New York City (NYC) is known for a variety of iconic landmarks, cultural institutions, and vibrant neighborhoods. Some of the most notable aspects include:\n",
|
||||
"\n",
|
||||
"1. **Statue of Liberty**: A symbol of freedom and democracy.\n",
|
||||
"2. **Times Square**: Known for its bright lights, Broadway theaters, and bustling atmosphere.\n",
|
||||
"3. **Central Park**: A large urban park offering a green oasis in the middle of the city.\n",
|
||||
"4. **Empire State Building**: An iconic skyscraper with an observation deck offering panoramic views of the city.\n",
|
||||
"5. **Broadway**: Famous for its world-class theater productions.\n",
|
||||
"6. **Wall Street**: The financial hub of the United States.\n",
|
||||
"7. **Museums**: Including the Metropolitan Museum of Art, the Museum of Modern Art (MoMA), and the American Museum of Natural History.\n",
|
||||
"8. **Diverse Cuisine**: A melting pot of culinary experiences from around the world.\n",
|
||||
"9. **Cultural Diversity**: A rich tapestry of cultures, languages, and traditions.\n",
|
||||
"10. **Fashion**: A global fashion capital, home to New York Fashion Week.\n",
|
||||
"\n",
|
||||
"These are just a few highlights of what makes NYC a unique and vibrant city.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"inputs = {\"messages\": [(\"user\", \"What's it known for?\")]}\n",
|
||||
"print_stream(graph.stream(inputs, config=config, stream_mode=\"values\"))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "c461eb47-b4f9-406f-8923-c68db7c5687f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.12.3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "992c4695-ec4f-428d-bd05-fb3b5fbd70f4",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# How to return structured output from the prebuilt ReAct agent\n",
|
||||
"\n",
|
||||
"!!! info \"Prerequisites\"\n",
|
||||
" This guide assumes familiarity with the following:\n",
|
||||
" \n",
|
||||
" - [Agent Architectures](../../concepts/agentic_concepts/)\n",
|
||||
" - [Chat Models](https://python.langchain.com/docs/concepts/chat_models/)\n",
|
||||
" - [Tools](https://python.langchain.com/docs/concepts/tools/)\n",
|
||||
" - [Structured Output](https://python.langchain.com/docs/concepts/structured_outputs/)\n",
|
||||
"\n",
|
||||
"To return structured output from the prebuilt ReAct agent you can provide a `response_format` parameter with the desired output schema to [create_react_agent][langgraph.prebuilt.chat_agent_executor.create_react_agent]:\n",
|
||||
"\n",
|
||||
"```python\n",
|
||||
"class ResponseFormat(BaseModel):\n",
|
||||
" \"\"\"Respond to the user in this format.\"\"\"\n",
|
||||
" my_special_output: str\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"graph = create_react_agent(\n",
|
||||
" model,\n",
|
||||
" tools=tools,\n",
|
||||
" # specify the schema for the structured output using `response_format` parameter\n",
|
||||
" response_format=ResponseFormat\n",
|
||||
")\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"Prebuilt ReAct makes an additional LLM call at the end of the ReAct loop to produce a structured output response. Please see [this guide](../react-agent-structured-output) to learn about other strategies for returning structured outputs from a tool-calling agent."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "7be3889f-3c17-4fa1-bd2b-84114a2c7247",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Setup\n",
|
||||
"\n",
|
||||
"First, let's install the required packages and set our API keys"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "a213e11a-5c62-4ddb-a707-490d91add383",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install -U langgraph langchain-openai"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "23a1885c-04ab-4750-aefa-105891fddf3e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def _set_env(var: str):\n",
|
||||
" if not os.environ.get(var):\n",
|
||||
" os.environ[var] = getpass.getpass(f\"{var}: \")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"_set_env(\"OPENAI_API_KEY\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "87a00ce9",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div class=\"admonition tip\">\n",
|
||||
" <p class=\"admonition-title\">Set up <a href=\"https://smith.langchain.com\">LangSmith</a> for LangGraph development</p>\n",
|
||||
" <p style=\"padding-top: 5px;\">\n",
|
||||
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
|
||||
" </p>\n",
|
||||
"</div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "03c0f089-070c-4cd4-87e0-6c51f2477b82",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "7a154152-973e-4b5d-aa13-48c617744a4c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# First we initialize the model we want to use.\n",
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"\n",
|
||||
"model = ChatOpenAI(model=\"gpt-4o\", temperature=0)\n",
|
||||
"\n",
|
||||
"# For this tutorial we will use custom tool that returns pre-defined values for weather in two cities (NYC & SF)\n",
|
||||
"\n",
|
||||
"from typing import Literal\n",
|
||||
"from langchain_core.tools import tool\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@tool\n",
|
||||
"def get_weather(city: Literal[\"nyc\", \"sf\"]):\n",
|
||||
" \"\"\"Use this to get weather information.\"\"\"\n",
|
||||
" if city == \"nyc\":\n",
|
||||
" return \"It might be cloudy in nyc\"\n",
|
||||
" elif city == \"sf\":\n",
|
||||
" return \"It's always sunny in sf\"\n",
|
||||
" else:\n",
|
||||
" raise AssertionError(\"Unknown city\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"tools = [get_weather]\n",
|
||||
"\n",
|
||||
"# Define the structured output schema\n",
|
||||
"\n",
|
||||
"from pydantic import BaseModel, Field\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class WeatherResponse(BaseModel):\n",
|
||||
" \"\"\"Respond to the user in this format.\"\"\"\n",
|
||||
"\n",
|
||||
" conditions: str = Field(description=\"Weather conditions\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the graph\n",
|
||||
"\n",
|
||||
"from langgraph.prebuilt import create_react_agent\n",
|
||||
"\n",
|
||||
"graph = create_react_agent(\n",
|
||||
" model,\n",
|
||||
" tools=tools,\n",
|
||||
" # specify the schema for the structured output using `response_format` parameter\n",
|
||||
" response_format=WeatherResponse,\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "00407425-506d-4ffd-9c86-987921d8c844",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Usage\n",
|
||||
"\n",
|
||||
"Let's now test our agent:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "9ffff6c3-a4f5-47c9-b51d-97caaee85cd6",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"inputs = {\"messages\": [(\"user\", \"What's the weather in NYC?\")]}\n",
|
||||
"response = graph.invoke(inputs)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "50e273a0-fbdb-4eee-89ca-580fbfb52daf",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"You can see that the agent output contains a `structured_response` key with the structured output conforming to the specified `WeatherResponse` schema, in addition to the message history under `messages` key."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "300748d4-0ed2-470d-8dbc-7c14231e73b8",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"WeatherResponse(conditions='cloudy')"
|
||||
]
|
||||
},
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"response[\"structured_response\"]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "bd9e3487-2cec-44cf-9472-0a51eebeddff",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Customizing prompt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a608548d-77fc-4d7a-845c-32ae9ec0489a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"You might need to further customize the second LLM call for the structured output generation and provide a system prompt. To do so, you can pass a tuple (prompt, schema):"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"id": "d1386f99-ffd1-4b36-86ec-cabb3357d929",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"graph = create_react_agent(\n",
|
||||
" model,\n",
|
||||
" tools=tools,\n",
|
||||
" # specify both the system prompt and the schema for the structured output\n",
|
||||
" response_format=(\"Always return capitalized weather conditions\", WeatherResponse),\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"inputs = {\"messages\": [(\"user\", \"What's the weather in NYC?\")]}\n",
|
||||
"response = graph.invoke(inputs)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "91f34991-b406-4fd2-a776-4dd03e3dc3dd",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"You can verify that the structured response now contains a capitalized value:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"id": "ba43a67f-127c-45e7-982c-a8210d97a3ed",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"WeatherResponse(conditions='Cloudy')"
|
||||
]
|
||||
},
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"response[\"structured_response\"]"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.12.3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user