Compare commits

...
25 Commits
Author SHA1 Message Date
Nuno Campos 307c0cfe09 sdkpy 0.1.25 2024-06-26 15:31:57 -07:00
Nuno CamposandGitHub c6466da098 sdk: Filter assistants by graph_id (#843) 2024-06-26 15:23:08 -07:00
Nuno Campos 32e8a2dc50 sdk py 0.1.24 2024-06-26 14:45:12 -07:00
Nuno CamposandGitHub 8d103667c8 sdk: Add support to filter threads by status (#842) 2024-06-26 14:44:27 -07:00
Isaac FranciscoandGitHub 2657ebb57c expose webhook option to sdk (#812) 2024-06-26 12:13:14 -07:00
Isaac FranciscoandGitHub 33d099989b replace threadless with stateless (#821) 2024-06-26 12:12:29 -07:00
Nuno Campos 29a040dc21 cli 0.1.45a0 2024-06-26 11:55:03 -07:00
Nuno CamposandGitHub 5697f07163 cli: Reduce to test and build commands (#838)
* cli: Reduce to test and build commands

* Bump timeout

* Fix test?

* Fix

* Fix output
2024-06-26 11:53:44 -07:00
Andrew NguonlyandGitHub fb5f3c972a Update openapi.json spec to include POST /runs endpoint. (#837) 2024-06-26 11:29:58 -07:00
Vadym BardaandGitHub 86c556d01e cli: release 0.1.43 (#836) 2024-06-26 13:45:35 -04:00
Vadym BardaandGitHub b7580abf45 cli: update CAT -> RUN in the dockerfile (#816) 2024-06-26 13:43:34 -04:00
Vadym BardaandGitHub 72ac58d1c2 docs: update persistence in the how-tos (#833) 2024-06-26 13:43:23 -04:00
Vadym BardaandGitHub 000a5c5b83 docs: update graph state in readme (#835) 2024-06-26 13:43:14 -04:00
Isaac FranciscoandGitHub 6caea79fba webhook (#810) 2024-06-26 10:41:29 -07:00
Nuno Campos fae97d9fba cli0.1.42 2024-06-26 09:37:25 -07:00
Nuno CamposandGitHub d16ec4f436 cli: Make base image configurable in build command (#831)
* cli: Make base image configurable in build command

* Lint

* Fix
2024-06-26 09:36:09 -07:00
William FHandGitHub 66b728e83a [Docs] Cleanup links in nb (#828) 2024-06-26 00:20:20 -07:00
William FHandGitHub feca5e1970 [Docs] Restore Linkcheck (#824)
Plus:
1. Improve docstrings of add_node
2. Update crosslinking of sqlite and aiosqlite docstrings
3. Fix a bunch of links so we can turn on strict validation
2024-06-26 00:04:13 -07:00
Jacob LeeandGitHub c217e4a58d Update intro to same thread example, update conceptual docs to include checkpoint (#827) 2024-06-25 22:20:50 -07:00
Harrison ChaseandGitHub fcdf7a8ced add links from concepts guide (#825) 2024-06-25 22:07:06 -07:00
Jacob LeeandGitHub 720ea986d3 Fix misaligned code block (#823) 2024-06-25 21:25:01 -07:00
ab54ae2c23 Harrison/tutorial (#820)
* first draft edits (#818)

Co-authored-by: Harrison Chase <hw.chase.17@gmail.com>

* cr

* cr

---------

Co-authored-by: Isaac Francisco <78627776+isahers1@users.noreply.github.com>
2024-06-25 20:46:01 -07:00
Jacob LeeandGitHub a5650e1d88 Style nits in quickstart (#819)
* Style nits in quickstart

CC @andrewnguonly

* Missing quote

* Update quick_start.md
2024-06-25 19:44:19 -07:00
Jacob LeeandGitHub e49f3f5434 Fix typo (#817)
CC @andrewnguonly
2024-06-25 19:24:20 -07:00
William FHandGitHub f9c720f25a Rm git committers (#815) 2024-06-25 17:09:26 -07:00
52 changed files with 933 additions and 1358 deletions
+4 -28
View File
@@ -30,7 +30,6 @@ jobs:
with:
python-version: ${{ matrix.python-version }}
poetry-version: ${{ env.POETRY_VERSION }}
working-directory: libs/cli
cache-key: integration-test-cli
- name: Setup env
if: steps.changed-files.outputs.all
@@ -42,42 +41,19 @@ jobs:
- name: Start service A
if: steps.changed-files.outputs.all
run: |
langgraph up -c examples/langgraph.json --wait --verbose
- name: Stop service A
if: steps.changed-files.outputs.all
run: |
langgraph down -c examples/langgraph.json
sudo rm -rf .langgraph-data
timeout 60 langgraph test -c examples/langgraph.json --verbose || (exit "$(($? == 124 ? 0 : $?))")
- name: Start service B
if: steps.changed-files.outputs.all
working-directory: libs/cli/examples/graphs
run: |
langgraph up --wait --verbose
- name: Stop service B
if: steps.changed-files.outputs.all
working-directory: libs/cli/examples/graphs
run: |
langgraph down
sudo rm -rf .langgraph-data
timeout 60 langgraph test --verbose || (exit "$(($? == 124 ? 0 : $?))")
- name: Start service C
if: steps.changed-files.outputs.all
working-directory: libs/cli/examples/graphs_reqs_a
run: |
langgraph up --wait -d compose.yml --verbose
- name: Stop service C
if: steps.changed-files.outputs.all
working-directory: libs/cli/examples/graphs_reqs_a
run: |
langgraph down
sudo rm -rf .langgraph-data
timeout 60 langgraph test --verbose || (exit "$(($? == 124 ? 0 : $?))")
- name: Start service D
if: steps.changed-files.outputs.all
working-directory: libs/cli/examples/graphs_reqs_b
run: |
langgraph up --wait -d compose.yml --verbose
- name: Stop service D
if: steps.changed-files.outputs.all
working-directory: libs/cli/examples/graphs_reqs_b
run: |
langgraph down
sudo rm -rf .langgraph-data
timeout 60 langgraph test --verbose || (exit "$(($? == 124 ? 0 : $?))")
+29 -18
View File
@@ -26,10 +26,10 @@ jobs:
- name: Check links in Markdown files
uses: gaurav-nelson/github-action-markdown-link-check@v1
with:
folder-path: 'examples/'
folder-path: "examples/"
check-modified-files-only: ${{ github.event_name != 'schedule' }}
file-path: './README.md'
config-file: './.markdown-link-check.config.json'
file-path: "./README.md"
config-file: "./.markdown-link-check.config.json"
notebook-link-check:
runs-on: ubuntu-latest
@@ -49,18 +49,29 @@ jobs:
poetry install --with docs
poetry run pip install -U pytest pytest-check-links langsmith langchain GitPython
# - name: Check links in notebooks
# env:
# LANGCHAIN_API_KEY: test
# run: |
# if [ "${{ github.event_name }}" != "schedule" ]; then
# git fetch origin main
# CHANGED_FILES=$(git diff --name-only origin/main | grep '\.ipynb$')
# if [ -n "$CHANGED_FILES" ]; then
# poetry run pytest -o python_files=non_python_only --check-links --check-links-ignore "https://(api|web)\.smith\.langchain\.com/.*" --check-links-ignore "https://x.com/.*" $CHANGED_FILES
# else
# echo "No notebook files changed."
# fi
# else
# poetry run pytest -o python_files=non_python_only --check-links --ignore="*.py" -k .ipynb --check-links-ignore "https://(api|web)\.smith\.langchain\.com/.*" --check-links-ignore "https://x.com/.*" ./examples
# fi
- name: Check links in notebooks
env:
LANGCHAIN_API_KEY: test
run: |
if [ "${{ github.event_name }}" == "schedule" ] || [ "${{ github.event_name }}" == "workflow_dispatch" ] || ([ "${{ github.event_name }}" == "push" ] && [ "${{ github.ref }}" == "refs/heads/main" ]); then
echo "Running link check on all notebooks in examples directory..."
poetry run pytest -v --check-links-ignore "https://(api|web)\.smith\.langchain\.com/.*" --check-links-ignore "https://x.com/.*" --check-links examples
else
echo "Fetching changes from origin/main..."
git fetch origin main
echo "Checking for changed notebook files..."
CHANGED_FILES=$(git diff --name-only origin/main | grep '\.ipynb$' || true)
echo "Changed files: ${CHANGED_FILES}"
if [ -n "${CHANGED_FILES}" ]; then
echo "Running link check on changed notebook files..."
poetry run pytest -v --check-links-ignore "https://(api|web)\.smith\.langchain\.com/.*" --check-links-ignore "https://x.com/.*" --check-links ${CHANGED_FILES}
PYTEST_EXIT_CODE=$?
echo "pytest exit code: ${PYTEST_EXIT_CODE}"
if [ ${PYTEST_EXIT_CODE} -ne 0 ]; then
echo "pytest failed with exit code ${PYTEST_EXIT_CODE}"
exit ${PYTEST_EXIT_CODE}
fi
else
echo "No notebook files changed."
fi
fi
+2 -2
View File
@@ -72,7 +72,7 @@ tool_node = ToolNode(tools)
model = ChatOpenAI(temperature=0).bind_tools(tools)
# Define the function that determines whether to continue or not
def should_continue(state: AgentState) -> Literal["tools", END]:
def should_continue(state: MessagesState) -> Literal["tools", END]:
messages = state['messages']
last_message = messages[-1]
# If the LLM makes a tool call, then we route to the "tools" node
@@ -83,7 +83,7 @@ def should_continue(state: AgentState) -> Literal["tools", END]:
# Define the function that calls the model
def call_model(state: AgentState):
def call_model(state: MessagesState):
messages = state['messages']
response = model.invoke(messages)
# We return a list, because this will get added to the existing list
+55 -9
View File
@@ -1,4 +1,6 @@
import json
import os
import re
import shutil
from pathlib import Path
@@ -77,6 +79,20 @@ _HIDE = set(
"rag/langgraph_rag_agent_llama3_local.ipynb",
"rag/langgraph_self_rag_pinecone_movies.ipynb",
"rag/langgraph_adaptive_rag_cohere.ipynb",
"dynamically-returning-directly.ipynb",
"force-calling-a-tool-first.ipynb",
"managing-agent-steps.ipynb",
"pass-run-time-values-to-tools.ipynb",
"respond-in-format.ipynb",
"quickstart.ipynb",
"human-in-the-loop.ipynb",
"learning.ipynb",
"managing-conversation-history.ipynb",
"docs/quickstart.ipynb",
"tutorials/rag-agent-testing.ipynb",
"state-context-key.ipynb",
"time-travel.ipynb",
"code_assistant/langgraph_code_assistant_mistral.ipynb",
]
)
@@ -98,6 +114,44 @@ def clean_notebooks():
os.rmdir(root)
def update_notebook_links(notebook_path):
with open(notebook_path, "r", encoding="utf-8") as f:
notebook = json.load(f)
for cell in notebook["cells"]:
if cell["cell_type"] == "markdown":
for i, source in enumerate(cell["source"]):
# Update relative notebook links
cell["source"][i] = re.sub(
r"\[([^\]]+)\]\(([^:)]+\.ipynb)\)",
lambda m: transform_link(m.group(1), m.group(2)),
source,
)
with open(notebook_path, "w", encoding="utf-8") as f:
json.dump(notebook, f, indent=2)
def transform_link(text, link):
dir_path, filename = os.path.split(link)
# Remove the .ipynb extension
filename_without_ext = os.path.splitext(filename)[0]
# If it's a local link (starts with ./)
if link.startswith("./"):
# Change to parent directory and remove ./ prefix
new_link = f"../{filename_without_ext}/"
elif dir_path:
# If there's a directory path, keep it and add one more level up
new_link = f"../{dir_path}/{filename_without_ext}/"
else:
# If it's just a filename, simply go one level up
new_link = f"../{filename_without_ext}/"
return f"[{text}]({new_link})"
def copy_notebooks():
# Nested ones are mostly tutorials rn
for root, dirs, files in os.walk(examples_dir):
@@ -148,17 +202,9 @@ def copy_notebooks():
content = content.replace('src=\\"./img/', 'src=\\"../img/')
with open(dst_path, "w") as f:
f.write(content)
update_notebook_links(dst_path)
dst_dir = dst_dir_
# Top level notebooks are "how-to's"
# for file in examples_dir.iterdir():
# if file.suffix.endswith(".ipynb") and not os.path.isdir(
# os.path.join(examples_dir, file)
# ):
# src_path = os.path.join(examples_dir, file)
# dst_path = os.path.join(docs_dir, "how-tos", file.name)
# shutil.copy(src_path, dst_path)
if __name__ == "__main__":
clean_notebooks()
+31 -17
View File
@@ -1,20 +1,29 @@
# API Concepts
This page describes the high-level concepts of the LangGraph Cloud API. The conceptual guide of LangGraph (Python library) is [here](../../concepts/index.md).
## Data Models
The LangGraph Cloud API consists of a few core data models: [Assistants](#assistants), [Threads](#threads), [Runs](#runs), and [Cron Jobs](#cron-jobs).
### Assistants
An assistant is a configured instance of a [`CompiledGraph`](../../../reference/graphs/#compiledgraph). It abstracts the cognitive architecture of the graph and contains instance specific configuration and metadata. Multiple assistants can reference the same graph but can contain different configuration and metadata, which may differentiate the behavior of the assistants. An assistant (i.e. the graph) is invoked as part of a run.
An assistant is a configured instance of a [`CompiledGraph`][compiledgraph]. It abstracts the cognitive architecture of the graph and contains instance specific configuration and metadata. Multiple assistants can reference the same graph but can contain different configuration and metadata, which may differentiate the behavior of the assistants. An assistant (i.e. the graph) is invoked as part of a run.
The LangGraph Cloud API provides several endpoints for creating and managing assistants. See the <a href="../reference/api/api_ref.html#tag/assistantscreate" target="_blank">API reference</a> for more details.
### Threads
A thread contains the accumulated state of a group of runs. If a run is executed on a thread, then the [state](../../../concepts/#persistence) of the underlying graph of the assistant will be persisted to the thread. A thread's current and historical state can be retrieved. To persist state, a thread must be created prior to executing a run.
A thread contains the accumulated state of a group of runs. If a run is executed on a thread, then the [state][state] of the underlying graph of the assistant will be persisted to the thread. A thread's current and historical state can be retrieved. To persist state, a thread must be created prior to executing a run.
The state of a thread at a particular point in time is called a checkpoint.
For more on threads and checkpoints, see this section of the [LangGraph conceptual guide](https://langchain-ai.github.io/langgraph/concepts/low_level/#checkpointer).
The LangGraph Cloud API provides several endpoints for creating and managing threads and thread state. See the <a href="../reference/api/api_ref.html#tag/threadscreate" target="_blank">API reference</a> for more details.
### Runs
A run is an invocation of an assistant. Each run may have its own input, configuration, and metadata, which may affect execution and output of the underlying graph. A run can optionally be executed on a thread.
The LangGraph Cloud API provides several endpoints for creating and managing runs. See the <a href="../reference/api/api_ref.html#tag/runscreate" target="_blank">API reference</a> for more details.
@@ -26,36 +35,40 @@ It's often useful to run graphs on some schedule. LangGraph Cloud supports cron
- Create a new thread with the specified assistant
- Send the specified input to that thread
Note that this sends the same input to the thread every time. See the [How-to Guide](../how-tos/cloud_examples/cron_jobs/) for creating cron jobs.
Note that this sends the same input to the thread every time. See the [how-to guide](../how-tos/cloud_examples/cron_jobs.ipynb) for creating cron jobs.
The LangGraph Cloud API provides several endpoints for creating and managing cron jobs. See the <a href="../reference/api/api_ref.html#tag/runscreate/POST/threads/{thread_id}/runs/crons" target="_blank">API reference</a> for more details.
## Features
The LangGraph Cloud API offers several features to support complex agent architectures.
### Streaming
Streaming is critical for making LLM applications feel responsive to end users. When creating a streaming run, the streaming mode determines what data is streamed back to the API client. The LangGraph Cloud API supports five streaming modes.
- `values`: Stream the full state of the graph after each node is executed. See the [How-to Guide](../how-tos/cloud_examples/stream_values/) for streaming values.
- `messages`: Stream complete messages (at the end of node execution) as well as tokens for any messages generated inside a node. This mode is primarily meant for powering chat applications. This is only an option if your graph contains a `messages` key. See the [How-to Guide](../how-tos/cloud_examples/stream_messages/) for streaming messages.
- `updates`: Streams updates to the state of the graph after each node is executed. See the [How-to Guide](../how-tos/cloud_examples/stream_updates/) for streaming updates.
- `events`: Stream all events (including the state of the graph) after each node is executed. See the [How-to Guide](../how-tos/cloud_examples/stream_events/) for streaming events. This can be used to do token-by-token streaming for LLMs.
- `debug`: Stream debug events after each node is executed. See the [How-to Guide](../how-tos/cloud_examples/stream_debug/) for streaming debug events.
Streaming is critical for making LLM applications feel responsive to end users. When creating a streaming run, the streaming mode determines what data is streamed back to the API client. The LangGraph Cloud API supports five streaming modes.
You can also specify multiple streaming modes at the same time. See the [How-to Guide](../how-tos/cloud_examples/stream_multiple/) for configuring multiple streaming modes at the same time.
- `values`: Stream the full state of the graph after each node is executed. See the [how-to guide](../how-tos/cloud_examples/stream_values.ipynb) for streaming values.
- `messages`: Stream complete messages (at the end of node execution) as well as tokens for any messages generated inside a node. This mode is primarily meant for powering chat applications. This is only an option if your graph contains a `messages` key. See the [how-to guide](../how-tos/cloud_examples/stream_messages.ipynb) for streaming messages.
- `updates`: Streams updates to the state of the graph after each node is executed. See the [how-to guide](../how-tos/cloud_examples/stream_updates.ipynb) for streaming updates.
- `events`: Stream all events (including the state of the graph) after each node is executed. See the [how-to guide](../how-tos/cloud_examples/stream_events.ipynb) for streaming events. This can be used to do token-by-token streaming for LLMs.
- `debug`: Stream debug events after each node is executed. See the [how-to guide](../how-tos/cloud_examples/stream_debug.ipynb) for streaming debug events.
You can also specify multiple streaming modes at the same time. See the [how-to guide](../how-tos/cloud_examples/stream_multiple.ipynb) for configuring multiple streaming modes at the same time.
See the <a href="../reference/api/api_ref.html#tag/runscreate/POST/threads/{thread_id}/runs/stream" target="_blank">API reference</a> for how to create streaming runs.
### Human-in-the-Loop
There are many occasions where the graph cannot run completely autonomously. For instance, the user might need to input some additional arguments to a function call, or select the next edge for the graph to continue on. In these instances, we need to insert some human in the loop interaction, which you can learn about in the [human in the loop how-tos](../how-tos/cloud_examples/human_in_the_loop_breakpoint).
There are many occasions where the graph cannot run completely autonomously. For instance, the user might need to input some additional arguments to a function call, or select the next edge for the graph to continue on. In these instances, we need to insert some human in the loop interaction, which you can learn about in the [human in the loop how-tos](../how-tos/cloud_examples/human_in_the_loop_breakpoint.ipynb).
### Double Texting
Many times users might interact with your graph in unintended ways. For instance, a user may send one message and before the graph has finished running send a second message. To solve this issue of "double-texting" (i.e. prompting the graph a second time before the first run has finished), Langgraph has provided four different solutions, all of which are covered in the [Double Texting how-tos](../how-tos/cloud_examples/interrupt_concurrent/). These options are:
- `reject`: This is the simplest option, this just rejects any follow up runs and does not allow double texting. See the [How-to Guide](../how-tos/cloud_examples/reject_concurrent/) for configuring the reject double text option.
- `enqueue`: This is a relatively simple option which continues the first run until it completes the whole run, then sends the new input as a separate run. See the [How-to Guide](../how-tos/cloud_examples/enqueue_concurrent/) for configuring the enqueue double text option.
- `interrupt`: This option interrupts the current execution but saves all the work done up until that point. It then inserts the user input and continues from there. If you enable this option, your graph should be able to handle weird edge cases that may arise. See the [How-to Guide](../how-tos/cloud_examples/interrupt_concurrent/) for configuring the interrupt double text option.
- `rollback`: This option rolls back all work done up until that point. It then sends the user input in, basically as if it just followed the original run input. See the [How-to Guide](../how-tos/cloud_examples/rollback_concurrent/) for configuring the rollback double text option.
Many times users might interact with your graph in unintended ways. For instance, a user may send one message and before the graph has finished running send a second message. To solve this issue of "double-texting" (i.e. prompting the graph a second time before the first run has finished), Langgraph has provided four different solutions, all of which are covered in the [Double Texting how-tos](../how-tos/cloud_examples/interrupt_concurrent.ipynb). These options are:
- `reject`: This is the simplest option, this just rejects any follow up runs and does not allow double texting. See the [how-to guide](../how-tos/cloud_examples/reject_concurrent.ipynb) for configuring the reject double text option.
- `enqueue`: This is a relatively simple option which continues the first run until it completes the whole run, then sends the new input as a separate run. See the [how-to guide](../how-tos/cloud_examples/enqueue_concurrent.ipynb) for configuring the enqueue double text option.
- `interrupt`: This option interrupts the current execution but saves all the work done up until that point. It then inserts the user input and continues from there. If you enable this option, your graph should be able to handle weird edge cases that may arise. See the [how-to guide](../how-tos/cloud_examples/interrupt_concurrent.ipynb) for configuring the interrupt double text option.
- `rollback`: This option rolls back all work done up until that point. It then sends the user input in, basically as if it just followed the original run input. See the [how-to guide](../how-tos/cloud_examples/rollback_concurrent.ipynb) for configuring the rollback double text option.
### Stateless Runs
@@ -69,10 +82,11 @@ All runs use the built-in checkpointer to store checkpoints for runs. However, i
Stateless runs are still retried as regular retries are per node, while everything still in memory, so doesn't use checkpoints.
The only difference is in stateless background runs, if the task worker dies halfway (not because the run itself failed, for some external reason) then the whole run will be retried like any background run, but
- whereas a stateful background run would retry from the last successful checkpoint
- a stateless background run would retry from the beginning
See the [How-to Guide](../how-tos/cloud_examples/stateless_runs/) for creating stateless runs.
See the [how-to guide](../how-tos/cloud_examples/stateless_runs.ipynb) for creating stateless runs.
## Deployment
+1 -1
View File
@@ -29,7 +29,7 @@ Starting from the <a href="https://smith.langchain.com/" target="_blank">LangSmi
## Create New Revision
When [creating a new deployment](#create-a-new-deployment), a new revision is created by default. Subsequent revisions can be created to deploy new code changes.
When [creating a new deployment](#create-new-deployment), a new revision is created by default. Subsequent revisions can be created to deploy new code changes.
Starting from the <a href="https://smith.langchain.com/" target="_blank">LangSmith UI</a>...
Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 59 KiB

+1 -1
View File
@@ -39,7 +39,7 @@ my-app/
## Define Graphs
Implement your graphs! Graphs can be defined in a single file or multiple files. Make note of the variable names of each [CompiledGraph](../../../reference/graphs/#compiledgraph) to be included in the LangGraph application. The variable names will be used later when creating the [LangGraph API configuration file](../reference/cli.md#configuration-file).
Implement your graphs! Graphs can be defined in a single file or multiple files. Make note of the variable names of each [CompiledGraph][compiledgraph] to be included in the LangGraph application. The variable names will be used later when creating the [LangGraph API configuration file](../reference/cli.md#configuration-file).
Example `openai_agent.py` file:
```python
Binary file not shown.

After

Width:  |  Height:  |  Size: 405 KiB

+3 -3
View File
@@ -8,7 +8,7 @@
## Overview
LangGraph Cloud is a managed service for deploying and hosting LangGraph applications. Deploying applications with LangGraph Cloud shortens the time-to-market for developers. With one click, deploy a production-ready API with built-in persistence for your LangGraph application. LangGraph Cloud APIs are horizatonally scalable and deployed with durable storage.
LangGraph Cloud is a managed service for deploying and hosting LangGraph applications. Deploying applications with LangGraph Cloud shortens the time-to-market for developers. With one click, deploy a production-ready API with built-in persistence for your LangGraph application. LangGraph Cloud APIs are horizontally scalable and deployed with durable storage.
The LangGraph Cloud API exposes functionality of your LangGraph application through [Assistants](./concepts/index.md#assistants). An assistant abstracts the cognitive architecture of your graph. Invoke an assistant by calling the pre-built [API endpoints](./reference/api/api_ref.md).
@@ -28,6 +28,6 @@ The LangGraph Cloud API supports key LangGraph features in addition to new funct
## Documentation
- [Tutorials](./quick_start.md): Learn to build and deploy applications for LangGraph Cloud.
- [How-to Guides](./deployment/setup/): Learn how to set up a LangGraph application for deployment and implement features of the LangGraph Cloud API such as streaming tokens, configuring double texting, and creating cron jobs. Go here if you want to copy and run a specific code snippet.
- [Conceptual Guides](./concepts/): In-depth explanations of the core data models (e.g. assistants) and key features (e.g. double texting) of the LangGraph Cloud API.
- [How-to Guides](./deployment/setup.md): Learn how to set up a LangGraph application for deployment and implement features of the LangGraph Cloud API such as streaming tokens, configuring double texting, and creating cron jobs. Go here if you want to copy and run a specific code snippet.
- [Conceptual Guides](./concepts/index.md): In-depth explanations of the core data models (e.g. assistants) and key features (e.g. double texting) of the LangGraph Cloud API.
- [Reference](./reference/api/api_ref.md): References for the LangGraph Cloud API, the corresponding Python and JS/TS SDKs, the LangGraph CLI, and deployment environment variables.
+105 -103
View File
@@ -1,7 +1,6 @@
# Quick Start
This quick start guide will cover how to develop an application for LangGraph Cloud, run it locally in Docker, and call the APIs to invoke a graph.
This quick start guide will cover how to build a simple chatbot with LangGraph, deploy it to LangGraph Cloud, use the LangGraph Studio to visualize and test it out, and use the LangGraph Cloud SDK to interact with it.
Alternatively, clone or fork the [`langgraph/example`](https://github.com/langchain-ai/langgraph-example) GitHub repository and follow the instructions in the `README`.
## Set up local files
@@ -13,29 +12,35 @@ Alternatively, clone or fork the [`langgraph/example`](https://github.com/langch
|-- langgraph.json # configuration file for LangGraph
|-- .env # environment files with API keys
2. The `agent.py` file should contain Python code for defining your graph. The following code is a simple example, the important thing is that at somepoint in your file you compile your graph and assign that Runnable to a variable (in this case the `graph` variable).
2. The `agent.py` file should contain Python code for defining your graph. The following code is a simple example, the important thing is that at some point in your file you compile your graph and assign the compiled graph to a variable (in this case the `graph` variable).
```python
from langchain_openai import ChatOpenAI
from langgraph.graph import END, MessageGraph
model = ChatOpenAI(temperature=0)
graph_workflow = MessageGraph()
graph_workflow.add_node("agent", model)
from langchain_anthropic import ChatAnthropic
from langgraph.graph import END, StateGraph, MessagesState
model = ChatAnthropic(model="claude-3-5-sonnet-20240620")
graph_workflow = StateGraph(MessagesState)
def agent(state: MessagesState):
response = model.invoke(state["messages"])
return {"messages": [response]}
graph_workflow.add_node(agent)
graph_workflow.add_edge("agent", END)
graph_workflow.set_entry_point("agent")
graph = graph_workflow.compile()
```
3. The `requirements.txt` file should contain any dependencies for your graph(s). In this case we only require two packages for our graph to run:
langgraph
langchain_openai
langchain_anthropic
4. The `langgraph.json` file is a configuration file that describes what graph you are going to host. It is important to note that you can host multiple graphs at a time. In this case we only host one: the `graph` Runnable from `agent.py`, but we could have defined multiple different graphs within `agent.py` to host, or written multiple python files to each host one or more graphs. Each graph you wish to host should have a unique identifier.
4. The `langgraph.json` file is a configuration file that describes what graph(s) you are going to host. In this case we only have one graph to host: the compiled `graph` object from `agent.py`.
```json
{
@@ -49,100 +54,25 @@ Alternatively, clone or fork the [`langgraph/example`](https://github.com/langch
Learn more about the LangGraph CLI configuration file [here](./reference/cli.md#configuration-file).
5. The `.env` file should contain the environment variables that are needed to run your graph. In this case we just need to specify the OpenAI API key, as well as the authentication type for langgraph.
OPENAI_API_KEY=<add your key here>
LANGGRAPH_AUTH_TYPE=noop
!!! warning "Disable Authentication"
When testing locally, set `LANGGRAPH_AUTH_TYPE` to `noop` to disable authentication.
Now that we have set everything up on our local file system, we are ready to host our graph.
## Run Locally
1. Install the [LangGraph CLI](./reference/cli.md#installation).
2. Run the following command to start the API server in Docker:
langgraph up -c langgraph.json
3. The API server is now running at `http://localhost:8123`. Navigate to [`http://localhost:8123/docs`](http://localhost:8123/docs) to view the API docs.
4. You can now test that your deployment is working as intended by invoking some of the cURL commands from the API docs.
First, let's test that the assistant we are hosting is indeed retrievable by the API, which we can do by using the "assistants/search" endpoint:
curl --request POST \
--url http://localhost:8123/assistants/search \
--header 'Content-Type: application/json' \
--data '{
"metadata": {},
"limit": 10,
"offset": 0
}
If the hosting is working as expected, you should receive a 200 response which looks something like this example response:
[
{
"assistant_id": "123e4567-e89b-12d3-a456-426614174000",
"graph_id": "agent",
"config": {
"tags": [
"…"
],
"recursion_limit": 1,
"configurable": {}
},
"created_at": "2024-06-24T19:21:47.514Z",
"updated_at": "2024-06-24T19:21:47.514Z",
"metadata": {}
}
]
If you are hosting multiple graphs, you should see all of them in this response. Once you have verified that this step is working as intended, you can test out that invoking your hosted graphs works as intended without any bugs. You can do this by calling the a version of the following cURL command:
curl --request POST \
--url http://localhost:8123/runs/stream \
--header 'Content-Type: application/json' \
--data '{
"assistant_id": "123e4567-e89b-12d3-a456-426614174000",
"input": {
"messages": [
{
"role": "user",
"content": "How are you?"
}
]
},
"metadata": {},
"config": {
"configurable": {}
},
"multitask_strategy": "reject",
"stream_mode": [
"values"
]
}'
Make sure to edit the `input` and `assistant_id` fields to match what assistant you want to test. If you receive a 200 response then congratulations your graph has run successfully and you are ready to move on to hosting on Langgraph Cloud!
## Deploy to Cloud
### Push your code to GitHub
Create a git repo in the `<my-app>` directory, and verify its existence. You can use the GitHub CLI if you like, or just create a repo manually.
Turn the `<my-app>` directory into a GitHub repo. You can use the GitHub CLI if you like, or just create a repo manually (if unfamiliar, instructions [here](https://docs.github.com/en/migrations/importing-source-code/using-the-command-line-to-import-source-code/adding-locally-hosted-code-to-github)).
### Deploy from GitHub with LangGraph Cloud
Once you have created your github repository with a Python file containing your compiled graph as well as a `langgraph.json` file containing the configuration for hosting your graph, you can head over to LangSmith and click on the 🚀 icon on the left navbar to create a new deployment. Click the `+ New Deployment` button.
Once you have created your github repository with a Python file containing your compiled graph as well as a `langgraph.json` file containing the configuration for hosting your graph, you can head over to LangSmith and click on the 🚀 icon on the left navbar to create a new deployment. Then click the `+ New Deployment` button.
![Langsmith Workflow](./img/cloud_deployment.png)
***If you have not deployed to LangGraph Cloud before:*** there will be a button that shows up saying Import from GitHub. Youll need to follow that flow to connect LangGraph Cloud to GitHub.
***Once you have set up your GitHub connection:*** the new deployment page will look as follows
***Once you have set up your GitHub connection:*** the new deployment page will look as follows:
![Screenshot 2024-06-11 at 1.17.03PM.png](./deployment/img/deployment_page.png)
![Deployment before being filled out](./deployment/img/deployment_page.png)
To deploy your application, you should do the following:
@@ -155,7 +85,7 @@ To deploy your application, you should do the following:
Putting this all together, you should have something as follows for your deployment details:
![Screenshot 2024-06-11 at 1.21.52PM.png](./deployment/img/deploy_filled_out.png)
![Deployment filled out](./deployment/img/deploy_filled_out.png)
Hit `Submit` and your application will start deploying!
@@ -165,7 +95,7 @@ Hit `Submit` and your application will start deploying!
After your deployment is complete, your deployments page should look as follows:
![Screenshot 2024-06-11 at 2.03.34PM.png](./deployment/img/deployed_page.png)
![Deployed page](./deployment/img/deployed_page.png)
You can see that by default, you get access to the `Trace Count` monitoring chart and `Recent Traces` run view. These are powered by LangSmith.
@@ -175,20 +105,92 @@ You can click on `All Charts` to view all monitoring info for your server, or cl
You can access the docs by clicking on the API docs link, which should send you to a page that looks like this:
![Screenshot 2024-06-19 at 2.27.24PM.png](./deployment/img/api_page.png)
![API Docs page](./deployment/img/api_page.png)
You wont actually be able to test any of the API endpoints without authorizing first. To do so, click on the Authorize button in the top right corner, input your `LANGCHAIN_API_KEY` in the `API Key` box, and then click `Authorize` to finish the process. You should now be able to select any of the API endpoints, click `Try it out` , enter the parameters you would like to pass, and then click `Execute` to view the results of the API call.
You wont actually be able to test any of the API endpoints without authorizing first. To do so, click on the Authorize button in the top right corner, input your `LANGCHAIN_API_KEY` in the `API Key` box, and then click `Authorize` to finish the process. You should now be able to select any of the API endpoints, click `Try it out`, enter the parameters you would like to pass, and then click `Execute` to view the results of the API call.
## Interact with your deployment via LangGraph Studio
### Access Studio
If you click on your deployment you should see a blue button in the top right that says `LangGraph Studio`. Clicking on this button will take you to a page that looks like this:
![Screenshot 2024-06-11 at 2.51.51PM.png](./deployment/img/graph_visualization.png)
![Studio UI before being run](./deployment/img/graph_visualization.png)
On this page you can test out your graph by passing in starting states and clicking `Start Run` (this should behave identically to calling `.invoke`). You will then be able to look into the execution thread for each run and explore the steps your graph is taking to produce its output.
![Studio UI once being run](./deployment/img/graph_run.png)
## Use with the SDK
Once you have tested that your hosted graph works as expected using Langgraph Studio, you can start using your hosted graph all over your organization by using the Langgraph SDK. You can learn about how to do that by following [this how-to guide](./sdk/python_sdk.ipynb)
Once you have tested that your hosted graph works as expected using LangGraph Studio, you can start using your hosted graph all over your organization by using the LangGraph SDK. Let's see how we can access our hosted graph and execute our run from a python file.
First, make sure you have the SDK installed by calling `pip install langgraph_sdk`.
Before using, you need to get the URL of your LangGraph deployment. You can find this on the auto generated documentation page here:
![Base URL of LangGraph deployment](./deployment/img/base_url.png)
You also need to make sure you have set up your API key properly so you can authenticate with LangGraph Cloud.
```shell
export LANGCHAIN_API_KEY=...
```
The first thing to do when using the SDK is to setup our client, access our assistant, and create a thread to execute a run on:
```python
from langgraph_sdk import get_client
# Replace this with the URL of your own deployed graph
URL = "https://chatbot-23a570f3210f52a7b167f09f6158e3b3-ffoprvkqsa-uc.a.run.app"
client = get_client(url=URL)
# Search all hosted graphs
assistants = await client.assistants.search()
# In this example we select the first assistant since we are only hosting a single graph
assistant = assistants[0]
# We create a thread for tracking the state of our run
thread = await client.threads.create()
```
We can then execute a run on the thread:
```python
input = {"messages":[{"role": "user", "content": "Hello! My name is Bagatur and I am 26 years old."}]}
async for chunk in client.runs.stream(
thread['thread_id'],
assistant["assistant_id"],
input=input,
stream_mode="updates",
):
if chunk.data and "run_id" not in chunk.data:
print(chunk.data)
```
{'agent': {'messages': [{'content': "Hi Bagatur! It's nice to meet you. How can I assist you today?", 'additional_kwargs': {}, 'response_metadata': {'finish_reason': 'stop', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_9cb5d38cf7'}, 'type': 'ai', 'name': None, 'id': 'run-c89118b7-1b1e-42b9-a85d-c43fe99881cd', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}
You can learn more about the Python SDK in [this how-to guide](./sdk/python_sdk.ipynb), and read up on the Javascript SDK in [this how-to guide](./sdk/js_sdk.ipynb)
## What's Next
Congratulations! If you've worked your way through this tutorial you are well on your way to becoming a LangGraph Cloud expert. Here are some other resources to check out to help you out on the path to expertise:
### LangGraph Cloud How-tos
If you want to learn more about streaming from hosted graphs, check out the Streaming [how-to guides](https://langchain-ai.github.io/langgraph/cloud/how-tos/cloud_examples/stream_values/).
To learn more about double-texting and all the ways you can handle it in your application, read up on these [how-to guides](https://langchain-ai.github.io/langgraph/cloud/how-tos/cloud_examples/interrupt_concurrent/).
To learn about how to include different human-in-the-loop behavior in your graph, take a look at [these how-tos](https://langchain-ai.github.io/langgraph/cloud/how-tos/cloud_examples/human_in_the_loop_breakpoint/).
### LangGraph Tutorials
Before hosting, you have to write a graph to host. Here are some tutorials to get you more comfortable with writing LangGraph graphs and give you inspiration for the types of graphs you want to host.
[This tutorial](https://langchain-ai.github.io/langgraph/tutorials/customer-support/customer-support/) walks you through how to write a customer support bot using LangGraph.
If you are interested in writing a SQL agent, check out [this tutorial](https://langchain-ai.github.io/langgraph/tutorials/sql-agent/).
Check out the [LangGraph tutorials](https://langchain-ai.github.io/langgraph/tutorials/) page to read about more exciting use cases.
@@ -1543,6 +1543,46 @@
}
}
},
"/runs": {
"post": {
"tags": [
"runs/create"
],
"summary": "Run Stateless",
"description": "Create a stateless run that will run in the background.",
"operationId": "run_stateless_runs_post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RunCreate"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/runs/crons/{cron_id}": {
"delete": {
"tags": [
+2 -2
View File
@@ -3,8 +3,8 @@ The LangGraph CLI includes commands to build and run a LangGraph Cloud API serve
## Installation
1. Ensure that Docker is installed (e.g. `docker --version`).
1. Install the `langgraph-cli` Python package (e.g. `pip install langgraph-cli`).
1. Run the command `langgraph --help` to confirm that the CLI is installed.
2. Install the `langgraph-cli` Python package (e.g. `pip install langgraph-cli`).
3. Run the command `langgraph --help` to confirm that the CLI is installed.
## Configuration File
The LangGraph CLI requires a JSON configuration file with the following keys:
+19 -7
View File
@@ -16,17 +16,21 @@ Since LangGraph nodes can be arbitrary Python functions, you can do this however
Memory is a key concept to agentic applications. Memory is important because end users often expect the application they are interacting with remember previous interactions. The most simple example of this is chatbots - they clearly need to remember previous messages in a conversation.
LangGraph is perfectly suited to give you full control over the memory of your application. With user defined [`State`](#state) you can specify the exact schema of the memory you want to retain. With [checkpointers](#checkpointer) you can store checkpoints of previous interactions and resume from there in follow up interactions.
LangGraph is perfectly suited to give you full control over the memory of your application. With user defined [`State`](./low_level.md#state) you can specify the exact schema of the memory you want to retain. With [checkpointers](./low_level.md#checkpointer) you can store checkpoints of previous interactions and resume from there in follow up interactions.
See [this guide](../how-tos/persistence.ipynb) for how to add memory to your graph.
## Human-in-the-loop
Agentic systems often require some human-in-the-loop (or "on-the-loop") interaction patterns. This is because agentic systems are still not super reliable, so having a human involved is required for any sensitive tasks/actions. These are all easily enabled in LangGraph, largely due to [checkpointers](#checkpointer). The reason a checkpointer is necessary is that a lot of these interaction patterns involve running a graph up until a certain point, waiting for some sort of human feedback, and then continuing. When you want to "continue" you will need to access the state of the graph previous to getting interrupted, and checkpointers are a built in, highly convenient way to do that.
Agentic systems often require some human-in-the-loop (or "on-the-loop") interaction patterns. This is because agentic systems are still not super reliable, so having a human involved is required for any sensitive tasks/actions. These are all easily enabled in LangGraph, largely due to [checkpointers](./low_level.md#checkpointer). The reason a checkpointer is necessary is that a lot of these interaction patterns involve running a graph up until a certain point, waiting for some sort of human feedback, and then continuing. When you want to "continue" you will need to access the state of the graph previous to getting interrupted, and checkpointers are a built in, highly convenient way to do that.
There are a few common human-in-the-loop interaction patterns we see emerging.
### Approval
A basic one is to have the agent wait for approval before executing certain tools. This may be all tools, or just a subset of tools. This is generally recommend for more sensitive actions (like writing to a database). This can easily be done in LangGraph by setting a [breakpoint](#breakpoints) before specific nodes.
A basic one is to have the agent wait for approval before executing certain tools. This may be all tools, or just a subset of tools. This is generally recommend for more sensitive actions (like writing to a database). This can easily be done in LangGraph by setting a [breakpoint](./low_level.md#breakpoints) before specific nodes.
See [this guide](../how-tos/human_in_the_loop/breakpoints.ipynb) for how do this in LangGraph.
### Wait for input
@@ -38,21 +42,27 @@ A similar one is to have the agent wait for human input. This can be done by:
4. Update the state with that user input, acting as that node
5. Resume execution
See [this guide](../how-tos/human_in_the_loop/wait-user-input.ipynb) for how do this in LangGraph.
### Edit agent actions
This is a more advanced interaction pattern. In this interaction pattern the human can actually edit some of the agent's previous decisions. This can be done either during the flow (after a [breakpoint](#breakpoints), part of the [approval](#approval) flow) or after the fact (as part of [time-travel](#time-travel))
This is a more advanced interaction pattern. In this interaction pattern the human can actually edit some of the agent's previous decisions. This can be done either during the flow (after a [breakpoint](./low_level.md#breakpoints), part of the [approval](#approval) flow) or after the fact (as part of [time-travel](#time-travel))
See [this guide](../how-tos/human_in_the_loop/edit-graph-state.ipynb) for how do this in LangGraph.
### Time travel
This is a pretty advanced interaction pattern. In this interaction pattern, the human can look back at the list of previous checkpoints, find one they like, optionally [edit it](#edit-agent-actions), and then resume execution from there.
See [this guide](../how-tos/human_in_the_loop/time-travel.ipynb) for how to do this in LangGraph.
## Map-Reduce
A common pattern in agents is to generate a list of objects, do some work on each of those objects, and then combine the results. This is very similar to the common [map-reduce](https://en.wikipedia.org/wiki/MapReduce) operation. This can be tricky for a few reasons. First, it can be tough to define a structured graph ahead of time because the length of the list of objects may be unknown. Second, in order to do this map-reduce you need multiple versions of the state to exist... but the graph shares a common shared state, so how can this be?
LangGraph supports this via the [Send](#send) api. This can be used to allow a conditional edge to Send multiple different states to multiple nodes. The state it sends can be different from the state of the core graph.
LangGraph supports this via the [Send](./low_level.md#send) api. This can be used to allow a conditional edge to Send multiple different states to multiple nodes. The state it sends can be different from the state of the core graph.
See a how-to guide for this [here](https://langchain-ai.github.io/langgraph/how-tos/map-reduce/)
See a how-to guide for this [here](../how-tos/map-reduce.ipynb)
## Multi-agent
@@ -76,7 +86,7 @@ This "reflection" step often uses an LLM, but doesn't have to. A good example of
One of the most common agent architectures is what is commonly called the ReAct agent architecture. In this architecture, an LLM is called repeatedly in a while-loop. At each step the agent decides which tools to call, and what the inputs to those tools should be. Those tools are then executed, and the outputs are fed back into the LLM as observations. The while-loop terminates when the agent decides it is not worth calling any more tools.
One of the few high level, pre-built agents we have in LangGraph - you can use it with [`create_react_agent`](https://langchain-ai.github.io/langgraph/reference/prebuilt/#create_react_agent)
One of the few high level, pre-built agents we have in LangGraph - you can use it with [`create_react_agent`](../reference/prebuilt.md#create_react_agent)
This is named after and based on the [ReAct](https://arxiv.org/abs/2210.03629) paper. However, there are several differences between this paper and our implementation:
@@ -85,3 +95,5 @@ This is named after and based on the [ReAct](https://arxiv.org/abs/2210.03629) p
- Third, the paper required all inputs to the tools to be a single string. This was largely due to LLMs not being super capable at the time, and only really being able to generate a single input. Our implementation allows for using tools that require multiple inputs.
- Forth, the paper only looks at calling a single tool at the time, largely due to limitations in LLMs performance at the time. Our implementation allows for calling multiple tools at a time.
- Finally, the paper asked the LLM to explicitly generate a "Thought" step before deciding which tools to call. This is the "Reasoning" part of "ReAct". Our implementation does not do this by default, largely because LLMs have gotten much better and that is not as necessary. Of course, if you wish to prompt it do so, you certainly can.
See [this guide](../how-tos/human_in_the_loop/time-travel.ipynb) for a full walkthrough of how to use the prebuilt ReAct agent.
+9 -9
View File
@@ -17,18 +17,18 @@ If these decisions are being made in a loop, then its even more agentic!
There are other concepts often associated with being agentic, but we would argue these are a by-product of the above definition:
- Tool calling: this is often how LLMs make decisions
- [Tool calling](agentic_concepts.md#tool-calling): this is often how LLMs make decisions
- Action taking: often times, the LLMs' outputs are used as the input to an action
- Memory: reliable systems need to have knowledge of things that occurred
- Planning: planning steps (either explicit or implicit) are useful for ensuring that the LLM, when making decisions, makes them in the highest fidelity way.
- [Memory](agentic_concepts.md#memory): reliable systems need to have knowledge of things that occurred
- [Planning](agentic_concepts.md#planning): planning steps (either explicit or implicit) are useful for ensuring that the LLM, when making decisions, makes them in the highest fidelity way.
## Why LangGraph?
LangGraph has several core principles that we believe make it the most suitable framework for building agentic applications:
- Controllability
- Human-in-the-Loop
- Streaming First
- [Controllability](../how-tos/index.md#controllability)
- [Human-in-the-Loop](../how-tos/index.md#human-in-the-loop)
- [Streaming First](../how-tos/index.md#streaming)
**Controllability**
@@ -40,7 +40,7 @@ LangGraph comes with a built-in persistence layer as a first-class concept. This
**Streaming First**
LangGraph comes with first class support for streaming. Agentic applications often take a while to run, and so giving the user some idea of what is happening is important, and streaming is a great way to do that. LangGraph supports streaming of both events (like a tool call being taken) as well as of tokens that an LLM may emit.
LangGraph comes with first class support for streaming. Agentic applications often take a while to run, and so giving the user some idea of what is happening is important, and streaming is a great way to do that. LangGraph supports streaming of both events ([like a tool call being taken](../how-tos/stream-updates.ipynb)) as well as of [tokens that an LLM may emit](../how-tos/streaming-tokens.ipynb).
## Deployment
@@ -48,8 +48,8 @@ So you've built your LangGraph object - now what?
Now you need to deploy it.
There are many ways to deploy LangGraph objects, and the right solution depends on your needs and use case.
We'll highlight two ways here: using [LangGraph Cloud](/langgraph/cloud) or rolling your own solution.
We'll highlight two ways here: using [LangGraph Cloud](../cloud/index.md) or rolling your own solution.
[LangGraph Cloud](/langgraph/cloud) is an opinionated way to deploy LangGraph objects from the LangChain team. Please see the [LangGraph Cloud documentation](/langgraph/cloud) for all the details about what it involves, to see if it is a good fit for you.
[LangGraph Cloud](../cloud/index.md) is an opinionated way to deploy LangGraph objects from the LangChain team. Please see the [LangGraph Cloud documentation](../cloud/index.md) for all the details about what it involves, to see if it is a good fit for you.
If it is not a good fit, you may want to roll your own deployment. In this case, we would recommend using [FastAPI](https://fastapi.tiangolo.com/) to stand up a server. You can then call this graph from inside the FastAPI server as you see fit.
+42 -42
View File
@@ -7,51 +7,51 @@ There are three main parts to this concept guide. First, we'll discuss at a very
LangGraph for Agentic Applications
- [What does it mean to be agentic?](high_level#what-does-it-mean-to-be-agentic)
- [Why LangGraph](high_level#why-langgraph)
- [Deployment](high_level#deployment)
- [What does it mean to be agentic?](high_level.md#what-does-it-mean-to-be-agentic)
- [Why LangGraph](high_level.md#why-langgraph)
- [Deployment](high_level.md#deployment)
Low Level Concepts
- [Graphs](low_level#graphs)
- [StateGraph](low_level#stategraph)
- [MessageGraph](low_level#messagegraph)
- [Compiling Your Graph](low_level#compiling-your-graph)
- [State](low_level#state)
- [Schema](low_level#schema)
- [Reducers](low_level#reducers)
- [MessageState](low_level#messagestate)
- [Nodes](low_level#nodes)
- [`START` node](low_level#start-node)
- [`END` node](low_level#end-node)
- [Edges](low_level#edges)
- [Normal Edges](low_level#normal-edges)
- [Conditional Edges](low_level#conditional-edges)
- [Entry Point](low_level#entry-point)
- [Conditional Entry Point](low_level#conditional-entry-point)
- [Send](low_level#send)
- [Checkpointer](low_level#checkpointer)
- [Threads](low_level#threads)
- [Checkpointer states](low_level#checkpointer-state)
- [Get state](low_level#get-state)
- [Get state history](low_level#get-state-history)
- [Update state](low_level#update-state)
- [Configuration](low_level#configuration)
- [Visualization](low_level#visualization)
- [Streaming](low_level#streaming)
- [Graphs](low_level.md#graphs)
- [StateGraph](low_level.md#stategraph)
- [MessageGraph](low_level.md#messagegraph)
- [Compiling Your Graph](low_level.md#compiling-your-graph)
- [State](low_level.md#state)
- [Schema](low_level.md#schema)
- [Reducers](low_level.md#reducers)
- [MessageState](low_level.md#messagestate)
- [Nodes](low_level.md#nodes)
- [`START` node](low_level.md#start-node)
- [`END` node](low_level.md#end-node)
- [Edges](low_level.md#edges)
- [Normal Edges](low_level.md#normal-edges)
- [Conditional Edges](low_level.md#conditional-edges)
- [Entry Point](low_level.md#entry-point)
- [Conditional Entry Point](low_level.md#conditional-entry-point)
- [Send](low_level.md#send)
- [Checkpointer](low_level.md#checkpointer)
- [Threads](low_level.md#threads)
- [Checkpointer states](low_level.md#checkpointer-state)
- [Get state](low_level.md#get-state)
- [Get state history](low_level.md#get-state-history)
- [Update state](low_level.md#update-state)
- [Configuration](low_level.md#configuration)
- [Visualization](low_level.md#visualization)
- [Streaming](low_level.md#streaming)
Common Agentic Patterns
- [Structured output](agentic_concepts#structured-output)
- [Tool calling](agentic_concepts#tool-calling)
- [Memory](agentic_concepts#memory)
- [Human in the loop](agentic_concepts#human-in-the-loop)
- [Approval](agentic_concepts#approval)
- [Wait for input](agentic_concepts#wait-for-input)
- [Edit agent actions](agentic_concepts#edit-agent-actions)
- [Time travel](agentic_concepts#time-travel)
- [Map-Reduce](agentic_concepts#map-reduce)
- [Multi-agent](agentic_concepts#multi-agent)
- [Planning](agentic_concepts#planning)
- [Reflection](agentic_concepts#reflection)
- [Off-the-shelf ReAct Agent](agentic_concepts#react-agent)
- [Structured output](agentic_concepts.md#structured-output)
- [Tool calling](agentic_concepts.md#tool-calling)
- [Memory](agentic_concepts.md#memory)
- [Human in the loop](agentic_concepts.md#human-in-the-loop)
- [Approval](agentic_concepts.md#approval)
- [Wait for input](agentic_concepts.md#wait-for-input)
- [Edit agent actions](agentic_concepts.md#edit-agent-actions)
- [Time travel](agentic_concepts.md#time-travel)
- [Map-Reduce](agentic_concepts.md#map-reduce)
- [Multi-agent](agentic_concepts.md#multi-agent)
- [Planning](agentic_concepts.md#planning)
- [Reflection](agentic_concepts.md#reflection)
- [Off-the-shelf ReAct Agent](agentic_concepts.md#react-agent)
+37 -29
View File
@@ -4,11 +4,11 @@
At its core, LangGraph models agent workflows as graphs. You define the behavior of your agents using three key components:
1. `State`: A shared data structure that represents the current snapshot of your application. It can be any Python type, but is typically a `TypedDict` or Pydantic `BaseModel`.
1. [`State`](#state): A shared data structure that represents the current snapshot of your application. It can be any Python type, but is typically a `TypedDict` or Pydantic `BaseModel`.
2. `Nodes`: Python functions that encode the logic of your agents. They receive the current `State` as input, perform some computation or side-effect, and return an updated `State`.
2. [`Nodes`](#nodes): Python functions that encode the logic of your agents. They receive the current `State` as input, perform some computation or side-effect, and return an updated `State`.
3. `Edges`: Python functions that determine which `Node` to execute next based on the current `State`. They can be conditional branches or fixed transitions.
3. [`Edges`](#edges): Python functions that determine which `Node` to execute next based on the current `State`. They can be conditional branches or fixed transitions.
By composing `Nodes` and `Edges`, you can create complex, looping workflows that evolve the `State` over time. The real power, though, comes from how LangGraph manages that `State`. To emphasize: `Nodes` and `Edges` are nothing more than Python functions - they can contain an LLM or just good ol' Python code.
@@ -38,11 +38,11 @@ You **MUST** compile your graph before you can use it.
## State
The first thing you do when you define a graph is define the `State` of the graph. The `State` consists of the schema of graph as well as `reducer` functions which specify how to apply updates to the state. The schema of the `State` will be the input schema to all `Nodes` and `Edges` in the graph, and can be either a `TypedDict` or a `Pydantic` model. All `Nodes` will emit updates to the `State` which are then applied using the specified `reducer` function.
The first thing you do when you define a graph is define the `State` of the graph. The `State` consists of the [schema of the graph](#schema) as well as [`reducer` functions](#reducers) which specify how to apply updates to the state. The schema of the `State` will be the input schema to all `Nodes` and `Edges` in the graph, and can be either a `TypedDict` or a `Pydantic` model. All `Nodes` will emit updates to the `State` which are then applied using the specified `reducer` function.
### Schema
The main documented way to specify the schema of a graph is by using `TypedDict`. However, we also support [using a Pydantic BaseModel](https://langchain-ai.github.io/langgraph/how-tos/state-model/) as your graph state to add **default values** and additional data validation.
The main documented way to specify the schema of a graph is by using `TypedDict`. However, we also support [using a Pydantic BaseModel](../how-tos/state-model.ipynb) as your graph state to add **default values** and additional data validation.
### Reducers
@@ -60,7 +60,6 @@ class State(TypedDict):
In this example, no reducer functions are specified for any key. Let's assume the input to the graph is `{"foo": 1, "bar": ["hi"]}`. Let's then assume the first `Node` returns `{"foo": 2}`. This is treated as an update to the state. Notice that the `Node` does not need to return the whole `State` schema - just an update. After applying this update, the `State` would then be `{"foo": 2, "bar": ["hi"]}`. If the second node returns `{"bar": ["bye"]}` then the `State` would then be `{"foo": 2, "bar": ["bye"]}`
**Example B:**
```python
@@ -74,7 +73,6 @@ class State(TypedDict):
In this example, we've used the `Annotated` type to specify a reducer function (`operator.add`) for the second key (`bar`). Note that the first key remains unchanged. Let's assume the input to the graph is `{"foo": 1, "bar": ["hi"]}`. Let's then assume the first `Node` returns `{"foo": 2}`. This is treated as an update to the state. Notice that the `Node` does not need to return the whole `State` schema - just an update. After applying this update, the `State` would then be `{"foo": 2, "bar": ["hi"]}`. If the second node returns `{"bar": ["bye"]}` then the `State` would then be `{"foo": 2, "bar": ["hi", "bye"]}`. Notice here that the `bar` key is updated by adding the two lists together.
### MessageState
`MessageState` is one of the few opinionated components in LangGraph. `MessageState` is a special state designed to make it easy to use a list of messages as a key in your state. Specifically, `MessageState` is defined as:
@@ -103,7 +101,7 @@ class State(MessagesState):
In LangGraph, nodes are typically python functions (sync or `async`) where the **first** positional argument is the [state](#state), and (optionally), the **second** positional argument is a "config", containing optional [configurable parameters](#configuration) (such as a `thread_id`).
Similar to `NetworkX`, you add these nodes to a graph using the [add_node](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.MessageGraph) method:
Similar to `NetworkX`, you add these nodes to a graph using the [add_node][langgraph.graph.StateGraph.add_node] method:
```python
from langchain_core.runnables import RunnableConfig
@@ -127,7 +125,6 @@ builder.add_node("other_node", my_other_node)
...
```
Behind the scenes, functions are converted to [RunnableLambda's](https://api.python.langchain.com/en/latest/runnables/langchain_core.runnables.base.RunnableLambda.html#langchain_core.runnables.base.RunnableLambda), which add batch and async support to your function, along with native tracing and debugging.
If you add a node to graph without specifying a name, it will be given a default name equivalent to the function name.
@@ -170,7 +167,7 @@ A node can have MULTIPLE outgoing edges. If a node has multiple out-going edges,
### Normal Edges
If you **always** want to go from node A to node B, you can use the [add_edge](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.StateGraph.add_edge) method directly.
If you **always** want to go from node A to node B, you can use the [add_edge][langgraph.graph.StateGraph.add_edge] method directly.
```python
graph.add_edge("node_a", "node_b")
@@ -178,7 +175,7 @@ graph.add_edge("node_a", "node_b")
### Conditional Edges
If you want to **optionally** route to 1 or more edges (or optionally terminate), you can use the [add_conditional_edges](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.StateGraph.add_conditional_edges) method. This method accepts the name of a node and a "routing function" to call after that node is executed:
If you want to **optionally** route to 1 or more edges (or optionally terminate), you can use the [add_conditional_edges][langgraph.graph.StateGraph.add_conditional_edges] method. This method accepts the name of a node and a "routing function" to call after that node is executed:
```python
graph.add_edge("node_a", routing_function)
@@ -196,7 +193,7 @@ graph.add_edge("node_a", routing_function, {True: "node_b", False: "node_c"})
### Entry Point
The entry point is first node to call when the graph starts.
The entry point is first node to call when the graph starts. You can use [`set_entry_point`][langgraph.graph.StateGraph.set_entry_point] to specify this.
```python
graph.set_entry_point("node_a")
@@ -213,6 +210,7 @@ graph.add_edge(START, "node_a")
### Conditional Entry Point
The conditional entry point is used when you want to specify a function to call to determine which node(s) should be called first.
You can use [`set_conditional_entry_point`][langgraph.graph.StateGraph.set_conditional_entry_point] to specify this.
```python
graph.set_conditional_entry_point(routing_function)
@@ -228,7 +226,7 @@ graph.set_conditional_entry_point(routing_function, {True: "node_b", False: "nod
By default, `Nodes` and `Edges` are defined ahead of time and operate on the same shared state. However, there can be cases where the exact edges are not known ahead of time and/or you may want different versions of `State` to exist at the same time. A common of example of this is with `map-reduce` design patterns. In this design pattern, a first node may generate a list of objects, and you may want to apply some other node to all those objects. The number of objects may be unknown ahead of time (meaning the number of edges may not be known) and the input `State` to the downstream `Node` should be different (one for each generated object).
To support this design pattern, LangGraph supports returning [`Send`](https://langchain-ai.github.io/langgraph/reference/graphs/#send) objects from conditional edges. `Send` takes two arguments: first is the name of the node, and second is the state to pass to that node.
To support this design pattern, LangGraph supports returning [`Send`](../reference/graphs.md#send) objects from conditional edges. `Send` takes two arguments: first is the name of the node, and second is the state to pass to that node.
```python
def continue_to_jokes(state: OverallState):
@@ -239,13 +237,15 @@ graph.add_conditional_edges("node_a", continue_to_jokes)
## Checkpointer
One of the main benefits of LangGraph is that it comes backed by a persistence layer. This is accomplished via [checkpointers](https://langchain-ai.github.io/langgraph/reference/checkpoints/#basecheckpointsaver).
One of the main benefits of LangGraph is that it comes backed by a persistence layer. This is accomplished via [checkpointers][basecheckpointsaver].
Checkpointers can be used to save a _checkpoint_ of the state of a graph after all steps of the graph. This allows for several things.
Checkpointers can be used to save a _checkpoint_ of the state of a graph after all steps of the graph. This allows for several things.
First, it allows for human-in-the-loop workflows, as it allows humans to inspect, interrupt, and approve steps. Checkpointers are needed for these workflows as the human has to be able to view the state of a graph at any point in time, and the graph has to be to resume execution after the human has made any updates to the state.
First, it allows for [human-in-the-loop workflows](agentic_concepts.md#human-in-the-loop), as it allows humans to inspect, interrupt, and approve steps. Checkpointers are needed for these workflows as the human has to be able to view the state of a graph at any point in time, and the graph has to be to resume execution after the human has made any updates to the state.
Second, it allows for "memory" between interactions. You can use checkpointers to create threads and save the state of a thread after a graph executes. In the case of repeated human interactions (like conversations) any follow up messages can be sent to that checkpoint, which will retain its memory of previous ones.
Second, it allows for ["memory"](agentic_concepts.md#memory) between interactions. You can use checkpointers to create threads and save the state of a thread after a graph executes. In the case of repeated human interactions (like conversations) any follow up messages can be sent to that checkpoint, which will retain its memory of previous ones.
See [this guide](../how-tos/persistence.ipynb) for how to add a checkpointer to your graph.
## Threads
@@ -263,16 +263,21 @@ config = {"configurable": {"thread_id": "a"}}
graph.invoke(inputs, config=config)
```
See [this guide](../how-tos/persistence.ipynb) for how to use threads.
## Checkpointer state
When you use a checkpointer with a graph, you can interact with the state of that graph.
This usually done when enabling different human-in-the-loop interaction patterns.
When interacting with the checkpointer state, you must specify [thread identifiers](#threads)
Each time you run the graph, the checkpointer creates several checkpoints every time a
node or set of nodes finishes running.
The most recent checkpoint is the current state of the thread.
When interacting with the checkpointer state, you must specify a [thread identifier](#threads).
Each checkpoint has two properties:
-**values**: This is the value of the state at this point in time.
-**next**: This is a tuple of the nodes to execute next in the graph.
- **values**: This is the value of the state at this point in time.
- **next**: This is a tuple of the nodes to execute next in the graph.
### Get state
@@ -280,7 +285,7 @@ You can get the state of a checkpointer by calling `graph.get_state(config)`. Th
### Get state history
You can also call `graph.get_state_history(config)` to get a list of the history of the graph. The config should contain `thread_id`, and the state history will be fetched for that thread.
You can also call `graph.get_state_history(config)` to get a list of the history of the graph. The config should contain `thread_id`, and the state history will be fetched for that thread.
### Update state
@@ -290,7 +295,6 @@ You can also interact with the state directly and update it. This takes three di
- values
- `as_node`
**config**
The config should contain `thread_id` specifying which thread to update.
@@ -310,13 +314,14 @@ class State(TypedDict):
bar: Annotated[list[str], add]
```
Let's now assume the current state of the graph is
Let's now assume the current state of the graph is
```
{"foo": 1, "bar": ["a"]}
```
If you update the state as below:
```
graph.update_state(config, {"foo": 2, "bar": ["b"]})
```
@@ -365,9 +370,11 @@ def node_a(state, config):
...
```
See [this guide](../how-tos/configuration.ipynb) for a full breakdown on configuration
## Breakpoints
It can often be useful to set breakpoints before or after certain nodes execute. This can be used to wait for human approval before continuing. These can be set when you ["compile" a graph](#compiling-your-graph). You can set breakpoints either *before* a node executes (using `interrupt_before`) or after a node executes (using `interrupt_after`.)
It can often be useful to set breakpoints before or after certain nodes execute. This can be used to wait for human approval before continuing. These can be set when you ["compile" a graph](#compiling-your-graph). You can set breakpoints either _before_ a node executes (using `interrupt_before`) or after a node executes (using `interrupt_after`.)
You **MUST** use a [checkpoiner](#checkpointer) when using breakpoints. This is because your graph needs to be able to resume execution.
@@ -381,17 +388,18 @@ graph.invoke(inputs, config=config)
graph.invoke(None, config=config)
```
See [this guide](../how-tos/human_in_the_loop/breakpoints.ipynb) for a full walkthrough of how to add breakpoints.
## Visualization
It's often nice to be able to visualize graphs, especially as they get more complex. LangGraph comes with several built-in ways to visualize graphs. See [this how-to guide](https://langchain-ai.github.io/langgraph/how-tos/visualization/) for more info.
It's often nice to be able to visualize graphs, especially as they get more complex. LangGraph comes with several built-in ways to visualize graphs. See [this how-to guide](../how-tos/visualization.ipynb) for more info.
## Streaming
LangGraph is built with first class support for streaming. There are several different streaming modes that LangGraph supports:
- `"values"`: This streams the full value of the state after each step of the graph.
- `"updates`: This streams the updates to the state after each step of the graph. If multiple updates are made in the same step (e.g. multiple nodes are run) then those updates are streamed separately.
- [`"values"`](../how-tos/stream-values.ipynb): This streams the full value of the state after each step of the graph.
- [`"updates`](../how-tos/stream-updates.ipynb): This streams the updates to the state after each step of the graph. If multiple updates are made in the same step (e.g. multiple nodes are run) then those updates are streamed separately.
- `"debug"`: This streams as much information as possible throughout the execution of the graph.
In addition, you can use the [`astream_events`](https://langchain-ai.github.io/langgraph/how-tos/streaming-tokens/) method to stream back events that happen _inside_ nodes. This is useful for streaming tokens of LLM calls.
In addition, you can use the [`astream_events`](../how-tos/streaming-events-from-within-tools.ipynb) method to stream back events that happen _inside_ nodes. This is useful for [streaming tokens of LLM calls](../how-tos/streaming-tokens.ipynb).
+7 -1
View File
@@ -16,12 +16,18 @@ These how-to guides show how to achieve that controllability.
- [How to create branches for parallel execution](branching.ipynb)
- [How to create map-reduce branches for parallel execution](map-reduce.ipynb)
## Persistence
LangGraph makes it easy to persist state across graph runs. The guide below shows how to add persistence to your graph.
- [How to add persistence ("memory") to your graph](persistence.ipynb)
## Human in the Loop
One of LangGraph's main benefits is that it makes human-in-the-loop workflows easy.
These guides cover common examples of that.
- [How to add persistence ("memory") to your graph](persistence.ipynb)
- [How to add breakpoints](human_in_the_loop/breakpoints.ipynb)
- [How to edit graph state](human_in_the_loop/edit-graph-state.ipynb)
- [How to wait for user input](human_in_the_loop/wait-user-input.ipynb)
+107 -110
View File
@@ -34,18 +34,18 @@ theme:
- search.suggest
- toc.follow
palette:
- scheme: default
primary: white
accent: gray
toggle:
icon: material/brightness-7
name: Switch to dark mode
- scheme: slate
primary: grey
accent: white
toggle:
icon: material/brightness-4
name: Switch to light mode
- scheme: default
primary: white
accent: gray
toggle:
icon: material/brightness-7
name: Switch to dark mode
- scheme: slate
primary: grey
accent: white
toggle:
icon: material/brightness-4
name: Switch to light mode
font:
text: "Public Sans"
code: "Roboto Mono"
@@ -60,7 +60,7 @@ plugins:
- https://docs.python.org/3/objects.inv
- https://api.python.langchain.com/en/latest/objects.inv
options:
members_order: source
members_order: source
allow_inspection: true
heading_level: 3
show_bases: true
@@ -68,9 +68,9 @@ plugins:
inherited_members: true
# merge_init_into_class: true
selection:
docstring_style: google
docstring_style: google
docstring_section_style: list
show_root_toc_entry: false
show_root_toc_entry: false
# show_signature_annotations: true
# show_symbol_type_heading: true
show_symbol_type_toc: true
@@ -80,19 +80,11 @@ plugins:
execute: false
include_source: True
include_requirejs: true
- git-committers:
repository: langchain-ai/langgraph
branch: main
docs_path: docs/docs/
token: !ENV ["MKDOCS_GIT_COMMITTERS_APIKEY"]
# TODO: Add minify plugin once it works alright with code block copying
# - minify:
# minify_html: true
nav:
- Home:
- 'index.md'
- Home:
- "index.md"
- Tutorials:
- 'tutorials/index.md'
- "tutorials/index.md"
- Introduction: tutorials/introduction.ipynb
- Chatbots:
- Customer Support: tutorials/customer-support/customer-support.ipynb
@@ -108,93 +100,94 @@ nav:
- Self-RAG using local LLMs: tutorials/rag/langgraph_self_rag_local.ipynb
- SQL Agent: tutorials/sql-agent.ipynb
- Agent Architectures:
- Multi-Agent Systems:
- Collaboration: tutorials/multi_agent/multi-agent-collaboration.ipynb
- Supervision: tutorials/multi_agent/agent_supervisor.ipynb
- Hierarchical Teams: tutorials/multi_agent/hierarchical_agent_teams.ipynb
- Planning Agents:
- Plan-and-Execute: tutorials/plan-and-execute/plan-and-execute.ipynb
- Reasoning without Observation: tutorials/rewoo/rewoo.ipynb
- LLMCompiler: tutorials/llm-compiler/LLMCompiler.ipynb
- Reflection & Critique:
- Basic Reflection: tutorials/reflection/reflection.ipynb
- Reflexion: tutorials/reflexion/reflexion.ipynb
- Language Agent Tree Search: tutorials/lats/lats.ipynb
- Self-Discover Agent: tutorials/self-discover/self-discover.ipynb
- Multi-Agent Systems:
- Collaboration: tutorials/multi_agent/multi-agent-collaboration.ipynb
- Supervision: tutorials/multi_agent/agent_supervisor.ipynb
- Hierarchical Teams: tutorials/multi_agent/hierarchical_agent_teams.ipynb
- Planning Agents:
- Plan-and-Execute: tutorials/plan-and-execute/plan-and-execute.ipynb
- Reasoning without Observation: tutorials/rewoo/rewoo.ipynb
- LLMCompiler: tutorials/llm-compiler/LLMCompiler.ipynb
- Reflection & Critique:
- Basic Reflection: tutorials/reflection/reflection.ipynb
- Reflexion: tutorials/reflexion/reflexion.ipynb
- Language Agent Tree Search: tutorials/lats/lats.ipynb
- Self-Discover Agent: tutorials/self-discover/self-discover.ipynb
- Evaluation & Analysis:
- Chatbot Evaluation via Simulation:
- Agent-based: tutorials/chatbot-simulation-evaluation/agent-simulation-evaluation.ipynb
- In LangSmith: tutorials/chatbot-simulation-evaluation/langsmith-agent-simulation-evaluation.ipynb
- Experimental:
- Web Research (STORM): tutorials/storm/storm.ipynb
- TNT-LLM: tutorials/tnt-llm/tnt-llm.ipynb
- Web Navigation: tutorials/web-navigation/web_voyager.ipynb
- Competitive Programming: tutorials/usaco/usaco.ipynb
- Extract structured output: tutorials/extraction/retries.ipynb
- Web Research (STORM): tutorials/storm/storm.ipynb
- TNT-LLM: tutorials/tnt-llm/tnt-llm.ipynb
- Web Navigation: tutorials/web-navigation/web_voyager.ipynb
- Competitive Programming: tutorials/usaco/usaco.ipynb
- Extract structured output: tutorials/extraction/retries.ipynb
- "How-to Guides":
- 'how-tos/index.md'
- Controllability:
- Create subgraphs: how-tos/subgraph.ipynb
- Create branches for parallel execution: how-tos/branching.ipynb
- Create map-reduce branches for parallel execution: how-tos/map-reduce.ipynb
- Human-in-the-loop:
- Add persistence ("memory"): how-tos/persistence.ipynb
- Add breakpoints: how-tos/human_in_the_loop/breakpoints.ipynb
- Wait for user input: how-tos/human_in_the_loop/wait-user-input.ipynb
- View and update past graph state: how-tos/human_in_the_loop/time-travel.ipynb
- Edit graph state: how-tos/human_in_the_loop/edit-graph-state.ipynb
- Streaming:
- Stream full state: how-tos/stream-values.ipynb
- Stream state updates: how-tos/stream-updates.ipynb
- Stream LLM tokens: how-tos/streaming-tokens.ipynb
- Stream arbitrarily nested content: how-tos/streaming-content.ipynb
- Configure multiple streaming modes: how-tos/stream-multiple.ipynb
- Stream events from within tools: how-tos/streaming-events-from-within-tools.ipynb
- Other:
- Run graph asynchronously: how-tos/async.ipynb
- Visualize your graph: how-tos/visualization.ipynb
- Add runtime configuration: how-tos/configuration.ipynb
- Use Pydantic model as state: how-tos/state-model.ipynb
- Prebuilt ReAct Agent:
- Create a ReAct agent: how-tos/create-react-agent.ipynb
- Add memory to a ReAct agent: how-tos/create-react-agent-memory.ipynb
- Add a system prompt to a ReAct agent: how-tos/create-react-agent-system-prompt.ipynb
- Add human-in-the-Loop to a ReAct agent: how-tos/create-react-agent-hitl.ipynb
- 'Conceptual Guides':
- 'concepts/index.md'
- LangGraph for Agentic Applications: concepts/high_level.md
- Low Level LangGraph Concepts: concepts/low_level.md
- Common Agentic Patterns: concepts/agentic_concepts.md
- "how-tos/index.md"
- Controllability:
- Create subgraphs: how-tos/subgraph.ipynb
- Create branches for parallel execution: how-tos/branching.ipynb
- Create map-reduce branches for parallel execution: how-tos/map-reduce.ipynb
- Persistence:
- Add persistence ("memory"): how-tos/persistence.ipynb
- Human-in-the-loop:
- Add breakpoints: how-tos/human_in_the_loop/breakpoints.ipynb
- Wait for user input: how-tos/human_in_the_loop/wait-user-input.ipynb
- View and update past graph state: how-tos/human_in_the_loop/time-travel.ipynb
- Edit graph state: how-tos/human_in_the_loop/edit-graph-state.ipynb
- Streaming:
- Stream full state: how-tos/stream-values.ipynb
- Stream state updates: how-tos/stream-updates.ipynb
- Stream LLM tokens: how-tos/streaming-tokens.ipynb
- Stream arbitrarily nested content: how-tos/streaming-content.ipynb
- Configure multiple streaming modes: how-tos/stream-multiple.ipynb
- Stream events from within tools: how-tos/streaming-events-from-within-tools.ipynb
- Other:
- Run graph asynchronously: how-tos/async.ipynb
- Visualize your graph: how-tos/visualization.ipynb
- Add runtime configuration: how-tos/configuration.ipynb
- Use Pydantic model as state: how-tos/state-model.ipynb
- Prebuilt ReAct Agent:
- Create a ReAct agent: how-tos/create-react-agent.ipynb
- Add memory to a ReAct agent: how-tos/create-react-agent-memory.ipynb
- Add a system prompt to a ReAct agent: how-tos/create-react-agent-system-prompt.ipynb
- Add human-in-the-Loop to a ReAct agent: how-tos/create-react-agent-hitl.ipynb
- "Conceptual Guides":
- "concepts/index.md"
- LangGraph for Agentic Applications: concepts/high_level.md
- Low Level LangGraph Concepts: concepts/low_level.md
- Common Agentic Patterns: concepts/agentic_concepts.md
- Reference:
- Graphs: reference/graphs.md
- Checkpointing: reference/checkpoints.md
- Prebuilt Components: reference/prebuilt.md
- Errors: reference/errors.md
- 'Cloud (alpha)':
- 'cloud/index.md'
- "Cloud (alpha)":
- "cloud/index.md"
- Tutorials:
- Quick Start: 'cloud/quick_start.md'
- Quick Start: "cloud/quick_start.md"
- How-to Guides:
- Deployment:
- Setup App: 'cloud/deployment/setup.md'
- Deploy to Cloud: 'cloud/deployment/cloud.md'
- Test with Studio: 'cloud/deployment/studio.md'
- Self-Host: 'cloud/deployment/self_hosted.md'
- Setup App: "cloud/deployment/setup.md"
- Deploy to Cloud: "cloud/deployment/cloud.md"
- Test with Studio: "cloud/deployment/studio.md"
- Self-Host: "cloud/deployment/self_hosted.md"
- Streaming:
- Stream Values: 'cloud/how-tos/cloud_examples/stream_values.ipynb'
- Stream Updates: 'cloud/how-tos/cloud_examples/stream_updates.ipynb'
- Stream Messages: 'cloud/how-tos/cloud_examples/stream_messages.ipynb'
- Stream Events: 'cloud/how-tos/cloud_examples/stream_events.ipynb'
- Stream Debug: 'cloud/how-tos/cloud_examples/stream_debug.ipynb'
- Multiple Modes: 'cloud/how-tos/cloud_examples/stream_multiple.ipynb'
- Double Texting:
- Interrupt: 'cloud/how-tos/cloud_examples/interrupt_concurrent.ipynb'
- Rollback: 'cloud/how-tos/cloud_examples/rollback_concurrent.ipynb'
- Reject: 'cloud/how-tos/cloud_examples/reject_concurrent.ipynb'
- Enqueue: 'cloud/how-tos/cloud_examples/enqueue_concurrent.ipynb'
- Run Agent in Background: 'cloud/how-tos/cloud_examples/background_run.ipynb'
- Run Multiple Agents in Thread: 'cloud/how-tos/cloud_examples/same-thread.ipynb'
- Stream Values: "cloud/how-tos/cloud_examples/stream_values.ipynb"
- Stream Updates: "cloud/how-tos/cloud_examples/stream_updates.ipynb"
- Stream Messages: "cloud/how-tos/cloud_examples/stream_messages.ipynb"
- Stream Events: "cloud/how-tos/cloud_examples/stream_events.ipynb"
- Stream Debug: "cloud/how-tos/cloud_examples/stream_debug.ipynb"
- Multiple Modes: "cloud/how-tos/cloud_examples/stream_multiple.ipynb"
- Double Texting:
- Interrupt: "cloud/how-tos/cloud_examples/interrupt_concurrent.ipynb"
- Rollback: "cloud/how-tos/cloud_examples/rollback_concurrent.ipynb"
- Reject: "cloud/how-tos/cloud_examples/reject_concurrent.ipynb"
- Enqueue: "cloud/how-tos/cloud_examples/enqueue_concurrent.ipynb"
- Run Agent in Background: "cloud/how-tos/cloud_examples/background_run.ipynb"
- Run Multiple Agents in Thread: "cloud/how-tos/cloud_examples/same-thread.ipynb"
- Human-in-the-Loop:
- Add Breakpoint: 'cloud/how-tos/cloud_examples/human_in_the_loop_breakpoint.ipynb'
- Wait for User Input: 'cloud/how-tos/cloud_examples/human_in_the_loop_user_input.ipynb'
@@ -204,19 +197,18 @@ nav:
- Convert LangGraph calls to LangGraph Cloud calls: 'cloud/how-tos/cloud_examples/langgraph_to_langgraph_cloud.ipynb'
- Create Cron Jobs: 'cloud/how-tos/cloud_examples/cron_jobs.ipynb'
- Create Stateless Runs: 'cloud/how-tos/cloud_examples/stateless_runs.ipynb'
- Integrate Webhooks: 'cloud/how-tos/cloud_examples/webhooks.ipynb'
- SDK:
- Python: 'cloud/sdk/python_sdk.ipynb'
- JS/TS: 'cloud/sdk/js_sdk.ipynb'
- Conceptual Guides:
'cloud/concepts/index.md'
- Python: "cloud/sdk/python_sdk.ipynb"
- JS/TS: "cloud/sdk/js_sdk.ipynb"
- Conceptual Guides: "cloud/concepts/index.md"
- Reference:
- API: 'cloud/reference/api/api_ref.md'
- API: "cloud/reference/api/api_ref.md"
- SDK:
- Python: 'cloud/reference/sdk/python_sdk_ref.md'
- JS/TS: 'cloud/reference/sdk/js_ts_sdk_ref.md'
- CLI: 'cloud/reference/cli.md'
- Environment Variables: 'cloud/reference/env_var.md'
- Python: "cloud/reference/sdk/python_sdk_ref.md"
- JS/TS: "cloud/reference/sdk/js_ts_sdk_ref.md"
- CLI: "cloud/reference/cli.md"
- Environment Variables: "cloud/reference/env_var.md"
markdown_extensions:
- abbr
@@ -247,7 +239,7 @@ markdown_extensions:
- pymdownx.magiclink:
normalize_issue_symbols: true
repo_url_shorthand: true
user: langchain-ai
user: langchain-ai
repo: langgraph
- pymdownx.mark
- pymdownx.smartsymbols
@@ -272,7 +264,7 @@ extra_css:
extra:
social:
- icon: fontawesome/brands/js
- icon: fontawesome/brands/js
link: https://langchain-ai.github.io/langgraphjs/
- icon: fontawesome/brands/github
link: https://github.com/langchain-ai/langgraph
@@ -292,5 +284,10 @@ extra:
- icon: material/emoticon-sad-outline
name: This page could be improved
data: 0
note: >-
note: >-
Thanks for your feedback! Please help us improve this page by adding to the discussion below.
validation:
omitted_files: warn
absolute_links: warn
unrecognized_links: warn
anchors: warn
+2 -2
View File
@@ -66,9 +66,9 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"## Cron job threadless\n",
"## Cron job stateless\n",
"\n",
"You can also create threadless cron jobs by using the following code:"
"You can also create stateless cron jobs by using the following code:"
]
},
{
@@ -6,7 +6,7 @@
"source": [
"# How to Wait for User Input\n",
"\n",
"One of the main human-in-the-loop interaction patterns is waiting for human input. A key use case involves asking the user clarifying questions. One way to accomplish this is simply go to the END node and exit the graph. Then, any user response comes back in as fresh invocation of the graph. This is basically just creating a chatbot architecture.\n",
"One of the main human-in-the-loop interaction patterns is waiting for human input. A key use case involves asking the user clarifying questions. One way to accomplish this is simply go to the `END` node and exit the graph. Then, any user response comes back in as fresh invocation of the graph. This is basically just creating a chatbot architecture.\n",
"\n",
"The issue with this is it is tough to resume back in a particular point in the graph. Often times the agent is halfway through some process, and just needs a bit of a user input. Although it is possible to design your graph in such a way where you have a `conditional_entry_point` to route user messages back to the right place, that is not super scalable (as it essentially involves having a routing function that can end up almost anywhere).\n",
"\n",
Binary file not shown.

After

Width:  |  Height:  |  Size: 322 KiB

+10 -6
View File
@@ -7,9 +7,13 @@
"source": [
"# How to run multiple agents on the same thread\n",
"\n",
"In LangGraph API, a thread is not explicitly associated with a particular agent.\n",
"This means that you can run multiple agents on the same thread.\n",
"In this example, we will create two agents and then call them both on the same thread."
"In LangGraph Cloud, a thread is not explicitly associated with a particular agent.\n",
"This means that you can run multiple agents on the same thread, which allows a different\n",
"agent to continue from an initial agent's progress.\n",
"\n",
"In this example, we will create two agents and then call them both on the same thread.\n",
"You'll see that the second agent will respond using information from the [checkpoint](https://langchain-ai.github.io/langgraph/concepts/low_level/#checkpointer-state) generated in the thread\n",
"by the first agent as context."
]
},
{
@@ -37,7 +41,7 @@
"id": "4f10d346-69e6-44f4-8ff0-ef539ba938df",
"metadata": {},
"source": [
"We can see that these agents are different"
"We can see that these agents are different:"
]
},
{
@@ -97,7 +101,7 @@
"id": "5e655e61-c2ee-488a-90f6-6189c84841da",
"metadata": {},
"source": [
"We can now run it on the OpenAI assistant first."
"We can now run the OpenAI assistant on the thread first."
]
},
{
@@ -133,7 +137,7 @@
"id": "c53709e9-ddb2-4429-9042-456eb6c91244",
"metadata": {},
"source": [
"Now, we can run it on a different Anthropic-based assistant."
"Now, we can run it on a second Anthropic-based assistant and see that this second assistant is aware of the initial question, and can answer the question, `and you?`:"
]
},
{
+72
View File
@@ -0,0 +1,72 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Use Webhooks\n",
"\n",
"You may wish to use webhooks in your client, especially when using async streams in case you want to update something in your service once the API call to Langgraph Cloud has finished running. To do so, you will need to expose an endpoint that can accept POST requests, and then pass it to your API request in the \"webhook\" parameter.\n",
"\n",
"Currently, the SDK has not exposed this endpoint but you can access it through curl commands as follows.\n",
"\n",
"The following endpoints accept `webhook` as a parameter: \n",
"\n",
"- Create Run -> POST /thread/{thread_id}/runs\n",
"- Create Thread Cron -> POST /thread/{thread_id}/runs/crons\n",
"- Stream Run -> POST /thread/{thread_id}/runs/stream\n",
"- Wait Run -> POST /thread/{thread_id}/runs/wait\n",
"- Create Cron -> POST /runs/crons\n",
"- Stream Run Stateless -> POST /runs/stream\n",
"- Wait Run Stateless -> POST /runs/wait\n",
"\n",
"The following example uses a url from a public website that allows users to create free webhooks, but you should pass in the webhook that you wish to use. "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"curl --request POST \\\n",
" --url http://localhost:8123/threads/b76d1e94-f251-40e3-8933-796d775cdb4c/runs/stream \\\n",
" --header 'Content-Type: application/json' \\\n",
" --data '{\n",
" \"assistant_id\": \"fe096781-5601-53d2-b2f6-0d3403f7e9ca\",\n",
" \"input\" : {\"messages\":[{\"role\": \"user\", \"content\": \"Hello! My name is Bagatur and I am 26 years old.\"}]},\n",
" \"metadata\": {},\n",
" \"config\": {\n",
" \"configurable\": {}\n",
" },\n",
" \"multitask_strategy\": \"reject\",\n",
" \"stream_mode\": [\n",
" \"values\"\n",
" ],\n",
" \"webhook\": \"https://webhook.site/6ca33471-dd65-4103-a851-0a252dae0f2a\"\n",
"}'"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To check that this worked as intended, we can go to the website where our webhook was created and confirm that it received a POST request:"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"![Webhook response](./img/webhook_results.png)"
]
}
],
"metadata": {
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
+1 -1
View File
@@ -7,7 +7,7 @@
"source": [
"# How to add human-in-the-loop processes to the prebuilt ReAct agent\n",
"\n",
"This tutorial 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",
"This tutorial will show how to add human-in-the-loop processes to the prebuilt ReAct agent. Please see [this tutorial](./create-react-agent.ipynb) 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."
]
+1 -1
View File
@@ -7,7 +7,7 @@
"source": [
"# How to add memory to the prebuilt ReAct agent\n",
"\n",
"This tutorial 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",
"This tutorial will show how to add memory to the prebuilt ReAct agent. Please see [this tutorial](./create-react-agent.ipynb) for how to get started with the prebuilt ReAct agent\n",
"\n",
"All we need to do to enable memory is pass in a checkpointer to `create_react_agents`"
]
@@ -7,7 +7,7 @@
"source": [
"# How to add a custom system prompt to the prebuilt ReAct agent\n",
"\n",
"This tutorial will show how to add a custom system prompt to the prebuilt ReAct agent. Please see [this tutorial](create-react-agent) for how to get started with the prebuilt ReAct agent\n",
"This tutorial will show how to add a custom system prompt to the prebuilt ReAct agent. Please see [this tutorial](./create-react-agent.ipynb) for how to get started with the prebuilt ReAct agent\n",
"\n",
"You can add a custom system prompt by passing a string to the `messages_modifier` param."
]
+2 -2
View File
@@ -577,7 +577,7 @@
"\n",
"There are multiple options, and the ideal option may depend on the specifics of your application and capabilities of your chosen LLM. Note that many chat models require that messages with tool calls be immediately followed by a tool message containing the result of the tool call. So our intervention may:\n",
"\n",
"1. Update the parameters of the tool call before proceeding normally (see this [how-to guide](https://langchain-ai.github.io/langgraph/how-tos/time-travel/#pause-before-tools) for an example);\n",
"1. Update the parameters of the tool call before proceeding normally (see this [how-to guide](./human_in_the_loop/breakpoints.ipynb) for an example);\n",
"2. Add a tool message to the conversation history indicating the user's desired intervention (see an example [here](https://langchain-ai.github.io/langgraph/tutorials/customer-support/customer-support/#state-assistant));\n",
"3. Catch the tool call message, replacing it with a `AIMessage` asking for verification and only adding the tool call message to the conversation history if approved.\n",
"\n",
@@ -972,7 +972,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.1"
"version": "3.12.2"
}
},
"nbformat": 4,
+1 -1
View File
@@ -7,7 +7,7 @@
"source": [
"# How to view and update past graph state\n",
"\n",
"Once you start [checkpointing](./persistence.ipynb) your graphs, you can easily **get** or **update** the state of the agent at any point in time. This permits a few things:\n",
"Once you start [checkpointing](../persistence.ipynb) your graphs, you can easily **get** or **update** the state of the agent at any point in time. This permits a few things:\n",
"\n",
"1. You can surface a state during an interrupt to a user to let them accept an action.\n",
"2. You can **rewind** the graph to reproduce or avoid issues.\n",
@@ -28,7 +28,7 @@
"* Web-search\n",
"* Iterative RAG\n",
"\n",
"We'll use [Command R](https://txt.cohere.com/command-r/), a recent release from Cohere that:\n",
"We'll use [Command R](https://cohere.com/blog/command-r), a recent release from Cohere that:\n",
"\n",
"* Has strong accuracy on RAG and Tool Use\n",
"* Has 128k context\n",
+1 -1
View File
@@ -457,7 +457,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.1"
"version": "3.12.2"
}
},
"nbformat": 4,
@@ -1,5 +0,0 @@
services:
langgraph-api:
build: yoyoyo # invalid, will be overridden by generated compose file
environment:
HELLO: world
@@ -1,5 +0,0 @@
services:
langgraph-api:
build: yoyoyo # invalid, will be overridden by generated compose file
environment:
HELLO: world
+113 -377
View File
@@ -1,8 +1,7 @@
import json
import pathlib
import shutil
import sys
from typing import Optional
from typing import Callable, Optional
import click
import click.exceptions
@@ -10,25 +9,10 @@ import click.exceptions
import langgraph_cli.config
import langgraph_cli.docker
from langgraph_cli.analytics import log_command
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.progress import Progress
from langgraph_cli.util import clean_empty_lines
OPT_DOCKER_COMPOSE = click.option(
"--docker-compose",
"-d",
help="Advanced: Path to docker-compose.yml file with additional services to launch.",
type=click.Path(
exists=True,
file_okay=True,
dir_okay=False,
resolve_path=True,
path_type=pathlib.Path,
),
)
OPT_CONFIG = click.option(
"--config",
"-c",
@@ -105,12 +89,6 @@ OPT_PORT = click.option(
\b
""",
)
OPT_RECREATE = click.option(
"--recreate/--no-recreate",
default=False,
show_default=True,
help="Recreate containers even if their configuration and image haven't changed",
)
OPT_PULL = click.option(
"--pull/--no-pull",
default=True,
@@ -130,21 +108,6 @@ OPT_VERBOSE = click.option(
default=False,
help="Show more output from the server logs",
)
OPT_WATCH = click.option("--watch", is_flag=True, help="Restart on file changes")
OPT_LANGGRAPH_API_PATH = click.option(
"--langgraph-api-path",
type=click.Path(exists=True, file_okay=False, dir_okay=True, resolve_path=True),
hidden=True,
)
OPT_DEBUGGER_PORT = click.option(
"--debugger-port",
type=int,
help="Pull the debugger image locally and serve the UI on specified port",
)
OPT_POSTGRES_URI = click.option(
"--postgres-uri",
help="Postgres URI to use for the database. Defaults to launching a local database",
)
@click.group()
@@ -152,178 +115,147 @@ def cli():
pass
@OPT_RECREATE
@OPT_PULL
@OPT_PORT
@OPT_DOCKER_COMPOSE
@OPT_CONFIG
@OPT_VERBOSE
@OPT_DEBUGGER_PORT
@OPT_WATCH
@OPT_LANGGRAPH_API_PATH
@OPT_POSTGRES_URI
@click.option(
"--wait",
is_flag=True,
help="Wait for services to start before returning. Implies --detach",
@cli.command(
help="Start langgraph test server. This command enables you to confirm your graph will work inside the langgraph API server, before using LangGraph Cloud."
)
@cli.command(help="Start langgraph API server")
@log_command
def up(
def test(
config: pathlib.Path,
docker_compose: Optional[pathlib.Path],
port: int,
recreate: bool,
pull: bool,
watch: bool,
langgraph_api_path: Optional[pathlib.Path],
wait: bool,
# stop_when_ready: bool,
verbose: bool,
debugger_port: Optional[int],
postgres_uri: Optional[str],
):
with Runner() as runner, Progress(message="Pulling...") as set:
# check docker available
capabilities = langgraph_cli.docker.check_capabilities(runner)
args, stdin = prepare(
# open config
with open(config) as f:
config_json = langgraph_cli.config.validate_config(json.load(f))
# build
base_image = "langchain/langgraph-trial"
tag = f"langgraph-test-{config.parent.name}"
_build(
runner,
capabilities=capabilities,
config_path=config,
docker_compose=docker_compose,
port=port,
pull=pull,
watch=watch,
langgraph_api_path=langgraph_api_path,
verbose=verbose,
debugger_port=debugger_port,
postgres_uri=postgres_uri,
set,
config,
config_json,
None,
base_image,
pull,
tag,
)
# add up + options
args.extend(["up", "--remove-orphans"])
if recreate:
args.extend(["--force-recreate", "--renew-anon-volumes"])
shutil.rmtree(config.parent / ".langgraph-data", ignore_errors=True)
try:
runner.run(subp_exec("docker", "volume", "rm", "langgraph-data"))
except click.exceptions.Exit:
pass
if watch:
args.append("--watch")
if wait:
args.append("--wait")
# run
set("Running...")
args = [
"run",
"--rm",
"-p",
f"{port}:8000",
]
if isinstance(config_json["env"], str):
args.extend(
[
"--env-file",
str(config.parent / config_json["env"]),
]
)
else:
args.append("--abort-on-container-exit")
# run docker compose
set("Building...")
for k, v in config_json["env"].items():
args.extend(
[
"-e",
f"{k}={v}",
]
)
if capabilities.healthcheck_start_interval:
args.extend(
[
"--health-interval",
"5s",
"--health-retries",
"1",
"--health-start-period",
"10s",
"--health-start-interval",
"1s",
]
)
else:
args.extend(
[
"--health-interval",
"5s",
"--health-retries",
"2",
]
)
def on_stdout(line: str):
if "unpacking to docker.io" in line:
set("Starting...")
elif "GET /ok" in line:
debugger_origin = (
f"http://localhost:{debugger_port}"
if debugger_port
else "https://smith.langchain.com"
)
if "GET /ok" in line:
set("")
sys.stdout.write(
f"""Ready!
- API: http://localhost:{port}
- Docs: http://localhost:{port}/docs
- Debugger: {debugger_origin}/studio/?baseUrl=http://127.0.0.1:{port}
"""
)
sys.stdout.flush()
return True
if capabilities.compose_type == "plugin":
compose_cmd = ["docker", "compose"]
elif capabilities.compose_type == "standalone":
compose_cmd = ["docker-compose"]
runner.run(
subp_exec(
*compose_cmd,
"docker",
*args,
input=stdin,
tag,
verbose=verbose,
on_stdout=on_stdout,
)
)
@OPT_PORT
@OPT_DOCKER_COMPOSE
@OPT_CONFIG
@OPT_VERBOSE
@OPT_DEBUGGER_PORT
@cli.command(help="Stop langgraph API server")
@log_command
def down(
def _build(
runner,
set: Callable[[str], None],
config: pathlib.Path,
docker_compose: Optional[pathlib.Path],
port: int,
verbose: bool,
debugger_port: Optional[int],
config_json: dict,
platform: Optional[str],
base_image: Optional[str],
pull: bool,
tag: str,
):
with Runner() as runner:
capabilities = langgraph_cli.docker.check_capabilities(runner)
args, stdin = prepare(
runner,
capabilities=capabilities,
config_path=config,
docker_compose=docker_compose,
port=port,
pull=False,
watch=False,
langgraph_api_path=None,
verbose=verbose,
debugger_port=debugger_port,
base_image = base_image or "langchain/langgraph-api"
# pull latest images
if pull:
runner.run(
subp_exec(
"docker",
"pull",
f"{base_image}:{config_json['python_version']}",
)
)
# add down + options
args.append("down")
# run docker compose
if capabilities.compose_type == "plugin":
compose_cmd = ["docker", "compose"]
elif capabilities.compose_type == "standalone":
compose_cmd = ["docker-compose"]
runner.run(subp_exec(*compose_cmd, *args, input=stdin, verbose=verbose))
@OPT_DOCKER_COMPOSE
@OPT_CONFIG
@click.option("--follow", "-f", is_flag=True, help="Follow logs")
@cli.command(help="Show langgraph API server logs")
@log_command
def logs(
config: pathlib.Path,
docker_compose: Optional[pathlib.Path],
follow: bool,
):
with Runner() as runner:
capabilities = langgraph_cli.docker.check_capabilities(runner)
args, stdin = prepare(
runner,
capabilities=capabilities,
config_path=config,
docker_compose=docker_compose,
port=8123,
pull=False,
watch=False,
verbose=False,
langgraph_api_path=None,
set("Building...")
# apply options
args = [
"-f",
"-", # stdin
"-t",
tag,
]
if platform:
args.extend(["--platform", platform])
# apply config
stdin = langgraph_cli.config.config_to_docker(config, config_json, base_image)
# run docker build
runner.run(
subp_exec(
"docker", "build", *args, str(config.parent), input=stdin, verbose=True
)
# add logs + options
args.append("logs")
if follow:
args.extend(["-f"])
# run docker compose
if capabilities.compose_type == "plugin":
compose_cmd = ["docker", "compose"]
elif capabilities.compose_type == "standalone":
compose_cmd = ["docker-compose"]
runner.run(subp_exec(*compose_cmd, *args, input=stdin, verbose=True))
)
@OPT_CONFIG
@@ -351,220 +283,24 @@ def logs(
\b
""",
)
@click.option(
"--base-image",
hidden=True,
)
@cli.command(help="Build langgraph API server docker image")
@log_command
def build(
config: pathlib.Path,
platform: Optional[str],
base_image: Optional[str],
pull: bool,
tag: str,
):
with open(config) as f:
config_json = langgraph_cli.config.validate_config(json.load(f))
with Runner() as runner:
with Runner() as runner, Progress(message="Pulling...") as set:
# check docker available
langgraph_cli.docker.check_capabilities(runner)
# pull latest images
if pull:
runner.run(
subp_exec(
"docker",
"pull",
f"langchain/langgraph-api:{config_json['python_version']}",
)
)
# apply options
args = [
"-f",
"-", # stdin
"-t",
tag,
]
if platform:
args.extend(["--platform", platform])
# apply config
stdin = langgraph_cli.config.config_to_docker(config, config_json)
# run docker build
runner.run(
subp_exec(
"docker", "build", *args, str(config.parent), input=stdin, verbose=True
)
)
@cli.group(help="Export langgraph compose files")
def export():
pass
@click.option(
"--output",
"-o",
help="Output path to write the docker compose file to",
type=click.Path(
exists=False,
file_okay=True,
dir_okay=False,
resolve_path=True,
path_type=pathlib.Path,
),
required=True,
)
@OPT_CONFIG
@OPT_PORT
@OPT_WATCH
@OPT_LANGGRAPH_API_PATH
@export.command(name="compose", help="Export docker compose file")
@log_command
def export_compose(
output: pathlib.Path,
config: pathlib.Path,
port: int,
watch: bool,
langgraph_api_path: Optional[pathlib.Path],
):
with Runner() as runner:
capabilities = langgraph_cli.docker.check_capabilities(runner)
_, stdin = prepare(
runner,
capabilities=capabilities,
config_path=config,
docker_compose=None,
pull=False,
watch=watch,
langgraph_api_path=langgraph_api_path,
port=port,
verbose=False,
)
with open(output, "w") as f:
f.write(clean_empty_lines(stdin))
@click.option(
"--output",
"-o",
help="Output path (directory) to write the helm chart to",
type=click.Path(
exists=False,
file_okay=False,
dir_okay=True,
resolve_path=True,
path_type=pathlib.Path,
),
required=True,
)
@OPT_PORT
@OPT_DOCKER_COMPOSE
@OPT_CONFIG
@export.command(
name="helm",
help="Build and export a helm chart to deploy to a Kubernetes cluster",
hidden=True,
)
@log_command
def export_helm(
output: pathlib.Path,
config: pathlib.Path,
docker_compose: Optional[pathlib.Path],
port: int,
):
with open(config) as f:
config_json = langgraph_cli.config.validate_config(json.load(f))
with Runner() as runner:
# check docker available
capabilities = langgraph_cli.docker.check_capabilities(runner)
# prepare args
stdin = langgraph_cli.docker.compose(capabilities, port=port)
args = [
"convert",
"--chart",
"-o",
str(output),
"-v",
]
# apply options
if docker_compose:
args.extend(["-f", str(docker_compose)])
args.extend(["-f", "-"]) # stdin
# apply config
stdin += langgraph_cli.config.config_to_compose(config, config_json)
# run kompose convert
runner.run(subp_exec("kompose", *args, input=stdin))
def prepare_args_and_stdin(
*,
capabilities: DockerCapabilities,
config_path: pathlib.Path,
config: Config,
docker_compose: Optional[pathlib.Path],
port: int,
watch: bool,
langgraph_api_path: Optional[pathlib.Path],
debugger_port: Optional[int] = None,
postgres_uri: Optional[str] = None,
):
# prepare args
stdin = langgraph_cli.docker.compose(
capabilities,
port=port,
debugger_port=debugger_port,
postgres_uri=postgres_uri,
)
args = [
"--project-directory",
str(config_path.parent),
]
# apply options
if docker_compose:
args.extend(["-f", str(docker_compose)])
args.extend(["-f", "-"]) # stdin
# apply config
stdin += langgraph_cli.config.config_to_compose(
config_path, config, watch=watch, langgraph_api_path=langgraph_api_path
)
return args, stdin
def prepare(
runner,
*,
capabilities: DockerCapabilities,
config_path: pathlib.Path,
docker_compose: Optional[pathlib.Path],
port: int,
pull: bool,
watch: bool,
langgraph_api_path: Optional[pathlib.Path],
verbose: bool,
debugger_port: Optional[int] = None,
postgres_uri: Optional[str] = None,
):
with open(config_path) as f:
config = langgraph_cli.config.validate_config(json.load(f))
# pull latest images
if pull:
runner.run(
subp_exec(
"docker",
"pull",
f"langchain/langgraph-api:{config['python_version']}",
verbose=verbose,
)
)
args, stdin = prepare_args_and_stdin(
capabilities=capabilities,
config_path=config_path,
config=config,
docker_compose=docker_compose,
port=port,
watch=watch,
langgraph_api_path=langgraph_api_path,
debugger_port=debugger_port,
postgres_uri=postgres_uri,
)
return args, stdin
# open config
with open(config) as f:
config_json = langgraph_cli.config.validate_config(json.load(f))
# build
_build(runner, set, config, config_json, platform, base_image, pull, tag)
+13 -10
View File
@@ -191,7 +191,7 @@ def _update_graph_paths(
config["graphs"][graph_id] = f"{module_str}:{attr_str}"
def config_to_docker(config_path: pathlib.Path, config: Config):
def config_to_docker(config_path: pathlib.Path, config: Config, base_image: str):
# configure pip
pip_install = "pip install -c /api/constraints.txt"
if config.get("pip_config_file"):
@@ -223,13 +223,15 @@ def config_to_docker(config_path: pathlib.Path, config: Config):
# https://til.simonwillison.net/python/pyproject
faux_pkgs_str = f"{os.linesep}{os.linesep}".join(
f"""ADD {relpath} {destpath}
COPY <<EOF /deps/__outer_{fullpath.name}/pyproject.toml
[project]
name = "{fullpath.name}"
version = "0.1"
[tool.setuptools.package-data]
"*" = ["**/*"]
EOF"""
RUN set -ex && \\
for line in '[project]' \\
'name = "{fullpath.name}"' \\
'version = "0.1"' \\
'[tool.setuptools.package-data]' \\
'"*" = ["**/*"]'; do \\
echo "$line" >> /deps/__outer_{fullpath.name}/pyproject.toml; \\
done
"""
for fullpath, (relpath, destpath) in local_deps.faux_pkgs.items()
)
local_pkgs_str = os.linesep.join(
@@ -237,7 +239,7 @@ EOF"""
for fullpath, relpath in local_deps.real_pkgs.items()
)
return f"""FROM langchain/langgraph-api:{config['python_version']}
return f"""FROM {base_image}:{config['python_version']}
{os.linesep.join(config["dockerfile_lines"])}
@@ -261,6 +263,7 @@ ENV LANGSERVE_GRAPHS='{json.dumps(config["graphs"])}'
def config_to_compose(
config_path: pathlib.Path,
config: Config,
base_image: str,
watch: bool = False,
langgraph_api_path: Optional[pathlib.Path] = None,
):
@@ -301,6 +304,6 @@ def config_to_compose(
build:
context: .
dockerfile_inline: |
{textwrap.indent(config_to_docker(config_path, config), " ")}
{textwrap.indent(config_to_docker(config_path, config, base_image), " ")}
{watch_str}
"""
+1 -116
View File
@@ -1,47 +1,13 @@
import json
import pathlib
import shutil
from typing import Literal, NamedTuple, Optional
from typing import NamedTuple
import click.exceptions
from langgraph_cli.exec import subp_exec
ROOT = pathlib.Path(__file__).parent.resolve()
DEFAULT_POSTGRES_URI = (
"postgres://postgres:postgres@langgraph-postgres:5432/postgres?sslmode=disable"
)
DB = """
langgraph-postgres:
image: postgres:16
ports:
- "5433:5432"
environment:
POSTGRES_DB: postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
volumes:
- langgraph-data:/var/lib/postgresql/data
healthcheck:
test: pg_isready -U postgres
start_period: 10s
timeout: 1s
retries: 5
"""
DEBUGGER = """
langgraph-debugger:
image: langchain/langgraph-debugger
restart: on-failure
ports:
- "{debugger_port}:3968"
depends_on:
langgraph-postgres:
condition: service_healthy
"""
class Version(NamedTuple):
@@ -50,14 +16,9 @@ class Version(NamedTuple):
patch: int
DockerComposeType = Literal["plugin", "standalone"]
class DockerCapabilities(NamedTuple):
version_docker: Version
version_compose: Version
healthcheck_start_interval: bool
compose_type: DockerComposeType = "plugin"
def _parse_version(version: str) -> Version:
@@ -88,87 +49,11 @@ 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_version_str, _ = runner.run(
subp_exec("docker-compose", "--version", "--short", collect=True)
)
compose_type = "standalone"
# parse versions
docker_version = _parse_version(info["ServerVersion"])
compose_version = _parse_version(compose_version_str)
# check capabilities
return DockerCapabilities(
version_docker=docker_version,
version_compose=compose_version,
healthcheck_start_interval=docker_version >= Version(25, 0, 0),
compose_type=compose_type,
)
def compose(
capabilities: DockerCapabilities,
*,
port: int,
debugger_port: Optional[int] = None,
# postgres://user:password@host:port/database?option=value
postgres_uri: Optional[str] = None,
) -> str:
if postgres_uri is None:
include_db = True
postgres_uri = DEFAULT_POSTGRES_URI
else:
include_db = False
db = DB.format() if include_db else ""
volumes = (
"""volumes:
langgraph-data:
driver: local
"""
if include_db
else ""
)
if db:
if capabilities.healthcheck_start_interval:
db += """
interval: 60s
start_interval: 1s"""
else:
db += """
interval: 5s"""
compose_str = f"""{volumes}services:
{db}
{DEBUGGER.format(debugger_port=debugger_port) if debugger_port else ""}
langgraph-api:
ports:
- "{port}:8000\""""
if include_db:
compose_str += """
depends_on:
langgraph-postgres:
condition: service_healthy"""
compose_str += f"""
environment:
POSTGRES_URI: {postgres_uri}
"""
if capabilities.healthcheck_start_interval:
compose_str += """ healthcheck:
test: python /api/healthcheck.py
interval: 60s
start_interval: 1s
start_period: 10s"""
return compose_str
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "langgraph-cli"
version = "0.1.42"
version = "0.1.45a0"
description = "CLI for interacting with LangGraph API"
authors = []
license = "MIT"
+81
View File
@@ -0,0 +1,81 @@
import asyncio
import os
from typing import Annotated, Sequence, TypedDict
from langchain_core.language_models.fake_chat_models import FakeListChatModel
from langchain_core.messages import BaseMessage, HumanMessage, ToolMessage
from langgraph.graph import END, StateGraph, add_messages
# check that env var is present
os.environ["SOME_ENV_VAR"]
class AgentState(TypedDict):
some_bytes: bytes
some_byte_array: bytearray
dict_with_bytes: dict[str, bytes]
messages: Annotated[Sequence[BaseMessage], add_messages]
sleep: int
async def call_model(state, config):
if sleep := state.get("sleep"):
await asyncio.sleep(sleep)
messages = state["messages"]
if len(messages) > 1:
assert state["some_bytes"] == b"some_bytes"
assert state["some_byte_array"] == bytearray(b"some_byte_array")
assert state["dict_with_bytes"] == {"more_bytes": b"more_bytes"}
# hacky way to reset model to the "first" response
if isinstance(messages[-1], HumanMessage):
model.i = 0
response = await model.ainvoke(messages)
return {
"messages": [response],
"some_bytes": b"some_bytes",
"some_byte_array": bytearray(b"some_byte_array"),
"dict_with_bytes": {"more_bytes": b"more_bytes"},
}
def call_tool(state):
last_message_content = state["messages"][-1].content
return {
"messages": [
ToolMessage(
f"tool_call__{last_message_content}", tool_call_id="tool_call_id"
)
]
}
def should_continue(state):
messages = state["messages"]
last_message = messages[-1]
if last_message.content == "end":
return END
else:
return "tool"
# NOTE: the model cycles through responses infinitely here
model = FakeListChatModel(responses=["begin", "end"])
workflow = StateGraph(AgentState)
workflow.add_node("agent", call_model)
workflow.add_node("tool", call_tool)
workflow.set_entry_point("agent")
workflow.add_conditional_edges(
"agent",
should_continue,
)
workflow.add_edge("tool", "agent")
graph = workflow.compile()
-111
View File
@@ -1,111 +0,0 @@
import pathlib
from langgraph_cli.cli import prepare_args_and_stdin
from langgraph_cli.config import Config, validate_config
from langgraph_cli.docker import DEFAULT_POSTGRES_URI, DockerCapabilities, Version
from langgraph_cli.util import clean_empty_lines
DEFAULT_DOCKER_CAPABILITIES = DockerCapabilities(
version_docker=Version(26, 1, 1),
version_compose=Version(2, 27, 0),
healthcheck_start_interval=True,
)
def test_prepare_args_and_stdin():
# this basically serves as an end-to-end test for using config and docker helpers
config_path = pathlib.Path("./langgraph.json")
config = validate_config(
Config(dependencies=["."], graphs={"agent": "agent.py:graph"})
)
port = 8000
debugger_port = 8001
actual_args, actual_stdin = prepare_args_and_stdin(
capabilities=DEFAULT_DOCKER_CAPABILITIES,
config_path=config_path,
config=config,
docker_compose="custom-docker-compose.yml",
port=port,
debugger_port=debugger_port,
watch=True,
langgraph_api_path="path/to/langgraph-api",
)
expected_args = [
"--project-directory",
".",
"-f",
"custom-docker-compose.yml",
"-f",
"-",
]
expected_stdin = f"""volumes:
langgraph-data:
driver: local
services:
langgraph-postgres:
image: postgres:16
ports:
- "5433:5432"
environment:
POSTGRES_DB: postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
volumes:
- langgraph-data:/var/lib/postgresql/data
healthcheck:
test: pg_isready -U postgres
start_period: 10s
timeout: 1s
retries: 5
interval: 60s
start_interval: 1s
langgraph-debugger:
image: langchain/langgraph-debugger
restart: on-failure
ports:
- "{debugger_port}:3968"
depends_on:
langgraph-postgres:
condition: service_healthy
langgraph-api:
ports:
- "8000:8000"
depends_on:
langgraph-postgres:
condition: service_healthy
environment:
POSTGRES_URI: {DEFAULT_POSTGRES_URI}
healthcheck:
test: python /api/healthcheck.py
interval: 60s
start_interval: 1s
start_period: 10s
pull_policy: build
build:
context: .
dockerfile_inline: |
FROM langchain/langgraph-api:3.11
ADD . /deps/
RUN pip install -c /api/constraints.txt -e /deps/*
ENV LANGSERVE_GRAPHS='{{"agent": "agent.py:graph"}}'
WORKDIR /deps/
develop:
watch:
- path: langgraph.json
action: rebuild
ignore:
- .langgraph-data
- path: .
action: rebuild
ignore:
- .langgraph-data
- path: path/to/langgraph-api
action: sync+restart
target: /api/langgraph_api\
"""
assert actual_args == expected_args
assert clean_empty_lines(actual_stdin) == expected_stdin
+5 -3
View File
@@ -1,13 +1,15 @@
{
"python_version": "3.12",
"pip_config_file": "pipconfig.txt",
"dockerfile_lines": ["ARG meow"],
"dockerfile_lines": [
"ARG meow"
],
"dependencies": [
"langchain_openai",
"."
],
"graphs": {
"agent": "tests/unit_tests/agent.py:graph"
"agent": "./agent.py:graph"
},
"env": ".env"
}
}
+46 -209
View File
@@ -4,10 +4,10 @@ import pathlib
import click
import pytest
from langgraph_cli.config import config_to_compose, config_to_docker, validate_config
from langgraph_cli.config import config_to_docker, validate_config
from langgraph_cli.util import clean_empty_lines
PATH_TO_CONFIG = pathlib.Path("tests/unit_tests/test_config.json")
PATH_TO_CONFIG = pathlib.Path(__file__).parent / "test_config.json"
def test_validate_config():
@@ -66,18 +66,21 @@ def test_validate_config():
def test_config_to_docker_simple():
graphs = {"agent": "./agent.py:graph"}
actual_docker_stdin = config_to_docker(
PATH_TO_CONFIG, validate_config({"dependencies": ["."], "graphs": graphs})
PATH_TO_CONFIG,
validate_config({"dependencies": ["."], "graphs": graphs}),
"langchain/langgraph-api",
)
expected_docker_stdin = """\
FROM langchain/langgraph-api:3.11
ADD . /deps/__outer_unit_tests/unit_tests
COPY <<EOF /deps/__outer_unit_tests/pyproject.toml
[project]
name = "unit_tests"
version = "0.1"
[tool.setuptools.package-data]
"*" = ["**/*"]
EOF
RUN set -ex && \\
for line in '[project]' \\
'name = "unit_tests"' \\
'version = "0.1"' \\
'[tool.setuptools.package-data]' \\
'"*" = ["**/*"]'; do \\
echo "$line" >> /deps/__outer_unit_tests/pyproject.toml; \\
done
RUN pip install -c /api/constraints.txt -e /deps/*
ENV LANGSERVE_GRAPHS='{"agent": "/deps/__outer_unit_tests/unit_tests/agent.py:graph"}'
WORKDIR /deps/__outer_unit_tests/unit_tests\
@@ -96,18 +99,20 @@ def test_config_to_docker_pipconfig():
"pip_config_file": "pipconfig.txt",
}
),
"langchain/langgraph-api",
)
expected_docker_stdin = """\
FROM langchain/langgraph-api:3.11
ADD pipconfig.txt /pipconfig.txt
ADD . /deps/__outer_unit_tests/unit_tests
COPY <<EOF /deps/__outer_unit_tests/pyproject.toml
[project]
name = "unit_tests"
version = "0.1"
[tool.setuptools.package-data]
"*" = ["**/*"]
EOF
RUN set -ex && \\
for line in '[project]' \\
'name = "unit_tests"' \\
'version = "0.1"' \\
'[tool.setuptools.package-data]' \\
'"*" = ["**/*"]'; do \\
echo "$line" >> /deps/__outer_unit_tests/pyproject.toml; \\
done
RUN PIP_CONFIG_FILE=/pipconfig.txt pip install -c /api/constraints.txt -e /deps/*
ENV LANGSERVE_GRAPHS='{"agent": "/deps/__outer_unit_tests/unit_tests/agent.py:graph"}'
WORKDIR /deps/__outer_unit_tests/unit_tests\
@@ -122,13 +127,16 @@ def test_config_to_docker_invalid_inputs():
config_to_docker(
PATH_TO_CONFIG,
validate_config({"dependencies": ["./missing"], "graphs": graphs}),
"langchain/langgraph-api",
)
# test missing local module
with pytest.raises(FileNotFoundError):
graphs = {"agent": "./missing_agent.py:graph"}
config_to_docker(
PATH_TO_CONFIG, validate_config({"dependencies": ["."], "graphs": graphs})
PATH_TO_CONFIG,
validate_config({"dependencies": ["."], "graphs": graphs}),
"langchain/langgraph-api",
)
@@ -142,17 +150,19 @@ def test_config_to_docker_local_deps():
"graphs": graphs,
}
),
"langchain/langgraph-api-custom",
)
expected_docker_stdin = """\
FROM langchain/langgraph-api:3.11
FROM langchain/langgraph-api-custom:3.11
ADD ./graphs /deps/__outer_graphs/src
COPY <<EOF /deps/__outer_graphs/pyproject.toml
[project]
name = "graphs"
version = "0.1"
[tool.setuptools.package-data]
"*" = ["**/*"]
EOF
RUN set -ex && \\
for line in '[project]' \\
'name = "graphs"' \\
'version = "0.1"' \\
'[tool.setuptools.package-data]' \\
'"*" = ["**/*"]'; do \\
echo "$line" >> /deps/__outer_graphs/pyproject.toml; \\
done
RUN pip install -c /api/constraints.txt -e /deps/*
ENV LANGSERVE_GRAPHS='{"agent": "/deps/__outer_graphs/src/agent.py:graph"}'\
"""
@@ -177,6 +187,7 @@ dependencies = ["langchain"]"""
"graphs": graphs,
}
),
"langchain/langgraph-api",
)
os.remove(pyproject_path)
expected_docker_stdin = """FROM langchain/langgraph-api:3.11
@@ -200,6 +211,7 @@ def test_config_to_docker_end_to_end():
"dockerfile_lines": ["ARG meow", "ARG foo"],
}
),
"langchain/langgraph-api",
)
expected_docker_stdin = """FROM langchain/langgraph-api:3.12
ARG meow
@@ -207,189 +219,14 @@ ARG foo
ADD pipconfig.txt /pipconfig.txt
RUN PIP_CONFIG_FILE=/pipconfig.txt pip install -c /api/constraints.txt langchain langchain_openai
ADD ./graphs/ /deps/__outer_graphs/src
COPY <<EOF /deps/__outer_graphs/pyproject.toml
[project]
name = "graphs"
version = "0.1"
[tool.setuptools.package-data]
"*" = ["**/*"]
EOF
RUN set -ex && \\
for line in '[project]' \\
'name = "graphs"' \\
'version = "0.1"' \\
'[tool.setuptools.package-data]' \\
'"*" = ["**/*"]'; do \\
echo "$line" >> /deps/__outer_graphs/pyproject.toml; \\
done
RUN PIP_CONFIG_FILE=/pipconfig.txt pip install -c /api/constraints.txt -e /deps/*
ENV LANGSERVE_GRAPHS='{"agent": "/deps/__outer_graphs/src/agent.py:graph"}'"""
assert clean_empty_lines(actual_docker_stdin) == expected_docker_stdin
# config_to_compose
def test_config_to_compose_simple_config():
graphs = {"agent": "./agent.py:graph"}
expected_compose_stdin = """\
pull_policy: build
build:
context: .
dockerfile_inline: |
FROM langchain/langgraph-api:3.11
ADD . /deps/__outer_unit_tests/unit_tests
COPY <<EOF /deps/__outer_unit_tests/pyproject.toml
[project]
name = "unit_tests"
version = "0.1"
[tool.setuptools.package-data]
"*" = ["**/*"]
EOF
RUN pip install -c /api/constraints.txt -e /deps/*
ENV LANGSERVE_GRAPHS='{"agent": "/deps/__outer_unit_tests/unit_tests/agent.py:graph"}'
WORKDIR /deps/__outer_unit_tests/unit_tests
"""
actual_compose_stdin = config_to_compose(
PATH_TO_CONFIG, validate_config({"dependencies": ["."], "graphs": graphs})
)
assert clean_empty_lines(actual_compose_stdin) == expected_compose_stdin
def test_config_to_compose_env_vars():
graphs = {"agent": "./agent.py:graph"}
expected_compose_stdin = """ OPENAI_API_KEY: key
pull_policy: build
build:
context: .
dockerfile_inline: |
FROM langchain/langgraph-api:3.11
ADD . /deps/__outer_unit_tests/unit_tests
COPY <<EOF /deps/__outer_unit_tests/pyproject.toml
[project]
name = "unit_tests"
version = "0.1"
[tool.setuptools.package-data]
"*" = ["**/*"]
EOF
RUN pip install -c /api/constraints.txt -e /deps/*
ENV LANGSERVE_GRAPHS='{"agent": "/deps/__outer_unit_tests/unit_tests/agent.py:graph"}'
WORKDIR /deps/__outer_unit_tests/unit_tests
"""
openai_api_key = "key"
actual_compose_stdin = config_to_compose(
PATH_TO_CONFIG,
validate_config(
{
"dependencies": ["."],
"graphs": graphs,
"env": {"OPENAI_API_KEY": openai_api_key},
}
),
)
assert clean_empty_lines(actual_compose_stdin) == expected_compose_stdin
def test_config_to_compose_env_file():
graphs = {"agent": "./agent.py:graph"}
expected_compose_stdin = """\
env_file: .env
pull_policy: build
build:
context: .
dockerfile_inline: |
FROM langchain/langgraph-api:3.11
ADD . /deps/__outer_unit_tests/unit_tests
COPY <<EOF /deps/__outer_unit_tests/pyproject.toml
[project]
name = "unit_tests"
version = "0.1"
[tool.setuptools.package-data]
"*" = ["**/*"]
EOF
RUN pip install -c /api/constraints.txt -e /deps/*
ENV LANGSERVE_GRAPHS='{"agent": "/deps/__outer_unit_tests/unit_tests/agent.py:graph"}'
WORKDIR /deps/__outer_unit_tests/unit_tests
"""
actual_compose_stdin = config_to_compose(
PATH_TO_CONFIG,
validate_config({"dependencies": ["."], "graphs": graphs, "env": ".env"}),
)
assert clean_empty_lines(actual_compose_stdin) == expected_compose_stdin
def test_config_to_compose_watch():
graphs = {"agent": "./agent.py:graph"}
expected_compose_stdin = """\
pull_policy: build
build:
context: .
dockerfile_inline: |
FROM langchain/langgraph-api:3.11
ADD . /deps/__outer_unit_tests/unit_tests
COPY <<EOF /deps/__outer_unit_tests/pyproject.toml
[project]
name = "unit_tests"
version = "0.1"
[tool.setuptools.package-data]
"*" = ["**/*"]
EOF
RUN pip install -c /api/constraints.txt -e /deps/*
ENV LANGSERVE_GRAPHS='{"agent": "/deps/__outer_unit_tests/unit_tests/agent.py:graph"}'
WORKDIR /deps/__outer_unit_tests/unit_tests
develop:
watch:
- path: tests/unit_tests/test_config.json
action: rebuild
ignore:
- .langgraph-data
- path: tests/unit_tests
action: rebuild
ignore:
- .langgraph-data\
"""
actual_compose_stdin = config_to_compose(
PATH_TO_CONFIG,
validate_config({"dependencies": ["."], "graphs": graphs}),
watch=True,
)
assert clean_empty_lines(actual_compose_stdin) == expected_compose_stdin
def test_config_to_compose_end_to_end():
# test all of the above + langgraph API path
graphs = {"agent": "./agent.py:graph"}
expected_compose_stdin = """\
env_file: .env
pull_policy: build
build:
context: .
dockerfile_inline: |
FROM langchain/langgraph-api:3.11
ADD . /deps/__outer_unit_tests/unit_tests
COPY <<EOF /deps/__outer_unit_tests/pyproject.toml
[project]
name = "unit_tests"
version = "0.1"
[tool.setuptools.package-data]
"*" = ["**/*"]
EOF
RUN pip install -c /api/constraints.txt -e /deps/*
ENV LANGSERVE_GRAPHS='{"agent": "/deps/__outer_unit_tests/unit_tests/agent.py:graph"}'
WORKDIR /deps/__outer_unit_tests/unit_tests
develop:
watch:
- path: tests/unit_tests/test_config.json
action: rebuild
ignore:
- .langgraph-data
- path: tests/unit_tests
action: rebuild
ignore:
- .langgraph-data
- path: path/to/langgraph/api
action: sync+restart
target: /api/langgraph_api\
"""
actual_compose_stdin = config_to_compose(
PATH_TO_CONFIG,
validate_config({"dependencies": ["."], "graphs": graphs, "env": ".env"}),
watch=True,
langgraph_api_path="path/to/langgraph/api",
)
assert clean_empty_lines(actual_compose_stdin) == expected_compose_stdin
-101
View File
@@ -1,101 +0,0 @@
from langgraph_cli.docker import (
DEFAULT_POSTGRES_URI,
DockerCapabilities,
Version,
compose,
)
from langgraph_cli.util import clean_empty_lines
DEFAULT_DOCKER_CAPABILITIES = DockerCapabilities(
version_docker=Version(26, 1, 1),
version_compose=Version(2, 27, 0),
healthcheck_start_interval=False,
)
def test_compose_with_no_debugger_and_custom_db():
port = 8123
custom_postgres_uri = "custom_postgres_uri"
actual_compose_str = compose(
DEFAULT_DOCKER_CAPABILITIES, port=port, postgres_uri=custom_postgres_uri
)
expected_compose_str = f"""services:
langgraph-api:
ports:
- "{port}:8000"
environment:
POSTGRES_URI: {custom_postgres_uri}"""
assert clean_empty_lines(actual_compose_str) == expected_compose_str
def test_compose_with_no_debugger_and_custom_db_with_healthcheck():
port = 8123
custom_postgres_uri = "custom_postgres_uri"
actual_compose_str = compose(
DEFAULT_DOCKER_CAPABILITIES._replace(healthcheck_start_interval=True),
port=port,
postgres_uri=custom_postgres_uri,
)
expected_compose_str = f"""services:
langgraph-api:
ports:
- "{port}:8000"
environment:
POSTGRES_URI: {custom_postgres_uri}
healthcheck:
test: python /api/healthcheck.py
interval: 60s
start_interval: 1s
start_period: 10s"""
assert clean_empty_lines(actual_compose_str) == expected_compose_str
def test_compose_with_debugger_and_custom_db():
port = 8123
custom_postgres_uri = "custom_postgres_uri"
actual_compose_str = compose(
DEFAULT_DOCKER_CAPABILITIES,
port=port,
postgres_uri=custom_postgres_uri,
)
expected_compose_str = f"""services:
langgraph-api:
ports:
- "{port}:8000"
environment:
POSTGRES_URI: {custom_postgres_uri}"""
assert clean_empty_lines(actual_compose_str) == expected_compose_str
def test_compose_with_debugger_and_default_db():
port = 8123
actual_compose_str = compose(DEFAULT_DOCKER_CAPABILITIES, port=port)
expected_compose_str = f"""volumes:
langgraph-data:
driver: local
services:
langgraph-postgres:
image: postgres:16
ports:
- "5433:5432"
environment:
POSTGRES_DB: postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
volumes:
- langgraph-data:/var/lib/postgresql/data
healthcheck:
test: pg_isready -U postgres
start_period: 10s
timeout: 1s
retries: 5
interval: 5s
langgraph-api:
ports:
- "{port}:8000"
depends_on:
langgraph-postgres:
condition: service_healthy
environment:
POSTGRES_URI: {DEFAULT_POSTGRES_URI}"""
assert clean_empty_lines(actual_compose_str) == expected_compose_str
+4 -3
View File
@@ -72,7 +72,7 @@ tool_node = ToolNode(tools)
model = ChatOpenAI(temperature=0).bind_tools(tools)
# Define the function that determines whether to continue or not
def should_continue(state: AgentState) -> Literal["tools", END]:
def should_continue(state: MessagesState) -> Literal["tools", END]:
messages = state['messages']
last_message = messages[-1]
# If the LLM makes a tool call, then we route to the "tools" node
@@ -83,7 +83,7 @@ def should_continue(state: AgentState) -> Literal["tools", END]:
# Define the function that calls the model
def call_model(state: AgentState):
def call_model(state: MessagesState):
messages = state['messages']
response = model.invoke(messages)
# We return a list, because this will get added to the existing list
@@ -215,4 +215,5 @@ final_state["messages"][-1].content
* [Tutorials](https://langchain-ai.github.io/langgraph/tutorials/): Learn to build with LangGraph through guided examples.
* [How-to Guides](https://langchain-ai.github.io/langgraph/how-tos/): Accomplish specific things within LangGraph, from streaming, to adding memory & persistence, to common design patterns (branching, subgraphs, etc.), these are the place to go if you want to copy and run a specific code snippet.
* [Conceptual Guides](https://langchain-ai.github.io/langgraph/concepts/): In-depth explanations of the key concepts and principles behind LangGraph, such as nodes, edges, state and more.
* [API Reference](https://langchain-ai.github.io/langgraph/reference/graphs/): Review important classes and methods, simple examples of how to use the graph and checkpointing APIs, higher-level prebuilt components and more.
* [API Reference](https://langchain-ai.github.io/langgraph/reference/graphs/): Review important classes and methods, simple examples of how to use the graph and checkpointing APIs, higher-level prebuilt components and more.
* [Cloud (alpha)](https://langchain-ai.github.io/langgraph/cloud/): With one click, deploy LangGraph applications to LangGraph Cloud.
@@ -27,7 +27,7 @@ def not_implemented_sync_method(func: T) -> T:
"The AsyncSqliteSaver does not support synchronous methods. "
"Consider using the SqliteSaver instead.\n"
"from langgraph.checkpoint.sqlite import SqliteSaver\n"
"See https://langchain-ai.github.io/langgraph/reference/checkpoints/#sqlitesaver "
"See https://langchain-ai.github.io/langgraph/reference/checkpoints/langgraph.checkpoint.sqlite.SqliteSaver "
"for more information."
)
@@ -135,7 +135,7 @@ class AsyncSqliteSaver(BaseCheckpointSaver, AbstractAsyncContextManager):
Note:
This method is not implemented for the AsyncSqliteSaver. Use `aget` instead.
Or consider using the [SqliteSaver](#sqlitesaver) checkpointer.
Or consider using the [SqliteSaver][sqlitesaver] checkpointer.
"""
@not_implemented_sync_method
@@ -151,7 +151,7 @@ class AsyncSqliteSaver(BaseCheckpointSaver, AbstractAsyncContextManager):
Note:
This method is not implemented for the AsyncSqliteSaver. Use `alist` instead.
Or consider using the [SqliteSaver](#sqlitesaver) checkpointer.
Or consider using the [SqliteSaver][sqlitesaver] checkpointer.
"""
@not_implemented_sync_method
@@ -56,7 +56,7 @@ _AIO_ERROR_MSG = (
"from langgraph.checkpoint.aiosqlite import AsyncSqliteSaver\n"
"Note: AsyncSqliteSaver requires the aiosqlite package to use.\n"
"Install with:\n`pip install aiosqlite`\n"
"See https://langchain-ai.github.io/langgraph/reference/checkpoints/#asyncsqlitesaver"
"See https://langchain-ai.github.io/langgraph/reference/checkpoints/asyncsqlitesaver"
"for more information."
)
@@ -69,7 +69,7 @@ class SqliteSaver(BaseCheckpointSaver, AbstractContextManager):
(demos and small projects) and does not
scale to multiple threads.
For a similar sqlite saver with `async` support,
consider using AsyncSqliteSaver.
consider using [AsyncSqliteSaver][asyncsqlitesaver].
Args:
conn (sqlite3.Connection): The SQLite database connection.
@@ -399,7 +399,7 @@ class SqliteSaver(BaseCheckpointSaver, AbstractContextManager):
Note:
This async method is not supported by the SqliteSaver class.
Use get_tuple() instead, or consider using [AsyncSqliteSaver](#asyncsqlitesaver).
Use get_tuple() instead, or consider using [AsyncSqliteSaver][asyncsqlitesaver].
"""
raise NotImplementedError(_AIO_ERROR_MSG)
@@ -415,7 +415,7 @@ class SqliteSaver(BaseCheckpointSaver, AbstractContextManager):
Note:
This async method is not supported by the SqliteSaver class.
Use list() instead, or consider using [AsyncSqliteSaver](#asyncsqlitesaver).
Use list() instead, or consider using [AsyncSqliteSaver][asyncsqlitesaver].
"""
raise NotImplementedError(_AIO_ERROR_MSG)
yield
@@ -430,7 +430,7 @@ class SqliteSaver(BaseCheckpointSaver, AbstractContextManager):
Note:
This async method is not supported by the SqliteSaver class.
Use put() instead, or consider using [AsyncSqliteSaver](#asyncsqlitesaver).
Use put() instead, or consider using [AsyncSqliteSaver][asyncsqlitesaver].
"""
raise NotImplementedError(_AIO_ERROR_MSG)
+44 -3
View File
@@ -154,6 +154,45 @@ class StateGraph(Graph):
def add_node(
self, node: Union[str, RunnableLike], action: Optional[RunnableLike] = None
) -> None:
"""Adds a new node to the state graph.
Will take the name of the function/runnable as the node name.
Args:
node (Union[str, RunnableLike)]: The function or runnable this node will run.
action (Optional[RunnableLike]): The action associated with the node. (default: None)
Raises:
ValueError: If the key is already being used as a state key.
Examples:
```pycon
>>> from langgraph.graph import START, StateGraph
...
>>> def my_node(state, config):
... return {"x": state["x"] + 1}
...
>>> builder = StateGraph(dict)
>>> builder.add_node(my_node) # node name will be 'my_node'
>>> builder.add_edge(START, "my_node")
>>> graph = builder.compile()
>>> graph.invoke({"x": 1})
{'x': 2}
```
Customize the name:
```pycon
>>> builder = StateGraph(dict)
>>> builder.add_node("my_fair_node", my_node)
>>> builder.add_edge(START, "my_fair_node")
>>> graph = builder.compile()
>>> graph.invoke({"x": 1})
{'x': 2}
```
Returns:
None
"""
if not isinstance(node, str):
action = node
if isinstance(action, Runnable):
@@ -393,9 +432,11 @@ class CompiledStateGraph(CompiledGraph):
def branch_writer(packets: list[Union[str, Send]]) -> Optional[ChannelWrite]:
if filtered := [p for p in packets if p != END]:
writes = [
ChannelWriteEntry(f"branch:{start}:{name}:{p}", start)
if not isinstance(p, Send)
else p
(
ChannelWriteEntry(f"branch:{start}:{name}:{p}", start)
if not isinstance(p, Send)
else p
)
for p in filtered
]
if branch.then and branch.then != END:
+22 -3
View File
@@ -22,6 +22,7 @@ from langgraph_sdk.schema import (
StreamMode,
Thread,
ThreadState,
ThreadStatus,
)
logger = logging.getLogger(__name__)
@@ -271,15 +272,22 @@ class AssistantsClient:
await self.http.delete(f"/assistants/{assistant_id}")
async def search(
self, *, metadata: Metadata = None, limit: int = 10, offset: int = 0
self,
*,
metadata: Metadata = None,
graph_id: Optional[str] = None,
limit: int = 10,
offset: int = 0,
) -> list[Assistant]:
"""Search for assistants."""
payload: Dict[str, Any] = {
"limit": limit,
"offset": offset,
}
if metadata:
payload["metadata"] = metadata
"""Search for assistants."""
if graph_id:
payload["graph_id"] = graph_id
return await self.http.post(
"/assistants/search",
json=payload,
@@ -319,7 +327,12 @@ class ThreadsClient:
await self.http.delete(f"/threads/{thread_id}")
async def search(
self, *, metadata: Metadata = None, limit: int = 10, offset: int = 0
self,
*,
metadata: Metadata = None,
status: Optional[ThreadStatus] = None,
limit: int = 10,
offset: int = 0,
) -> list[Thread]:
"""Search for threads."""
payload: Dict[str, Any] = {
@@ -328,6 +341,8 @@ class ThreadsClient:
}
if metadata:
payload["metadata"] = metadata
if status:
payload["status"] = status
return await self.http.post(
"/threads/search",
json=payload,
@@ -444,6 +459,7 @@ class RunsClient:
interrupt_before: Optional[list[str]] = None,
interrupt_after: Optional[list[str]] = None,
feedback_keys: Optional[list[str]] = None,
webhook: Optional[str] = None,
multitask_strategy: Optional[MultitaskStrategy] = None,
) -> AsyncIterator[StreamPart]:
"""Create a run and stream the results."""
@@ -456,6 +472,7 @@ class RunsClient:
"interrupt_before": interrupt_before,
"interrupt_after": interrupt_after,
"feedback_keys": feedback_keys,
"webhook": webhook,
"checkpoint_id": checkpoint_id,
"multitask_strategy": multitask_strategy,
}
@@ -573,6 +590,7 @@ class RunsClient:
checkpoint_id: Optional[str] = None,
interrupt_before: Optional[list[str]] = None,
interrupt_after: Optional[list[str]] = None,
webhook: Optional[str] = None,
multitask_strategy: Optional[MultitaskStrategy] = None,
) -> Union[list[dict], dict[str, Any]]:
"""Create a run, wait for and return the final state."""
@@ -583,6 +601,7 @@ class RunsClient:
"assistant_id": assistant_id,
"interrupt_before": interrupt_before,
"interrupt_after": interrupt_after,
"webhook": webhook,
"checkpoint_id": checkpoint_id,
"multitask_strategy": multitask_strategy,
}
+7 -3
View File
@@ -5,6 +5,8 @@ Metadata = Optional[dict[str, Any]]
RunStatus = Literal["pending", "running", "error", "success", "timeout", "interrupted"]
ThreadStatus = Literal["idle", "busy", "interrupted"]
StreamMode = Literal["values", "messages", "updates", "events", "debug"]
MultitaskStrategy = Literal["reject", "interrupt", "rollback", "enqueue"]
@@ -70,8 +72,8 @@ class Thread(TypedDict):
"""The last time the thread was updated."""
metadata: Metadata
"""The thread metadata."""
multitask_strategy: MultitaskStrategy
"""The multitask strategy for this thread."""
status: ThreadStatus
"""The status of the thread, one of 'idle', 'busy', 'interrupted'."""
class ThreadState(TypedDict):
@@ -102,6 +104,8 @@ class Run(TypedDict):
updated_at: datetime
"""The last time the run was updated."""
status: RunStatus
"""The status of the run. One of 'pending', 'running', 'error', 'success'."""
"""The status of the run. One of 'pending', 'running', "error", 'success', "timeout", "interrupted"."""
metadata: Metadata
"""The run metadata."""
multitask_strategy: MultitaskStrategy
"""Strategy to handle concurrent runs on the same thread."""
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "langgraph-sdk"
version = "0.1.23"
version = "0.1.25"
description = "SDK for interacting with LangGraph API"
authors = []
license = "MIT"