Compare commits

..
Author SHA1 Message Date
David Asamu 0c850a5baa feat: implement remote build support and archive creation for deployments
- Added a new `create_archive` function to generate a tarball of the project source, including handling of .dockerignore patterns.
- Enhanced the `deploy` function to support remote builds, allowing deployment without local Docker availability.
- Introduced a `--remote` option in the CLI to control remote build behavior.
- Updated the HostBackendClient with methods for requesting upload URLs and updating deployments with source tarballs.
2026-03-11 16:08:13 +00:00
hari-dhanushkodi 19db4d2b23 update docker build logic 2026-03-02 22:43:52 -05:00
David Asamu e6fe1a5d68 update default host backend url 2026-02-26 23:20:34 +00:00
David Asamu aceeb8e352 feat: set default host URL for CLI and simplify deployment error handling
- Added a default value for the `--host-url` option in the CLI to streamline user experience.
- Removed the explicit error check for `host_url` in the `deploy` function, allowing for a more flexible deployment process.
2026-02-26 22:24:29 +00:00
David Asamu 6c2154047a feat: add deployment type option to CLI and enhance deployment process
- Introduced a new CLI option `--deployment-type` to specify the deployment environment (dev or prod).
- Updated the `deploy` function to utilize the specified deployment type when creating a deployment.
- Improved the build process with a progress indicator for better user feedback during deployment.
2026-02-26 22:00:59 +00:00
David Asamu 817125ae4f feat: enhance CLI with environment variable handling and Docker configuration
- Added functions to parse .env files and resolve environment variables from configuration.
- Introduced a context manager for temporary Docker configuration using authentication tokens.
- Updated Docker capabilities check to include optional requirements for Docker Compose and Buildx.
- Enhanced the HostBackendClient with new methods for listing and retrieving deployments and revisions.
- Improved the Progress class to display elapsed time during tasks.
2026-02-26 16:34:08 +00:00
hari-dhanushkodi 9cda4fa11d remove subgraph 2026-02-24 23:23:27 -05:00
hari-dhanushkodi fc9c710bae feat: add langgraph deploy command 2026-02-24 23:22:19 -05:00
Lauren Hirata SinghandGitHub 8cb87eaf76 chore(docs): Update link for LangGraph guides in README (#6680)
https://langchain.slack.com/archives/C04GWPE38LV/p1768251150861949
2026-01-13 09:34:16 -05:00
Mason DaughertyandGitHub 089cdd0ffb chore: update twitter URLs (#6683) 2026-01-13 09:33:56 -05:00
Mason DaughertyandGitHub fb0091fd57 docs: restore examples/ (#6682) 2026-01-13 01:26:48 -05:00
88 changed files with 10238 additions and 45 deletions
+1 -1
View File
@@ -79,7 +79,7 @@ While LangGraph can be used standalone, it also integrates seamlessly with any L
## Additional resources
- [Guides](https://docs.langchain.com/oss/python/langgraph/guides): Quick, actionable code snippets for topics such as streaming, adding memory & persistence, and design patterns (e.g. branching, subgraphs, etc.).
- [Guides](https://docs.langchain.com/oss/python/langgraph/overview): Quick, actionable code snippets for topics such as streaming, adding memory & persistence, and design patterns (e.g. branching, subgraphs, etc.).
- [Reference](https://reference.langchain.com/python/langgraph/): Detailed reference on core classes, methods, how to use the graph and checkpointing APIs, and higher-level prebuilt components.
- [Examples](https://docs.langchain.com/oss/python/langgraph/agentic-rag): Guided examples on getting started with LangGraph.
- [LangChain Forum](https://forum.langchain.com/): Connect with the community and share all of your technical questions, ideas, and feedback.
+3
View File
@@ -0,0 +1,3 @@
# LangGraph examples
This directory should NOT be used for documentation. All new documentation must be added to `docs/docs/` directory.
+33
View File
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "23544406",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/async.ipynb"
]
}
],
"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.2"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+33
View File
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "14f7ca50",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/branching.ipynb"
]
}
],
"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.11.8"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "10251c1c",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/tutorials/chatbot-simulation-evaluation/agent-simulation-evaluation.ipynb"
]
}
],
"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.11.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "a4351a24",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/tutorials/chatbot-simulation-evaluation/langsmith-agent-simulation-evaluation.ipynb"
]
}
],
"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.11.2"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
@@ -0,0 +1,203 @@
import functools
from typing import Annotated, Any, Callable, Dict, List, Optional, Union
from langchain_community.adapters.openai import convert_message_to_dict
from langchain_core.messages import AIMessage, AnyMessage, BaseMessage, HumanMessage
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables import Runnable, RunnableLambda
from langchain_core.runnables import chain as as_runnable
from langchain_openai import ChatOpenAI
from typing_extensions import TypedDict
from langgraph.graph import END, StateGraph, START
def langchain_to_openai_messages(messages: List[BaseMessage]):
"""
Convert a list of langchain base messages to a list of openai messages.
Parameters:
messages (List[BaseMessage]): A list of langchain base messages.
Returns:
List[dict]: A list of openai messages.
"""
return [
convert_message_to_dict(m) if isinstance(m, BaseMessage) else m
for m in messages
]
def create_simulated_user(
system_prompt: str, llm: Runnable | None = None
) -> Runnable[Dict, AIMessage]:
"""
Creates a simulated user for chatbot simulation.
Args:
system_prompt (str): The system prompt to be used by the simulated user.
llm (Runnable | None, optional): The language model to be used for the simulation.
Defaults to gpt-3.5-turbo.
Returns:
Runnable[Dict, AIMessage]: The simulated user for chatbot simulation.
"""
return ChatPromptTemplate.from_messages(
[
("system", system_prompt),
MessagesPlaceholder(variable_name="messages"),
]
) | (llm or ChatOpenAI(model="gpt-3.5-turbo")).with_config(
run_name="simulated_user"
)
Messages = Union[list[AnyMessage], AnyMessage]
def add_messages(left: Messages, right: Messages) -> Messages:
if not isinstance(left, list):
left = [left]
if not isinstance(right, list):
right = [right]
return left + right
class SimulationState(TypedDict):
"""
Represents the state of a simulation.
Attributes:
messages (List[AnyMessage]): A list of messages in the simulation.
inputs (Optional[dict[str, Any]]): Optional inputs for the simulation.
"""
messages: Annotated[List[AnyMessage], add_messages]
inputs: Optional[dict[str, Any]]
def create_chat_simulator(
assistant: (
Callable[[List[AnyMessage]], str | AIMessage]
| Runnable[List[AnyMessage], str | AIMessage]
),
simulated_user: Runnable[Dict, AIMessage],
*,
input_key: str,
max_turns: int = 6,
should_continue: Optional[Callable[[SimulationState], str]] = None,
):
"""Creates a chat simulator for evaluating a chatbot.
Args:
assistant: The chatbot assistant function or runnable object.
simulated_user: The simulated user object.
input_key: The key for the input to the chat simulation.
max_turns: The maximum number of turns in the chat simulation. Default is 6.
should_continue: Optional function to determine if the simulation should continue.
If not provided, a default function will be used.
Returns:
The compiled chat simulation graph.
"""
graph_builder = StateGraph(SimulationState)
graph_builder.add_node(
"user",
_create_simulated_user_node(simulated_user),
)
graph_builder.add_node(
"assistant", _fetch_messages | assistant | _coerce_to_message
)
graph_builder.add_edge("assistant", "user")
graph_builder.add_conditional_edges(
"user",
should_continue or functools.partial(_should_continue, max_turns=max_turns),
)
# If your dataset has a 'leading question/input', then we route first to the assistant, otherwise, we let the user take the lead.
graph_builder.add_edge(START, "assistant" if input_key is not None else "user")
return (
RunnableLambda(_prepare_example).bind(input_key=input_key)
| graph_builder.compile()
)
## Private methods
def _prepare_example(inputs: dict[str, Any], input_key: Optional[str] = None):
if input_key is not None:
if input_key not in inputs:
raise ValueError(
f"Dataset's example input must contain the provided input key: '{input_key}'.\nFound: {list(inputs.keys())}"
)
messages = [HumanMessage(content=inputs[input_key])]
return {
"inputs": {k: v for k, v in inputs.items() if k != input_key},
"messages": messages,
}
return {"inputs": inputs, "messages": []}
def _invoke_simulated_user(state: SimulationState, simulated_user: Runnable):
"""Invoke the simulated user node."""
runnable = (
simulated_user
if isinstance(simulated_user, Runnable)
else RunnableLambda(simulated_user)
)
inputs = state.get("inputs", {})
inputs["messages"] = state["messages"]
return runnable.invoke(inputs)
def _swap_roles(state: SimulationState):
new_messages = []
for m in state["messages"]:
if isinstance(m, AIMessage):
new_messages.append(HumanMessage(content=m.content))
else:
new_messages.append(AIMessage(content=m.content))
return {
"inputs": state.get("inputs", {}),
"messages": new_messages,
}
@as_runnable
def _fetch_messages(state: SimulationState):
"""Invoke the simulated user node."""
return state["messages"]
def _convert_to_human_message(message: BaseMessage):
return {"messages": [HumanMessage(content=message.content)]}
def _create_simulated_user_node(simulated_user: Runnable):
"""Simulated user accepts a {"messages": [...]} argument and returns a single message."""
return (
_swap_roles
| RunnableLambda(_invoke_simulated_user).bind(simulated_user=simulated_user)
| _convert_to_human_message
)
def _coerce_to_message(assistant_output: str | BaseMessage):
if isinstance(assistant_output, str):
return {"messages": [AIMessage(content=assistant_output)]}
else:
return {"messages": [assistant_output]}
def _should_continue(state: SimulationState, max_turns: int = 6):
messages = state["messages"]
# TODO support other stop criteria
if len(messages) > max_turns:
return END
elif messages[-1].content.strip() == "FINISHED":
return END
else:
return "assistant"
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "a9014f94",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/tutorials/chatbots/information-gather-prompting.ipynb"
]
}
],
"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.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "2b789e16",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/cloud/how-tos/langgraph_to_langgraph_cloud.ipynb"
]
}
],
"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.11.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "1f2f13ca",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/tutorials/code_assistant/langgraph_code_assistant.ipynb"
]
}
],
"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.2"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
File diff suppressed because one or more lines are too long
+33
View File
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "e9a58c69",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/configuration.ipynb"
]
}
],
"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.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+33
View File
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "a1e6efeb",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/create-react-agent-hitl.ipynb"
]
}
],
"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.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+33
View File
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "1ef41a89",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/create-react-agent-memory.ipynb"
]
}
],
"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.11.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "9e2f7902",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/create-react-agent-system-prompt.ipynb"
]
}
],
"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.11.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+33
View File
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "eb07372e",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/create-react-agent.ipynb"
]
}
],
"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.11.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "a8232bc9",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/tutorials/customer-support/customer-support.ipynb"
]
}
],
"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.2"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+33
View File
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "8dbdba5b",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/tutorials/extraction/retries.ipynb"
]
}
],
"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.2"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "3ecab357",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/human_in_the_loop/wait-user-input.ipynb"
]
}
],
"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.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+33
View File
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "fc0793cb",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/input_output_schema.ipynb"
]
}
],
"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.11.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+33
View File
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "09038b53",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/tutorials/lats/lats.ipynb"
]
}
],
"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.2"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+33
View File
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "85205e97",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/tutorials/llm-compiler/LLMCompiler.ipynb"
]
}
],
"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.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+33
View File
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "42abb708",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/map-reduce.ipynb"
]
}
],
"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.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "298784f6",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/memory/add-summary-conversation-history.ipynb"
]
}
],
"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.11.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+33
View File
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "3f4370fd",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/memory/delete-messages.ipynb"
]
}
],
"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.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "6ec7cb13",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/memory/manage-conversation-history.ipynb"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"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.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "5cc8a2ad",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/tutorials/multi_agent/hierarchical_agent_teams.ipynb"
]
}
],
"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.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "d2b507b9",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/tutorials/multi_agent/multi-agent-collaboration.ipynb"
]
}
],
"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.11.2"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+33
View File
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "017a01f4",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/node-retries.ipynb"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "env",
"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.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
+33
View File
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "05f6ad0a",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/pass-config-to-tools.ipynb"
]
}
],
"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.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "8f38bec5",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/pass-run-time-values-to-tools.ipynb"
]
}
],
"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.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+33
View File
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "4da17088",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/pass_private_state.ipynb"
]
}
],
"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.11.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+33
View File
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "d16e8b9c",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/memory/add-memory.md."
]
}
],
"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.2"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+33
View File
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "78217098",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/persistence_mongodb.ipynb"
]
}
],
"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.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+33
View File
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "18526f23",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/memory/add-memory.md"
]
}
],
"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.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+33
View File
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "eee6ecdd",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/persistence_redis.ipynb"
]
}
],
"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.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "9138f92e",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/tutorials/plan-and-execute/plan-and-execute.ipynb"
]
}
],
"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.11.2"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+33
View File
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "294995c4",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/react-agent-from-scratch.ipynb"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"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.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "40f0d107",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/react-agent-structured-output.ipynb"
]
}
],
"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.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
+33
View File
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "fa3f7c50",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/recursion-limit.ipynb"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"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.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
+33
View File
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "658773a2",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/tutorials/reflection/reflection.ipynb"
]
}
],
"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.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+33
View File
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "caf07859",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/tutorials/reflexion/reflexion.ipynb"
]
}
],
"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.2"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+33
View File
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "961f43ec",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/tutorials/rewoo/rewoo.ipynb"
]
}
],
"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.11.2"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+33
View File
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "bbd6e9b8",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/run-id-langsmith.ipynb"
]
}
],
"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.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "f6db1873",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/tutorials/self-discover/self-discover.ipynb"
]
}
],
"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.2"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+33
View File
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "4149ffcc",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/state-model.ipynb"
]
}
],
"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.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+33
View File
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "3e05d7f9",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/tutorials/storm/storm.ipynb"
]
}
],
"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.2"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
+33
View File
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "e663f597",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/stream-multiple.ipynb"
]
}
],
"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.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+33
View File
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "e6829c80",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/stream-updates.ipynb"
]
}
],
"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.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+33
View File
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "5ec11895",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/stream-values.ipynb"
]
}
],
"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.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+33
View File
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "6619387c",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/streaming-content.ipynb"
]
}
],
"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.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "57b7e303",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/streaming-events-from-within-tools-without-langchain.ipynb"
]
}
],
"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.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "8e71a0c8",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/streaming-events-from-within-tools.ipynb"
]
}
],
"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.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+33
View File
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "756e4554",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/streaming-from-final-node.ipynb"
]
}
],
"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.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+33
View File
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "47164a72",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/streaming-subgraphs.ipynb"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"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.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "218dfbcb",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/streaming-tokens-without-langchain.ipynb"
]
}
],
"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.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+33
View File
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "99eb887e",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/streaming-tokens.ipynb"
]
}
],
"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.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+33
View File
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "0de7689f",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/subgraph-transform-state.ipynb"
]
}
],
"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.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
+33
View File
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "f49876e1",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/subgraph.ipynb"
]
}
],
"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.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
+33
View File
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "5106959e",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/subgraphs-manage-state.ipynb"
]
}
],
"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.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
+33
View File
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "dc21501d",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/tool-calling-errors.ipynb"
]
}
],
"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.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
+33
View File
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "7fd8bd65",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/tool-calling.ipynb"
]
}
],
"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.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
+33
View File
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "83c2223f",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/tutorials/sql-agent.ipynb"
]
}
],
"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.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+33
View File
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "11140167",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/tutorials/tnt-llm/tnt-llm.ipynb"
]
}
],
"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.11.2"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+33
View File
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "9dffdb54",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/tutorials/usaco/usaco.ipynb"
]
}
],
"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.11.2"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+33
View File
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "9c9cb15a",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/visualization.ipynb"
]
}
],
"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.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+33
View File
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "007ea2e9",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/tutorials/web-navigation/web_voyager.ipynb"
]
}
],
"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.11.2"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+1 -1
View File
@@ -20,7 +20,7 @@ dependencies = [
[project.urls]
Source = "https://github.com/langchain-ai/langgraph/tree/main/libs/checkpoint-postgres"
Twitter = "https://x.com/LangChainAI"
Twitter = "https://x.com/LangChain"
Slack = "https://www.langchain.com/join-community"
Reddit = "https://www.reddit.com/r/LangChain/"
+1 -1
View File
@@ -19,7 +19,7 @@ dependencies = [
[project.urls]
Source = "https://github.com/langchain-ai/langgraph/tree/main/libs/checkpoint-sqlite"
Twitter = "https://x.com/LangChainAI"
Twitter = "https://x.com/LangChain"
Slack = "https://www.langchain.com/join-community"
Reddit = "https://www.reddit.com/r/LangChain/"
+1 -1
View File
@@ -18,7 +18,7 @@ dependencies = [
[project.urls]
Source = "https://github.com/langchain-ai/langgraph/tree/main/libs/checkpoint"
Twitter = "https://x.com/LangChainAI"
Twitter = "https://x.com/LangChain"
Slack = "https://www.langchain.com/join-community"
Reddit = "https://www.reddit.com/r/LangChain/"
+133
View File
@@ -0,0 +1,133 @@
"""Create a tarball of project source for remote builds."""
import os
import pathlib
import tarfile
import tempfile
import click
_WARN_SIZE = 50 * 1024 * 1024 # 50 MB
_MAX_SIZE = 200 * 1024 * 1024 # 200 MB
def _tar_filter(tarinfo: tarfile.TarInfo) -> tarfile.TarInfo | None:
"""Strip symlinks, hardlinks, and traversal paths from archive."""
if tarinfo.issym() or tarinfo.islnk():
return None
if ".." in tarinfo.name.split("/"):
return None
return tarinfo
def _read_ignore_patterns(context_dir: pathlib.Path) -> list[str]:
"""Read .dockerignore patterns if present."""
dockerignore = context_dir / ".dockerignore"
if dockerignore.is_file():
patterns = []
for line in dockerignore.read_text().splitlines():
line = line.strip()
if line and not line.startswith("#"):
patterns.append(line)
return patterns
return []
def _should_ignore(rel_path: str, patterns: list[str]) -> bool:
"""Check if a relative path matches any dockerignore pattern."""
import fnmatch
rel_path = rel_path.replace(os.sep, "/")
for pattern in patterns:
negate = pattern.startswith("!")
if negate:
pattern = pattern[1:]
if fnmatch.fnmatch(rel_path, pattern) or fnmatch.fnmatch(
rel_path, f"**/{pattern}"
):
if negate:
return False
return True
parts = rel_path.split("/")
for i in range(len(parts)):
partial = "/".join(parts[: i + 1])
if fnmatch.fnmatch(partial, pattern):
if negate:
return False
return True
return False
def create_archive(
config_path: pathlib.Path,
) -> tuple[str, int]:
"""Create a .tar.gz archive of the project source.
Returns (archive_path, file_size).
The archive root is config.parent (the directory containing langgraph.json).
"""
context_dir = config_path.parent.resolve()
config_filename = config_path.name
ignore_patterns = _read_ignore_patterns(context_dir)
tmp_dir = tempfile.mkdtemp(prefix="langgraph-deploy-")
archive_path = os.path.join(tmp_dir, "source.tar.gz")
with tarfile.open(archive_path, "w:gz") as tar:
for root, dirs, files in os.walk(context_dir):
rel_root = os.path.relpath(root, context_dir)
if rel_root == ".":
rel_root = ""
dirs[:] = [
d
for d in dirs
if not _should_ignore(
os.path.join(rel_root, d) if rel_root else d, ignore_patterns
)
]
for f in files:
rel_path = os.path.join(rel_root, f) if rel_root else f
if _should_ignore(rel_path, ignore_patterns):
continue
full_path = os.path.join(root, f)
arcname = rel_path.replace(os.sep, "/")
info = tar.gettarinfo(full_path, arcname=arcname)
filtered = _tar_filter(info)
if filtered is None:
continue
with open(full_path, "rb") as fobj:
tar.addfile(filtered, fobj)
file_size = os.path.getsize(archive_path)
# Validate config file is at archive root
with tarfile.open(archive_path, "r:gz") as tar:
names = tar.getnames()
if config_filename not in names:
os.unlink(archive_path)
raise click.ClickException(
f"Archive validation failed: {config_filename} not found at archive root"
)
if file_size > _MAX_SIZE:
os.unlink(archive_path)
raise click.ClickException(
f"Source archive is {file_size / 1_048_576:.1f} MB, which exceeds the 200 MB limit. "
"Check your .dockerignore for large files (model weights, data, node_modules, .venv)."
)
if file_size > _WARN_SIZE:
click.secho(
f" Warning: source archive is {file_size / 1_048_576:.1f} MB. "
"Consider adding large files to .dockerignore.",
fg="yellow",
)
return archive_path, file_size
+811 -11
View File
@@ -1,10 +1,18 @@
"""CLI entrypoint for LangGraph API server."""
import base64
import copy
import json as json_mod
import os
import pathlib
import platform
import re
import shutil
import sys
import tempfile
import time
from collections.abc import Callable, Sequence
from contextlib import contextmanager
import click
import click.exceptions
@@ -17,11 +25,147 @@ from langgraph_cli.config import Config
from langgraph_cli.constants import DEFAULT_CONFIG, DEFAULT_PORT
from langgraph_cli.docker import DockerCapabilities
from langgraph_cli.exec import Runner, subp_exec
from langgraph_cli.host_backend import HostBackendClient, HostBackendError
from langgraph_cli.progress import Progress
from langgraph_cli.templates import TEMPLATE_HELP_STRING, create_new
from langgraph_cli.util import warn_non_wolfi_distro
from langgraph_cli.version import __version__
RESERVED_ENV_VARS = frozenset(
[
# LANGCHAIN_RESERVED_ENV_VARS from host-backend
"LANGCHAIN_TRACING_V2",
"LANGSMITH_TRACING_V2",
"LANGCHAIN_ENDPOINT",
"LANGCHAIN_PROJECT",
"LANGSMITH_PROJECT",
"LANGSMITH_LANGGRAPH_GIT_REPO",
"LANGGRAPH_GIT_REPO_PATH",
"LANGCHAIN_API_KEY",
"LANGSMITH_CONTROL_PLANE_API_KEY",
"POSTGRES_URI",
"POSTGRES_PASSWORD",
"DATABASE_URI",
"LANGSMITH_LANGGRAPH_GIT_REF",
"LANGSMITH_LANGGRAPH_GIT_REF_SHA",
"LANGGRAPH_AUTH_TYPE",
"LANGSMITH_AUTH_ENDPOINT",
"LANGSMITH_TENANT_ID",
"LANGSMITH_AUTH_VERIFY_TENANT_ID",
"LANGSMITH_HOST_PROJECT_ID",
"LANGSMITH_HOST_PROJECT_NAME",
"LANGSMITH_HOST_REVISION_ID",
"LOG_JSON",
"LOG_DICT_TRACEBACKS",
"REDIS_URI",
"LANGCHAIN_CALLBACKS_BACKGROUND",
"DD_TRACE_PSYCOPG_ENABLED",
"DD_TRACE_REDIS_ENABLED",
"LANGGRAPH_CLOUD_LICENSE_KEY",
# ALLOWED_SELF_HOSTED_ENV_VARS (rejected for non-self-hosted)
"LANGSMITH_API_KEY",
"LANGSMITH_ENDPOINT",
"POSTGRES_URI_CUSTOM",
"REDIS_URI_CUSTOM",
"PATH",
"PORT",
"MOUNT_PREFIX",
"LSD_ENV",
"LSD_DD_API_KEY",
"LSD_DD_ENDPOINT",
"LSD_DEPLOYMENT_TYPE",
]
)
_API_KEY_ENV_NAMES = (
"LANGGRAPH_HOST_API_KEY",
"LANGSMITH_API_KEY",
"LANGCHAIN_API_KEY",
)
def _parse_dotenv_file(path: pathlib.Path) -> dict[str, str]:
"""Parse a .env file into a dict, skipping comments and blank lines."""
result: dict[str, str] = {}
if not path.is_file():
return result
for line in path.read_text(encoding="utf-8").splitlines():
line = line.strip()
if not line or line.startswith("#"):
continue
if "=" not in line:
continue
key, _, value = line.partition("=")
key = key.strip()
value = value.strip()
if (
value
and len(value) >= 2
and value[0] == value[-1]
and value[0] in ("'", '"')
):
value = value[1:-1]
if key:
result[key] = value
return result
def _parse_env_from_config(
config_json: dict, config_path: pathlib.Path
) -> dict[str, str]:
"""Resolve env vars from langgraph.json 'env' field or a .env fallback."""
env_field = config_json.get("env")
if isinstance(env_field, dict):
return {str(k): str(v) for k, v in env_field.items()}
if isinstance(env_field, str):
env_path = (config_path.parent / env_field).resolve()
return _parse_dotenv_file(env_path)
fallback = pathlib.Path.cwd() / ".env"
return _parse_dotenv_file(fallback)
def _secrets_from_env(
env_vars: dict[str, str],
) -> list[dict[str, str]]:
"""Convert env dict to secrets list, filtering reserved vars with warnings."""
secrets: list[dict[str, str]] = []
for name, value in env_vars.items():
if name in RESERVED_ENV_VARS:
click.secho(f" Skipping reserved env var: {name}", fg="yellow")
continue
if not value:
continue
secrets.append({"name": name, "value": value})
return secrets
_TERMINAL_STATUSES = frozenset(
[
"DEPLOYED",
"CREATE_FAILED",
"BUILD_FAILED",
"DEPLOY_FAILED",
"SKIPPED",
]
)
@contextmanager
def _docker_config_for_token(registry_host: str, token: str):
"""Create a temporary Docker config with only the push token.
Yields the path to a temporary config directory that can be passed
to ``docker --config <path>`` so that system credential helpers
(e.g. gcloud) don't interfere with the push token.
"""
auth_b64 = base64.b64encode(f"oauth2accesstoken:{token}".encode()).decode()
config_data = {"auths": {registry_host: {"auth": auth_b64}}}
with tempfile.TemporaryDirectory() as tmpdir:
with open(os.path.join(tmpdir, "config.json"), "w") as f:
json_mod.dump(config_data, f)
yield tmpdir
OPT_DOCKER_COMPOSE = click.option(
"--docker-compose",
"-d",
@@ -304,37 +448,37 @@ def _build(
passthrough: Sequence[str] = (),
install_command: str | None = None,
build_command: str | None = None,
docker_command: Sequence[str] | None = None,
extra_flags: Sequence[str] = (),
verbose: bool = True,
):
# pull latest images
if pull:
runner.run(
subp_exec(
"docker",
"pull",
langgraph_cli.config.docker_tag(config_json, base_image, api_version),
verbose=True,
verbose=verbose,
)
)
set("Building...")
# apply options
args = [
"-f",
"-", # stdin
"-t",
tag,
]
# determine build context: use current directory for JS projects, config parent for Python
is_js_project = config_json.get("node_version") and not config_json.get(
"python_version"
)
# build/install commands only apply to JS projects for now
# without install/build command, JS projects will follow the old behavior
if is_js_project and (build_command or install_command):
build_context = str(pathlib.Path.cwd())
else:
build_context = str(config.parent)
# apply config
# Deep copy to avoid mutating the caller's config (config_to_docker
# rewrites graph paths to container-internal paths in place).
config_json = copy.deepcopy(config_json)
stdin, additional_contexts = langgraph_cli.config.config_to_docker(
config_path=config,
config=config_json,
@@ -344,19 +488,19 @@ def _build(
build_command=build_command,
build_context=build_context,
)
# add additional_contexts
if additional_contexts:
for k, v in additional_contexts.items():
args.extend(["--build-context", f"{k}={v}"])
cmd = tuple(docker_command) if docker_command else ("docker", "build")
runner.run(
subp_exec(
"docker",
"build",
*cmd,
*args,
*extra_flags,
*passthrough,
build_context,
input=stdin,
verbose=True,
verbose=verbose,
)
)
@@ -429,6 +573,662 @@ def build(
)
@click.option(
"--api-key",
envvar="LANGGRAPH_HOST_API_KEY",
help="API key. If omitted, resolved from .env or prompted.",
)
@click.option(
"--name",
help=(
"Deployment name. Defaults to current directory name "
"if --deployment-id is not provided."
),
)
@click.option(
"--deployment-id",
help=(
"ID of an existing deployment to update. If omitted, "
"--name is used to find or create the deployment."
),
)
@click.option(
"--deployment-type",
type=click.Choice(["dev", "prod"]),
default="dev",
show_default=True,
help="Deployment type (used when creating a new deployment).",
)
@click.option(
"--no-wait",
is_flag=True,
default=False,
help="Skip waiting for deployment status.",
)
@OPT_VERBOSE
@click.option(
"--host-url",
envvar="LANGGRAPH_HOST_URL",
default="https://api.host.langchain.com",
hidden=True,
)
@click.option("--image-name", hidden=True)
@click.option("--image-tag", default="latest", hidden=True)
@click.option(
"--config",
"-c",
default=DEFAULT_CONFIG,
hidden=True,
type=click.Path(
exists=True,
file_okay=True,
dir_okay=False,
resolve_path=True,
path_type=pathlib.Path,
),
)
@click.option("--pull/--no-pull", default=True, hidden=True)
@click.option("--base-image", hidden=True)
@click.option("--install-command", hidden=True)
@click.option("--build-command", hidden=True)
@click.option("--api-version", type=str, hidden=True)
@click.option(
"--remote/--no-remote",
default=None,
help=(
"Force or disable remote build. Default: auto-detect "
"(use remote build when Docker is unavailable)."
),
)
@click.argument("docker_build_args", nargs=-1, type=click.UNPROCESSED)
@cli.command(
help=(
"Build and deploy a LangGraph image to LangSmith Deployments.\n\n"
"Run from the root of your LangGraph project (where langgraph.json "
"is located). This command also accepts build flags (--base-image, "
"--pull, etc.). See 'langgraph build --help' for details."
),
context_settings=dict(ignore_unknown_options=True),
)
@log_command
def deploy(
config: pathlib.Path,
pull: bool,
verbose: bool,
api_version: str | None,
host_url: str | None,
api_key: str | None,
deployment_id: str | None,
deployment_type: str,
name: str | None,
image_name: str | None,
image_tag: str,
base_image: str | None,
install_command: str | None,
build_command: str | None,
no_wait: bool,
remote: bool | None,
docker_build_args: Sequence[str],
):
config_json = langgraph_cli.config.validate_config_file(config)
warn_non_wolfi_distro(config_json)
env_vars = _parse_env_from_config(config_json, config)
if not api_key:
for key_name in _API_KEY_ENV_NAMES:
if key_name in env_vars:
api_key = env_vars[key_name]
break
if not api_key:
api_key = click.prompt("Host API key", hide_input=True)
if not deployment_id and not name:
default_name = _normalize_image_name(pathlib.Path.cwd().name)
name = click.prompt("Deployment name", default=default_name)
secrets = _secrets_from_env(env_vars)
# Determine whether to use remote build
use_remote = remote
if use_remote is None:
docker_available = langgraph_cli.docker.is_docker_available()
use_remote = not docker_available
elif use_remote is False:
pass # --no-remote: fail if Docker is missing (existing behavior)
if use_remote:
_deploy_remote(
config=config,
config_json=config_json,
verbose=verbose,
api_version=api_version,
host_url=host_url,
api_key=api_key,
deployment_id=deployment_id,
deployment_type=deployment_type,
name=name,
base_image=base_image,
install_command=install_command,
build_command=build_command,
no_wait=no_wait,
secrets=secrets,
)
else:
_deploy_local(
config=config,
config_json=config_json,
verbose=verbose,
api_version=api_version,
host_url=host_url,
api_key=api_key,
deployment_id=deployment_id,
deployment_type=deployment_type,
name=name,
image_name=image_name,
image_tag=image_tag,
base_image=base_image,
install_command=install_command,
build_command=build_command,
no_wait=no_wait,
pull=pull,
docker_build_args=docker_build_args,
secrets=secrets,
)
def _deploy_local(
*,
config: pathlib.Path,
config_json: dict,
verbose: bool,
api_version: str | None,
host_url: str | None,
api_key: str,
deployment_id: str | None,
deployment_type: str,
name: str | None,
image_name: str | None,
image_tag: str,
base_image: str | None,
install_command: str | None,
build_command: str | None,
no_wait: bool,
pull: bool,
docker_build_args: Sequence[str],
secrets: list[dict[str, str]],
):
"""Local Docker build + push deploy path."""
needs_buildx = platform.machine() != "x86_64"
local_tag = f"langgraph-deploy-tmp:{int(time.time())}"
with Runner() as runner:
langgraph_cli.docker.check_capabilities(
runner,
require_compose=False,
require_buildx=needs_buildx,
)
def log_step(message: str) -> None:
click.secho(message, fg="cyan")
step = 1
log_step(f"{step}. Building image")
if needs_buildx:
build_flags: list[str] = [
"--platform",
"linux/amd64",
"--load",
]
if not verbose:
build_flags.append("--progress=quiet")
with Progress(message="Building...", elapsed=not verbose):
_build(
runner,
lambda _msg: None,
config,
config_json,
base_image,
api_version,
pull,
local_tag,
docker_build_args,
install_command,
build_command,
docker_command=("docker", "buildx", "build"),
extra_flags=build_flags,
verbose=verbose,
)
else:
with Progress(message="Building...", elapsed=not verbose):
_build(
runner,
lambda _msg: None,
config,
config_json,
base_image,
api_version,
pull,
local_tag,
docker_build_args,
install_command,
build_command,
verbose=verbose,
)
step += 1
client = HostBackendClient(host_url, api_key)
deployment_id = _find_or_create_deployment(
client, deployment_id, name, deployment_type, secrets, "internal_docker",
step, log_step,
)
step += 1
log_step(f"{step}. Requesting push token")
push_data = client.request_push_token(deployment_id)
deployment_token = push_data.get("token")
registry_url = push_data.get("registry_url")
if not deployment_token or not registry_url:
raise click.ClickException(
"Push token response missing token or registry_url"
)
step += 1
normalized_registry = registry_url.rstrip("/")
if "://" in normalized_registry:
normalized_registry = normalized_registry.split("//", 1)[1]
repo_seed = image_name or name or config.parent.name
repo_name = _normalize_image_name(repo_seed)
tag_value = _normalize_image_tag(image_tag)
remote_image = f"{normalized_registry}/{repo_name}:{tag_value}"
registry_host = normalized_registry.split("/")[0]
with _docker_config_for_token(registry_host, deployment_token) as cfg:
log_step(f"{step}. Logging into {registry_host}")
token_input = (
deployment_token
if deployment_token.endswith("\n")
else f"{deployment_token}\n"
)
runner.run(
subp_exec(
"docker",
"--config",
cfg,
"login",
"-u",
"oauth2accesstoken",
"--password-stdin",
registry_host,
input=token_input,
verbose=verbose,
)
)
step += 1
log_step(f"{step}. Pushing image {remote_image}")
runner.run(
subp_exec(
"docker",
"tag",
local_tag,
remote_image,
verbose=verbose,
)
)
with Progress(message="Pushing...", elapsed=not verbose):
runner.run(
subp_exec(
"docker",
"--config",
cfg,
"push",
remote_image,
verbose=verbose,
)
)
step += 1
log_step(f"{step}. Updating deployment {deployment_id}")
client.update_deployment(deployment_id, remote_image, secrets=secrets)
if no_wait:
click.secho(" Deployment updated", fg="green")
return
_poll_revision_status(client, deployment_id, verbose=verbose)
def _deploy_remote(
*,
config: pathlib.Path,
config_json: dict,
verbose: bool,
api_version: str | None,
host_url: str | None,
api_key: str,
deployment_id: str | None,
deployment_type: str,
name: str | None,
base_image: str | None,
install_command: str | None,
build_command: str | None,
no_wait: bool,
secrets: list[dict[str, str]],
):
"""Remote build deploy path (no local Docker required)."""
import urllib.request
from langgraph_cli.archive import create_archive
def log_step(message: str) -> None:
click.secho(message, fg="cyan")
step = 1
click.secho("Docker not available. Using remote build.", fg="yellow")
# -- Step: Create tarball --
log_step(f"{step}. Creating source archive")
try:
archive_path, file_size = create_archive(config)
except KeyboardInterrupt:
click.echo("\nCancelled.")
raise click.exceptions.Exit(1)
click.secho(
f" Archive created ({file_size / 1_048_576:.1f} MB)", fg="green"
)
step += 1
client = HostBackendClient(host_url, api_key)
# -- Step: Find or create deployment --
deployment_id = _find_or_create_deployment(
client, deployment_id, name, deployment_type, secrets, "internal_source",
step, log_step,
)
step += 1
# -- Step: Request upload URL --
log_step(f"{step}. Requesting upload URL")
upload_data = client.request_upload_url(deployment_id)
signed_url = upload_data.get("upload_url")
object_path = upload_data.get("object_path")
if not signed_url or not object_path:
raise click.ClickException("Upload URL response missing required fields")
step += 1
# -- Step: Upload tarball --
log_step(f"{step}. Uploading source")
try:
_upload_to_gcs(signed_url, archive_path, file_size)
except KeyboardInterrupt:
click.echo("\nUpload cancelled.")
raise click.exceptions.Exit(1)
finally:
try:
os.unlink(archive_path)
except OSError:
pass
step += 1
# -- Step: Update deployment --
log_step(f"{step}. Triggering remote build")
client.update_deployment_internal_source(
deployment_id,
source_tarball_path=object_path,
secrets=secrets,
config_path=config.name,
install_command=install_command,
build_command=build_command,
)
step += 1
if no_wait:
click.secho(" Build triggered", fg="green")
return
# -- Poll revision status with optional log streaming --
_poll_revision_status(client, deployment_id, verbose=verbose, is_remote_build=True)
def _upload_to_gcs(signed_url: str, file_path: str, file_size: int) -> None:
"""Upload tarball to GCS via signed PUT URL with progress display."""
import urllib.request
uploaded = 0
with open(file_path, "rb") as f:
original_read = f.read
def tracked_read(size=-1):
nonlocal uploaded
data = original_read(size)
if data:
uploaded += len(data)
pct = int(uploaded * 100 / file_size) if file_size else 100
click.echo(
f"\r Uploading ({file_size / 1_048_576:.1f} MB)... {pct}%",
nl=False,
)
return data
f.read = tracked_read
req = urllib.request.Request(
signed_url,
data=f,
method="PUT",
headers={
"Content-Type": "application/gzip",
"Content-Length": str(file_size),
"X-Goog-Content-Length-Range": "0,209715200",
},
)
try:
urllib.request.urlopen(req)
except urllib.error.HTTPError as err:
detail = err.read().decode("utf-8", errors="ignore")
raise click.ClickException(
f"Upload failed with status {err.code}: {detail}"
) from None
click.echo()
def _find_or_create_deployment(
client: HostBackendClient,
deployment_id: str | None,
name: str | None,
deployment_type: str,
secrets: list[dict[str, str]],
source: str,
step: int,
log_step: Callable[[str], None],
) -> str:
"""Find an existing deployment or create a new one. Returns deployment_id."""
if deployment_id:
log_step(f"{step}. Using deployment {deployment_id}")
return deployment_id
log_step(f"{step}. Looking up deployment '{name}'")
existing = client.list_deployments(name_contains=name)
found_id = None
if isinstance(existing, dict):
for dep in existing.get("resources", []):
if isinstance(dep, dict) and dep.get("name") == name:
found_id = dep.get("id")
break
if found_id:
deployment_id = str(found_id)
click.secho(
f" Found existing deployment (ID: {deployment_id})",
fg="green",
)
return deployment_id
log_step(f" Creating deployment '{name}'")
payload = {
"name": name,
"source": source,
"source_config": {"deployment_type": deployment_type},
"source_revision_config": {},
"secrets": secrets,
}
created = client.create_deployment(payload)
created_id = created.get("id") if isinstance(created, dict) else None
if not isinstance(created_id, str) or not created_id:
raise HostBackendError(
"POST /v2/deployments succeeded but response missing a valid 'id'"
)
deployment_id = created_id
click.secho(f" Deployment ID: {deployment_id}", fg="green")
return deployment_id
def _poll_revision_status(
client: HostBackendClient,
deployment_id: str,
*,
verbose: bool = False,
is_remote_build: bool = False,
) -> None:
"""Poll revision status until terminal, optionally streaming build logs."""
revisions_resp = client.list_revisions(deployment_id, limit=1)
resources = (
revisions_resp.get("resources", [])
if isinstance(revisions_resp, dict)
else []
)
if not resources:
click.secho(" Deployment updated", fg="green")
return
revision_id = str(resources[0]["id"])
last_status = ""
log_offset: str | None = None
deadline = time.time() + 900 if is_remote_build else time.time() + 300
with Progress(message="Deploying...", elapsed=True) as set_progress:
while time.time() < deadline:
try:
rev = client.get_revision(deployment_id, revision_id)
except KeyboardInterrupt:
set_progress("")
click.secho(
f"\n Interrupted. Deployment ID: {deployment_id}, "
f"Revision ID: {revision_id}",
fg="yellow",
)
click.secho(
" The build will continue remotely.",
fg="yellow",
)
raise click.exceptions.Exit(1)
status = (
rev.get("status", "UNKNOWN") if isinstance(rev, dict) else "UNKNOWN"
)
if status != last_status:
last_status = status
set_progress("")
click.secho(f" Status: {status}", fg="cyan")
if status in _TERMINAL_STATUSES:
break
set_progress(f"{status}...")
# Stream build logs when verbose and building
if (
is_remote_build
and verbose
and status in ("AWAITING_BUILD", "BUILDING")
):
try:
logs_resp = client.list_build_logs(
deployment_id, revision_id, offset=log_offset
)
if isinstance(logs_resp, dict):
for entry in logs_resp.get("logs", []):
msg = entry.get("message", "")
if msg:
click.echo(f" | {msg}")
log_offset = logs_resp.get("next_offset") or log_offset
except Exception:
pass
time.sleep(3)
else:
set_progress("")
# On BUILD_FAILED, tail the last few build log lines
if is_remote_build and last_status == "BUILD_FAILED" and not verbose:
click.secho(" Last build log lines:", fg="red")
try:
logs_resp = client.list_build_logs(
deployment_id, revision_id, order="desc", limit=30
)
if isinstance(logs_resp, dict):
entries = list(reversed(logs_resp.get("logs", [])))
for entry in entries:
msg = entry.get("message", "")
if msg:
click.echo(f" | {msg}")
except Exception:
click.secho(" (failed to fetch build logs)", fg="red")
click.secho(
" Re-run with --verbose to see full build output.",
fg="yellow",
)
dep_info = client.get_deployment(deployment_id)
custom_url = None
if isinstance(dep_info, dict):
sc = dep_info.get("source_config")
if isinstance(sc, dict):
custom_url = sc.get("custom_url")
if last_status == "DEPLOYED":
click.secho(" Deployment successful!", fg="green")
if custom_url:
click.secho(f" URL: {custom_url}", fg="green")
elif last_status in ("BUILD_FAILED", "DEPLOY_FAILED", "CREATE_FAILED"):
click.secho(f" Deployment failed: {last_status}", fg="red")
raise click.exceptions.Exit(1)
else:
click.secho(
f" Timed out waiting for deployment (last status: {last_status}).",
fg="yellow",
)
if custom_url:
click.secho(
f" Check status at: {custom_url}",
fg="yellow",
)
else:
click.secho(
" Check status in the LangSmith Deployments dashboard.",
fg="yellow",
)
def _normalize_image_name(value: str | None) -> str:
if not value:
return "app"
slug = re.sub(r"[^a-z0-9._-]+", "-", value.lower()).strip("-.")
return slug or "app"
def _normalize_image_tag(value: str) -> str:
if not value:
value = "latest"
if not re.fullmatch(r"[A-Za-z0-9_.-]+", value):
raise click.UsageError(
"Image tag may only contain characters A-Z, a-z, 0-9, '_', '-', '.'"
)
return value
def _get_docker_ignore_content() -> str:
"""Return the content of a .dockerignore file.
+45 -18
View File
@@ -24,9 +24,9 @@ DockerComposeType = Literal["plugin", "standalone"]
class DockerCapabilities(NamedTuple):
version_docker: Version
version_compose: Version
version_compose: Version | None
healthcheck_start_interval: bool
compose_type: DockerComposeType = "plugin"
compose_type: DockerComposeType | None = None
def _parse_version(version: str) -> Version:
@@ -45,7 +45,26 @@ def _parse_version(version: str) -> Version:
)
def check_capabilities(runner) -> DockerCapabilities:
def is_docker_available() -> bool:
"""Check if Docker is installed and running without raising."""
if shutil.which("docker") is None:
return False
try:
import subprocess
result = subprocess.run(
["docker", "info"],
capture_output=True,
timeout=10,
)
return result.returncode == 0
except Exception:
return False
def check_capabilities(
runner, *, require_compose: bool = True, require_buildx: bool = False
) -> DockerCapabilities:
# check docker available
if shutil.which("docker") is None:
raise click.UsageError("Docker not installed") from None
@@ -61,25 +80,33 @@ def check_capabilities(runner) -> DockerCapabilities:
if not info["ServerVersion"]:
raise click.UsageError("Docker not running") from None
compose_type: DockerComposeType
try:
compose = next(
p for p in info["ClientInfo"]["Plugins"] if p["Name"] == "compose"
)
compose_version_str = compose["Version"]
compose_type = "plugin"
except (KeyError, StopIteration):
if shutil.which("docker-compose") is None:
raise click.UsageError("Docker Compose not installed") from None
compose_type: DockerComposeType | None = None
compose_version: Version | None = None
if require_compose:
try:
compose = next(
p for p in info["ClientInfo"]["Plugins"] if p["Name"] == "compose"
)
compose_version_str = compose["Version"]
compose_type = "plugin"
except (KeyError, StopIteration):
if shutil.which("docker-compose") is None:
raise click.UsageError("Docker Compose not installed") from None
compose_version_str, _ = runner.run(
subp_exec("docker-compose", "--version", "--short", collect=True)
)
compose_type = "standalone"
compose_version_str, _ = runner.run(
subp_exec("docker-compose", "--version", "--short", collect=True)
)
compose_type = "standalone"
compose_version = _parse_version(compose_version_str)
if require_buildx:
try:
runner.run(subp_exec("docker", "buildx", "version", collect=True))
except click.exceptions.Exit:
raise click.UsageError("Docker Buildx not installed") from None
# parse versions
docker_version = _parse_version(info["ServerVersion"])
compose_version = _parse_version(compose_version_str)
# check capabilities
return DockerCapabilities(
+163
View File
@@ -0,0 +1,163 @@
"""HTTP client for LangGraph host backend deployments."""
from __future__ import annotations
import json
import urllib.error
import urllib.parse
import urllib.request
from typing import Any
import click
class HostBackendError(click.ClickException):
"""Raised when the host backend returns an error response."""
class HostBackendClient:
"""Minimal JSON HTTP client for the host backend deployment service."""
def __init__(self, base_url: str, api_key: str):
if not base_url:
raise click.UsageError("Host backend URL is required")
base_url = base_url.rstrip("/")
self._base_url = base_url
self._api_key = api_key
def _request(
self, method: str, path: str, payload: dict[str, Any] | None = None
) -> Any:
url = f"{self._base_url}{path}"
data: bytes | None
if payload is not None:
data = json.dumps(payload).encode("utf-8")
else:
data = None
headers: dict[str, str] = {
"X-Api-Key": self._api_key,
"Accept": "application/json",
}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
try:
with urllib.request.urlopen(req, timeout=30) as resp:
body = resp.read()
except urllib.error.HTTPError as err:
detail = err.read().decode("utf-8", errors="ignore")
message = detail or err.reason
raise HostBackendError(
f"{method} {path} failed with status {err.code}: {message}"
) from None
except urllib.error.URLError as err:
raise HostBackendError(str(err.reason)) from None
if not body:
return None
try:
return json.loads(body)
except json.JSONDecodeError as err:
raise HostBackendError(
f"Failed to decode response from {path}: {err.msg}"
) from None
def create_deployment(self, payload: dict[str, Any]) -> dict[str, Any]:
return self._request("POST", "/v2/deployments", payload)
def list_deployments(self, name_contains: str) -> dict[str, Any]:
encoded = urllib.parse.quote(name_contains, safe="")
return self._request("GET", f"/v2/deployments?name_contains={encoded}")
def get_deployment(self, deployment_id: str) -> dict[str, Any]:
return self._request("GET", f"/v2/deployments/{deployment_id}")
def request_push_token(self, deployment_id: str) -> dict[str, Any]:
return self._request(
"POST",
f"/v2/deployments/{deployment_id}/push-token",
)
def update_deployment(
self,
deployment_id: str,
image_uri: str,
secrets: list[dict[str, str]] | None = None,
) -> dict[str, Any]:
payload: dict[str, Any] = {
"source_revision_config": {"image_uri": image_uri},
}
if secrets is not None:
payload["secrets"] = secrets
return self._request(
"PATCH",
f"/v2/deployments/{deployment_id}",
payload,
)
def list_revisions(self, deployment_id: str, limit: int = 1) -> dict[str, Any]:
return self._request(
"GET",
f"/v2/deployments/{deployment_id}/revisions?limit={limit}",
)
def get_revision(self, deployment_id: str, revision_id: str) -> dict[str, Any]:
return self._request(
"GET",
f"/v2/deployments/{deployment_id}/revisions/{revision_id}",
)
def request_upload_url(self, deployment_id: str) -> dict[str, Any]:
"""Get a signed GCS URL for uploading the source tarball."""
return self._request(
"POST",
f"/v2/deployments/{deployment_id}/upload-url",
)
def update_deployment_internal_source(
self,
deployment_id: str,
source_tarball_path: str,
secrets: list[dict[str, str]] | None = None,
config_path: str | None = None,
install_command: str | None = None,
build_command: str | None = None,
) -> dict[str, Any]:
"""Trigger a remote build revision with the uploaded tarball."""
src_config: dict[str, Any] = {
"source_tarball_path": source_tarball_path,
}
if config_path is not None:
src_config["langgraph_config_path"] = config_path
payload: dict[str, Any] = {"source_revision_config": src_config}
source_config: dict[str, Any] = {}
if install_command is not None:
source_config["install_command"] = install_command
if build_command is not None:
source_config["build_command"] = build_command
if source_config:
payload["source_config"] = source_config
if secrets is not None:
payload["secrets"] = secrets
return self._request("PATCH", f"/v2/deployments/{deployment_id}", payload)
def list_build_logs(
self,
deployment_id: str,
revision_id: str,
order: str = "asc",
limit: int = 50,
offset: str | None = None,
) -> dict[str, Any]:
"""Fetch build logs for a revision."""
payload: dict[str, Any] = {"order": order, "limit": limit}
if offset:
payload["offset"] = offset
return self._request(
"POST",
f"/v1/projects/{deployment_id}/revisions/{revision_id}/build_logs",
payload,
)
+24 -8
View File
@@ -12,8 +12,11 @@ class Progress:
while True:
yield from "|/-\\"
def __init__(self, *, message=""):
def __init__(self, *, message="", elapsed: bool = False):
self.message = message
self._base_message = message
self._show_elapsed = elapsed
self._stop = threading.Event()
self.spinner_generator = self.spinning_cursor()
def spinner_iteration(self):
@@ -21,7 +24,6 @@ class Progress:
sys.stdout.write(next(self.spinner_generator) + " " + message)
sys.stdout.flush()
time.sleep(self.delay)
# clear the spinner and message
sys.stdout.write(
"\b" * (len(message) + 2)
+ " " * (len(message) + 2)
@@ -29,13 +31,26 @@ class Progress:
)
sys.stdout.flush()
def _format_elapsed(self, seconds: float) -> str:
mins, secs = divmod(int(seconds), 60)
if mins:
return f"{self._base_message} ({mins}m {secs:02d}s)"
return f"{self._base_message} ({secs}s)"
def spinner_task(self):
while self.message:
start = time.monotonic()
while not self._stop.is_set():
if not self.message:
time.sleep(self.delay)
continue
if self._show_elapsed:
self.message = self._format_elapsed(time.monotonic() - start)
message = self.message
if not message:
continue
sys.stdout.write(next(self.spinner_generator) + " " + message)
sys.stdout.flush()
time.sleep(self.delay)
# clear the spinner and message
sys.stdout.write(
"\b" * (len(message) + 2)
+ " " * (len(message) + 2)
@@ -50,21 +65,22 @@ class Progress:
def set_message(message):
self.message = message
if not message:
self.thread.join()
self._base_message = message or self._base_message
return set_message
else:
def set_message(message):
sys.stderr.write(message + "\n")
sys.stderr.flush()
if message:
sys.stderr.write(message + "\n")
sys.stderr.flush()
return set_message
def __exit__(self, exception, value, tb):
if sys.stdout.isatty():
self.message = ""
self._stop.set()
try:
self.thread.join()
finally:
+1 -1
View File
@@ -26,7 +26,7 @@ inmem = [
[project.urls]
Source = "https://github.com/langchain-ai/langgraph/tree/main/libs/cli"
Twitter = "https://x.com/LangChainAI"
Twitter = "https://x.com/LangChain"
Slack = "https://www.langchain.com/join-community"
Reddit = "https://www.reddit.com/r/LangChain/"
+1 -1
View File
@@ -38,7 +38,7 @@ Homepage = "https://docs.langchain.com/oss/python/langgraph/overview"
Documentation = "https://reference.langchain.com/python/langgraph/"
Source = "https://github.com/langchain-ai/langgraph/tree/main/libs/langgraph"
Changelog = "https://github.com/langchain-ai/langgraph/releases"
Twitter = "https://x.com/LangChainAI"
Twitter = "https://x.com/LangChain"
Slack = "https://www.langchain.com/join-community"
Reddit = "https://www.reddit.com/r/LangChain/"
+1 -1
View File
@@ -30,7 +30,7 @@ dependencies = [
[project.urls]
Source = "https://github.com/langchain-ai/langgraph/tree/main/libs/prebuilt"
Twitter = "https://x.com/LangChainAI"
Twitter = "https://x.com/LangChain"
Slack = "https://www.langchain.com/join-community"
Reddit = "https://www.reddit.com/r/LangChain/"
+1 -1
View File
@@ -18,7 +18,7 @@ path = "langgraph_sdk/__init__.py"
[project.urls]
Source = "https://github.com/langchain-ai/langgraph/tree/main/libs/sdk-py"
Twitter = "https://x.com/LangChainAI"
Twitter = "https://x.com/LangChain"
Slack = "https://www.langchain.com/join-community"
Reddit = "https://www.reddit.com/r/LangChain/"