mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-18 05:35:43 +02:00
Compare commits
25
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
307c0cfe09 | ||
|
|
c6466da098 | ||
|
|
32e8a2dc50 | ||
|
|
8d103667c8 | ||
|
|
2657ebb57c | ||
|
|
33d099989b | ||
|
|
29a040dc21 | ||
|
|
5697f07163 | ||
|
|
fb5f3c972a | ||
|
|
86c556d01e | ||
|
|
b7580abf45 | ||
|
|
72ac58d1c2 | ||
|
|
000a5c5b83 | ||
|
|
6caea79fba | ||
|
|
fae97d9fba | ||
|
|
d16ec4f436 | ||
|
|
66b728e83a | ||
|
|
feca5e1970 | ||
|
|
c217e4a58d | ||
|
|
fcdf7a8ced | ||
|
|
720ea986d3 | ||
|
|
ab54ae2c23 | ||
|
|
a5650e1d88 | ||
|
|
e49f3f5434 | ||
|
|
f9c720f25a |
@@ -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 : $?))")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 |
@@ -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 |
@@ -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
@@ -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 it’s 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.
|
||||
|
||||

|
||||
|
||||
***If you have not deployed to LangGraph Cloud before:*** there will be a button that shows up saying Import from GitHub. You’ll 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:
|
||||
|
||||

|
||||

|
||||
|
||||
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:
|
||||
|
||||

|
||||

|
||||
|
||||
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:
|
||||
|
||||

|
||||

|
||||
|
||||
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:
|
||||
|
||||

|
||||

|
||||
|
||||
You won’t 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 won’t 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:
|
||||
|
||||

|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
## 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:
|
||||
|
||||

|
||||
|
||||
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": [
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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 |
@@ -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?`:"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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": [
|
||||
""
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"language_info": {
|
||||
"name": "python"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
@@ -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."
|
||||
]
|
||||
|
||||
@@ -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."
|
||||
]
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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,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,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"
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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,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,
|
||||
}
|
||||
|
||||
@@ -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,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"
|
||||
|
||||
Reference in New Issue
Block a user