Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
794a0fff03 | ||
|
|
06ed6d7cab | ||
|
|
6eacc6b7c8 | ||
|
|
f6ac881591 | ||
|
|
f431b415fc | ||
|
|
4b51c27461 | ||
|
|
585c5c41ce | ||
|
|
c64588a673 | ||
|
|
75fa7395bd | ||
|
|
66ad48e771 | ||
|
|
41fd8020ee | ||
|
|
09a28ccef6 | ||
|
|
c0431227d8 | ||
|
|
770e1601e5 | ||
|
|
045c2af663 | ||
|
|
23d3a7ac07 | ||
|
|
67d00aca90 | ||
|
|
e54989ca74 | ||
|
|
14372a4515 | ||
|
|
6e33bda433 | ||
|
|
77eb88eef2 | ||
|
|
7584f058c2 | ||
|
|
fba6e0504c | ||
|
|
c17fe2d189 | ||
|
|
4a58dcccf2 | ||
|
|
ed2e1a736f | ||
|
|
a168615f2d | ||
|
|
190372e137 | ||
|
|
eba8303c98 | ||
|
|
2845d7ace5 | ||
|
|
2ceac211e7 | ||
|
|
8f6b3b636d |
@@ -0,0 +1,28 @@
|
||||
name: Check File Size
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
file-size-check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v44
|
||||
- name: Filter by size
|
||||
run: |
|
||||
large_added_files=$(find ${{ steps.changed-files.outputs.added_files }} -maxdepth 0 -size +1M)
|
||||
if [ -n "$large_added_files" ]; then
|
||||
echo "Large files added: $large_added_files"
|
||||
echo "# Large files added:" >> $GITHUB_STEP_SUMMARY
|
||||
echo "$large_added_files" >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
fi
|
||||
@@ -3,7 +3,6 @@
|
||||

|
||||
[](https://pepy.tech/project/langgraph)
|
||||
[](https://github.com/langchain-ai/langgraph/issues)
|
||||
[](https://discord.com/channels/1038097195422978059/1170024642245832774)
|
||||
[](https://langchain-ai.github.io/langgraph/)
|
||||
|
||||
⚡ Building language agents as graphs ⚡
|
||||
|
||||
@@ -12,6 +12,10 @@ An assistant is a configured instance of a [`CompiledGraph`][compiledgraph]. It
|
||||
|
||||
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.
|
||||
|
||||
#### Configuring Assistants
|
||||
|
||||
You can save custom assistants from the same graph to set different default prompts, models, and other configurations without changing a line of code in your graph. This allows you the ability to quickly test out different configurations without having to rewrite your graph every time, and also give users the flexibility to select different configurations when using your LangGraph application. See <a href="https://langchain-ai.github.io/langgraph/cloud/how-tos/cloud_examples/configuration_cloud/">this</a> how-to for information on how to configure a deployed graph.
|
||||
|
||||
### Threads
|
||||
|
||||
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.
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
# Rebuild Graph at Runtime
|
||||
|
||||
You might need to rebuild your graph with a different configuration for a new run. For example, you might need to use a different graph state or graph structure depending on the config. This guide shows how you can do this.
|
||||
|
||||
!!! note "Note"
|
||||
In most cases, customizing behavior based on the config should be handled by a single graph where each node can read a config and change its behavior based on it
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Make sure to check out [this how-to guide](./setup.md) on setting up your app for deployment first.
|
||||
|
||||
## Define graphs
|
||||
|
||||
Let's say you have an app with a simple graph that calls an LLM and returns the response to the user. The app file directory looks like the following:
|
||||
|
||||
```
|
||||
my-app/
|
||||
|-- requirements.txt
|
||||
|-- .env
|
||||
|-- openai_agent.py # code for your graph
|
||||
```
|
||||
|
||||
where the graph is defined in `openai_agent.py`.
|
||||
|
||||
### No rebuild
|
||||
|
||||
In the standard LangGraph API configuration, the server uses the compiled graph instance that's defined at the top level of `openai_agent.py`, which looks like the following:
|
||||
|
||||
```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)
|
||||
graph_workflow.add_edge("agent", END)
|
||||
graph_workflow.set_entry_point("agent")
|
||||
|
||||
agent = graph_workflow.compile()
|
||||
```
|
||||
|
||||
To make the server aware of your graph, you need to specify a path to the variable that contains the `CompiledStateGraph` instance in your LangGraph API configuration (`langgraph.json`), e.g.:
|
||||
|
||||
```
|
||||
{
|
||||
"dependencies": ["."],
|
||||
"graphs": {
|
||||
"openai_agent": "./openai_agent.py:agent",
|
||||
},
|
||||
"env": "./.env"
|
||||
}
|
||||
```
|
||||
|
||||
### Rebuild
|
||||
|
||||
To make your graph rebuild on each new run with custom configuration, you need to rewrite `openai_agent.py` to instead provide a _function_ that takes a config and returns a graph (or compiled graph) instance. Let's say we want to return our existing graph for user ID '1', and a tool-calling agent for other users. We can modify `openai_agent.py` as follows:
|
||||
|
||||
```python
|
||||
from typing import Annotated, TypedDict
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langgraph.graph import END, MessageGraph
|
||||
from langgraph.graph.state import StateGraph
|
||||
from langgraph.graph.message import add_messages
|
||||
from langgraph.prebuilt import ToolNode
|
||||
from langchain_core.tools import tool
|
||||
from langchain_core.messages import BaseMessage
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
|
||||
|
||||
class State(TypedDict):
|
||||
messages: Annotated[list[BaseMessage], add_messages]
|
||||
|
||||
|
||||
model = ChatOpenAI(temperature=0)
|
||||
|
||||
def make_default_graph():
|
||||
"""Make a simple LLM agent"""
|
||||
graph_workflow = StateGraph(State)
|
||||
def call_model(state):
|
||||
return {"messages": [model.invoke(state["messages"])]}
|
||||
|
||||
graph_workflow.add_node("agent", call_model)
|
||||
graph_workflow.add_edge("agent", END)
|
||||
graph_workflow.set_entry_point("agent")
|
||||
|
||||
agent = graph_workflow.compile()
|
||||
return agent
|
||||
|
||||
|
||||
def make_alternative_graph():
|
||||
"""Make a tool-calling agent"""
|
||||
|
||||
@tool
|
||||
def add(a: float, b: float):
|
||||
"""Adds two numbers."""
|
||||
return a + b
|
||||
|
||||
tool_node = ToolNode([add])
|
||||
model_with_tools = model.bind_tools([add])
|
||||
def call_model(state):
|
||||
return {"messages": [model_with_tools.invoke(state["messages"])]}
|
||||
|
||||
def should_continue(state: State):
|
||||
if state["messages"][-1].tool_calls:
|
||||
return "tools"
|
||||
else:
|
||||
return END
|
||||
|
||||
graph_workflow = StateGraph(State)
|
||||
|
||||
graph_workflow.add_node("agent", call_model)
|
||||
graph_workflow.add_node("tools", tool_node)
|
||||
graph_workflow.add_edge("tools", "agent")
|
||||
graph_workflow.set_entry_point("agent")
|
||||
graph_workflow.add_conditional_edges("agent", should_continue)
|
||||
|
||||
agent = graph_workflow.compile()
|
||||
return agent
|
||||
|
||||
|
||||
# this is the graph making function that will decide which graph to
|
||||
# build based on the provided config
|
||||
def make_graph(config: RunnableConfig):
|
||||
user_id = config.get("configurable", {}).get("user_id")
|
||||
# route to different graph state / structure based on the user ID
|
||||
if user_id == "1":
|
||||
return make_default_graph()
|
||||
else:
|
||||
return make_alternative_graph()
|
||||
```
|
||||
|
||||
Finally, you need to specify the path to your graph-making function (`make_graph`) in `langgraph.json`:
|
||||
|
||||
```
|
||||
{
|
||||
"dependencies": ["."],
|
||||
"graphs": {
|
||||
"openai_agent": "./openai_agent.py:make_graph",
|
||||
},
|
||||
"env": "./.env"
|
||||
}
|
||||
```
|
||||
|
||||
See more info on LangGraph API configuration file [here](../reference/cli.md#configuration-file)
|
||||
@@ -88,7 +88,7 @@ agent = graph_workflow.compile()
|
||||
```
|
||||
|
||||
!!! warning "Assign `CompiledGraph` to Variable"
|
||||
The build process for LangGraph Cloud requires that the `CompiledGraph` object be assigned to a variable at the top-level of a Python module.
|
||||
The build process for LangGraph Cloud requires that the `CompiledGraph` object be assigned to a variable at the top-level of a Python module (alternatively, you can provide [a function that creates a graph](./graph_rebuild.md)).
|
||||
|
||||
Example file directory:
|
||||
```
|
||||
|
||||
|
Before Width: | Height: | Size: 20 MiB |
|
After Width: | Height: | Size: 721 KiB |
|
Before Width: | Height: | Size: 15 MiB |
|
After Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 26 MiB |
|
After Width: | Height: | Size: 267 KiB |
|
Before Width: | Height: | Size: 4.9 MiB |
|
After Width: | Height: | Size: 355 KiB |
@@ -8,6 +8,8 @@ The LangGraph Studio lets you test different configurations and inputs to your g
|
||||
1. Select `Submit` to invoke the selected assistant.
|
||||
1. View output of the invocation in the right-hand pane.
|
||||
|
||||
The following GIF shows these exact steps being carried out:
|
||||
The following video shows these exact steps being carried out:
|
||||
|
||||

|
||||
<video controls allowfullscreen="true" poster="../img/studio_input_poster.png">
|
||||
<source src="../img/studio_input.mp4" type="video/mp4">
|
||||
</video>
|
||||
|
||||
@@ -9,6 +9,8 @@ Starting from the <a href="https://smith.langchain.com/" target="_blank">LangSmi
|
||||
1. In the top-right corner, select `Open LangGraph Studio`.
|
||||
1. [Invoke an assistant](./invoke_studio.md) or [view an existing thread](./threads_studio.md).
|
||||
|
||||
The following GIF shows these exact steps being carried out:
|
||||
The following video shows these exact steps being carried out:
|
||||
|
||||

|
||||
<video controls allowfullscreen="true" poster="../img/studio_usage_poster.png">
|
||||
<source src="../img/studio_usage.mp4" type="video/mp4">
|
||||
</video>
|
||||
|
||||
@@ -6,14 +6,18 @@
|
||||
1. View the state of the thread (i.e. the output) in the right-hand pane.
|
||||
1. To create a new thread, select `+ New Thread`.
|
||||
|
||||
The following GIF shows these exact steps being carried out:
|
||||
The following video shows these exact steps being carried out:
|
||||
|
||||

|
||||
<video controls="true" allowfullscreen="true" poster="../img/studio_threads_poster.png">
|
||||
<source src="../img/studio_threads.mp4" type="video/mp4">
|
||||
</video>
|
||||
|
||||
## Edit Thread State
|
||||
|
||||
The LangGraph Studio UI contains features for editing thread state. Explore these features in the right-hand pane. Select the `Edit` icon, modify the desired state, and then select `Fork` to invoke the assistant with the updated state.
|
||||
|
||||
The following GIF shows how to edit a thread in the studio:
|
||||
The following video shows how to edit a thread in the studio:
|
||||
|
||||

|
||||
<video controls allowfullscreen="true" poster="../img/studio_forks_poster.png">
|
||||
<source src="../img/studio_forks.mp4" type="video/mp4">
|
||||
</video>
|
||||
|
||||
@@ -12,7 +12,11 @@
|
||||
!!! warning "Under Construction"
|
||||
LangGraph Cloud documentation is under construction. Contents may change until general availability.
|
||||
|
||||

|
||||
|
||||
<video controls preload="auto" allowfullscreen="true" poster="how-tos/img/studio_forks_poster.png">
|
||||
<source src="how-tos/img/studio_forks.mp4" type="video/mp4">
|
||||
</video>
|
||||
|
||||
|
||||
## Overview
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ The LangGraph CLI requires a JSON configuration file with the following keys:
|
||||
| Key | Description |
|
||||
| --- | ----------- |
|
||||
| `dependencies` | **Required**. Array of dependencies for LangGraph Cloud API server. Dependencies can be one of the following: (1) `"."`, which will look for local Python packages, (2) `pyproject.toml`, `setup.py` or `requirements.txt` in the app directory `"./local_package"`, or (3) a package name. |
|
||||
| `graphs` | **Required**. Mapping from graph ID to path where the compiled graph is defined. Example: `./your_package/your_file.py:variable`, where `variable` is an instance of `langgraph.graph.graph.CompiledGraph`. |
|
||||
| `graphs` | **Required**. Mapping from graph ID to path where the compiled graph or a function that makes a graph is defined. Example: <ul><li>`./your_package/your_file.py:variable`, where `variable` is an instance of `langgraph.graph.state.CompiledStateGraph`</li><li>`./your_package/your_file.py:make_graph`, where `make_graph` is a function that takes a config dictionary (`langchain_core.runnables.RunnableConfig`) and creates an instance of `langgraph.graph.state.StateGraph` / `langgraph.graph.state.CompiledStateGraph`.</li></ul> |
|
||||
| `env` | Path to `.env` file or a mapping from environment variable to its value. |
|
||||
| `python_version` | `3.11` or `3.12`. Defaults to `3.11`. |
|
||||
| `pip_config_file`| Path to `pip` config file. |
|
||||
@@ -49,7 +49,7 @@ Example:
|
||||
"."
|
||||
],
|
||||
"graphs": {
|
||||
"my_graph_id": "./your_package/your_file.py:variable"
|
||||
"my_graph_id": "./your_package/your_file.py:make_graph"
|
||||
},
|
||||
"env": {
|
||||
"OPENAI_API_KEY": "secret-key"
|
||||
|
||||
@@ -61,6 +61,13 @@ These guides show how to use different streaming modes.
|
||||
- [How to pass graph state to tools](pass-run-time-values-to-tools.ipynb)
|
||||
- [How to pass config to tools](pass-config-to-tools.ipynb)
|
||||
|
||||
## State Management
|
||||
|
||||
- [Use Pydantic model as state](state-model.ipynb)
|
||||
- [Use a context object in state](state-context-key.ipynb)
|
||||
- [Have a separate input and output schema](input_output_schema.ipynb)
|
||||
- [Pass private state between nodes inside the graph](pass_private_state.ipynb)
|
||||
|
||||
## Other
|
||||
|
||||
- [How to run graph asynchronously](async.ipynb)
|
||||
|
||||
@@ -192,6 +192,7 @@ nav:
|
||||
- Deployment:
|
||||
- Setup App: "cloud/deployment/setup.md"
|
||||
- Setup App (pyproject.toml): "cloud/deployment/setup_pyproject.md"
|
||||
- Rebuild Graph at Runtime: "cloud/deployment/graph_rebuild.md"
|
||||
- Test App Locally: "cloud/deployment/test_locally.md"
|
||||
- Deploy to Cloud: "cloud/deployment/cloud.md"
|
||||
- Self-Host: "cloud/deployment/self_hosted.md"
|
||||
|
||||
|
Before Width: | Height: | Size: 140 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 322 KiB After Width: | Height: | Size: 432 KiB |
@@ -50,7 +50,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdin",
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"OPENAI_API_KEY: ········\n"
|
||||
@@ -991,7 +991,7 @@
|
||||
"id": "08996d90-a3ff-4655-9763-1dd4971344d4",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### With LangGraph Clound"
|
||||
"### With LangGraph Cloud"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
|
Before Width: | Height: | Size: 3.6 MiB After Width: | Height: | Size: 616 KiB |
|
Before Width: | Height: | Size: 3.8 MiB After Width: | Height: | Size: 523 KiB |
|
Before Width: | Height: | Size: 4.2 MiB After Width: | Height: | Size: 562 KiB |
|
Before Width: | Height: | Size: 3.4 MiB After Width: | Height: | Size: 422 KiB |
|
Before Width: | Height: | Size: 3.6 MiB After Width: | Height: | Size: 613 KiB |
@@ -84,8 +84,9 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "ef7bcad1-1274-4b7c-a2e9-365180ef3a31",
|
||||
"id": "9c374e41-f9b7-439e-a520-6d8c853c5220",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Part 1: Build a Basic Chatbot\n",
|
||||
@@ -120,13 +121,24 @@
|
||||
"graph_builder = StateGraph(State)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "31c755cd-8994-4867-bdff-96a55d7beae7",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div class=\"admonition tip\">\n",
|
||||
" <p class=\"admonition-title\">Note</p>\n",
|
||||
" <p>\n",
|
||||
" The first thing you do when you define a graph is define the <code>State</code> of the graph. The <code>State</code> consists of the schema of the graph as well as reducer functions which specify how to apply updates to the state. In our example <code>State</code> is a <code>TypedDict</code> with a single key: <code>messages</code>. The <code>messages</code> key is annotated with the <a href=\"https://langchain-ai.github.io/langgraph/reference/graphs/?h=add+messages#add_messages\"><code>add_messages</code></a> reducer function, which tells LangGraph to append new messages to the existing list, rather than overwriting it. State keys without an annotation will be overwritten by each update, storing the most recent value. Check out <a href=\"https://langchain-ai.github.io/langgraph/reference/graphs/?h=add+messages#add_messages\">this conceptual guide</a> to learn more about state, reducers and other low-level concepts.\n",
|
||||
" </p>\n",
|
||||
"</div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "4137feed-746e-4c72-a34a-f7a699ad5dcf",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"**Notice** that we've defined our `State` as a TypedDict with a single key: `messages`. The `messages` key is annotated with the [`add_messages`](https://langchain-ai.github.io/langgraph/reference/graphs/?h=add+messages#add_messages) function, which tells LangGraph to append new messages to the existing list, rather than overwriting it.\n",
|
||||
"\n",
|
||||
"So now our graph knows two things:\n",
|
||||
"\n",
|
||||
"1. Every `node` we define will receive the current `State` as input and return a value that updates that state.\n",
|
||||
@@ -3056,9 +3068,9 @@
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"display_name": "langgraph",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
"name": "langgraph"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
@@ -3070,7 +3082,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.1"
|
||||
"version": "3.11.9"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 554 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 248 KiB After Width: | Height: | Size: 202 KiB |
|
Before Width: | Height: | Size: 863 KiB After Width: | Height: | Size: 371 KiB |
|
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 193 KiB After Width: | Height: | Size: 156 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 354 KiB |
|
Before Width: | Height: | Size: 914 KiB After Width: | Height: | Size: 301 KiB |
|
Before Width: | Height: | Size: 1003 KiB After Width: | Height: | Size: 345 KiB |
|
Before Width: | Height: | Size: 234 KiB After Width: | Height: | Size: 212 KiB |
|
Before Width: | Height: | Size: 829 KiB After Width: | Height: | Size: 341 KiB |
|
Before Width: | Height: | Size: 1.8 MiB After Width: | Height: | Size: 914 KiB |
@@ -11,7 +11,7 @@
|
||||
"source": [
|
||||
"# How to create subgraphs\n",
|
||||
"\n",
|
||||
"For more complex systems, sub-graphs are a useful design principle. Sub-graphs allow you to create and manage different states in different parts of your graph. This allows you build things like [multi-agent teams](./multi_agent/hierarchical_agent_teams.ipynb), where each team can track its own separate state.\n",
|
||||
"For more complex systems, sub-graphs are a useful design principle. Sub-graphs allow you to create and manage different states in different parts of your graph. This allows you build things like [multi-agent teams](https://langchain-ai.github.io/langgraph/tutorials/multi_agent/hierarchical_agent_teams/), where each team can track its own separate state.\n",
|
||||
"\n",
|
||||
""
|
||||
]
|
||||
|
||||
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 501 KiB After Width: | Height: | Size: 701 KiB |
@@ -15,6 +15,7 @@
|
||||
"\n",
|
||||
"```\n",
|
||||
"ollama pull llama3-groq-tool-use\n",
|
||||
"ollama pull llama3.1\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"And also, we'll use the Ollama partner package.\n",
|
||||
@@ -39,35 +40,39 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"execution_count": 6,
|
||||
"id": "120c1da8-e45e-4ffa-9ac1-a536026c7e1c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\n",
|
||||
"\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m24.0\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m24.1.2\u001b[0m\n",
|
||||
"\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n",
|
||||
"Note: you may need to restart the kernel to use updated packages.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"%pip install -qU langchain-ollama"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"execution_count": 8,
|
||||
"id": "32c0504b-007a-4af6-9976-c7294ed26b73",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"USER_AGENT environment variable not set, consider setting it to identify your requests.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# /// LLM ///\n",
|
||||
"\n",
|
||||
"from langchain_ollama import ChatOllama\n",
|
||||
"\n",
|
||||
"llm = ChatOllama(\n",
|
||||
" model=\"llama3-groq-tool-use\",\n",
|
||||
" # model=\"llama3-groq-tool-use\",\n",
|
||||
" model=\"llama3.1\",\n",
|
||||
" temperature=0,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
@@ -129,14 +134,13 @@
|
||||
" for d in web_results\n",
|
||||
" ]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Tool list\n",
|
||||
"tools = [retrieve_documents, web_search]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"execution_count": 9,
|
||||
"id": "30052f47-2b5d-46f5-9873-eb716145cda1",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -148,11 +152,9 @@
|
||||
"from langgraph.graph.message import AnyMessage, add_messages\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class State(TypedDict):\n",
|
||||
" messages: Annotated[list[AnyMessage], add_messages]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class Assistant:\n",
|
||||
" def __init__(self, runnable: Runnable):\n",
|
||||
" \"\"\"\n",
|
||||
@@ -209,7 +211,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"execution_count": 10,
|
||||
"id": "40504a0b-8a99-4420-a6bf-561c62e893d1",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -282,7 +284,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"execution_count": 11,
|
||||
"id": "43c633d5-e7a7-4b7c-8dc7-760a3b032e95",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -301,9 +303,19 @@
|
||||
"response = predict_react_agent_answer(example)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "bf82fa52-9e6c-4f37-94ae-91450dac602e",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"See trace with llama3.1 here:\n",
|
||||
"\n",
|
||||
"https://smith.langchain.com/public/44d0c7dd-a756-47ad-8025-ee7ae6469ecb/r"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"execution_count": 13,
|
||||
"id": "cd74a0b3-be40-46cd-97bf-ef9676878289",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -311,6 +323,24 @@
|
||||
"example = {\"input\": \"Get me information about the current weather in SF.\"}\n",
|
||||
"response = predict_react_agent_answer(example)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "8cac91bf-c975-44a2-a9fd-99706fee5735",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"See trace with llama3.1 here:\n",
|
||||
"\n",
|
||||
"https://smith.langchain.com/public/7a4938e3-f94f-4e04-a162-bf592fba4643/r"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "74b813cb-18ed-42d8-b313-6ee56ded4bcc",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
|
||||
|
Before Width: | Height: | Size: 974 KiB After Width: | Height: | Size: 344 KiB |
|
Before Width: | Height: | Size: 8.0 MiB After Width: | Height: | Size: 910 KiB |
|
Before Width: | Height: | Size: 8.1 MiB After Width: | Height: | Size: 922 KiB |
|
Before Width: | Height: | Size: 7.9 MiB After Width: | Height: | Size: 969 KiB |
|
Before Width: | Height: | Size: 8.0 MiB After Width: | Height: | Size: 910 KiB |
|
Before Width: | Height: | Size: 2.2 MiB After Width: | Height: | Size: 550 KiB |
@@ -3,7 +3,6 @@
|
||||

|
||||
[](https://pepy.tech/project/langgraph)
|
||||
[](https://github.com/langchain-ai/langgraph/issues)
|
||||
[](https://discord.com/channels/1038097195422978059/1170024642245832774)
|
||||
[](https://langchain-ai.github.io/langgraph/)
|
||||
|
||||
⚡ Building language agents as graphs ⚡
|
||||
|
||||
@@ -452,7 +452,11 @@ class CompiledStateGraph(CompiledGraph):
|
||||
def get_input_schema(
|
||||
self, config: Optional[RunnableConfig] = None
|
||||
) -> type[BaseModel]:
|
||||
if isclass(self.builder.input) and issubclass(self.builder.input, BaseModel):
|
||||
from pydantic import BaseModel as BaseModelP
|
||||
|
||||
if isclass(self.builder.input) and issubclass(
|
||||
self.builder.input, (BaseModel, BaseModelP)
|
||||
):
|
||||
return self.builder.input
|
||||
else:
|
||||
keys = list(self.builder.schemas[self.builder.input].keys())
|
||||
@@ -475,7 +479,11 @@ class CompiledStateGraph(CompiledGraph):
|
||||
def get_output_schema(
|
||||
self, config: Optional[RunnableConfig] = None
|
||||
) -> type[BaseModel]:
|
||||
if isclass(self.builder.input) and issubclass(self.builder.output, BaseModel):
|
||||
from pydantic import BaseModel as BaseModelP
|
||||
|
||||
if isclass(self.builder.input) and issubclass(
|
||||
self.builder.output, (BaseModel, BaseModelP)
|
||||
):
|
||||
return self.builder.output
|
||||
|
||||
return super().get_output_schema(config)
|
||||
@@ -497,7 +505,7 @@ class CompiledStateGraph(CompiledGraph):
|
||||
return SKIP_WRITE
|
||||
elif isinstance(input, dict):
|
||||
return input.get(key, SKIP_WRITE)
|
||||
elif get_type_hints(type(input)).get(key):
|
||||
elif get_type_hints(type(input)):
|
||||
value = getattr(input, key, SKIP_WRITE)
|
||||
return value if value is not None else SKIP_WRITE
|
||||
else:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "langgraph"
|
||||
version = "0.1.14"
|
||||
version = "0.1.15"
|
||||
description = "Building stateful, multi-actor applications with LLMs"
|
||||
authors = []
|
||||
license = "MIT"
|
||||
|
||||
@@ -615,6 +615,232 @@
|
||||
|
||||
'''
|
||||
# ---
|
||||
# name: test_in_one_fan_out_state_graph_waiting_edge_custom_state_class_pydantic1
|
||||
'''
|
||||
graph TD;
|
||||
__start__ --> rewrite_query;
|
||||
analyzer_one --> retriever_one;
|
||||
qa --> __end__;
|
||||
retriever_one --> qa;
|
||||
retriever_two --> qa;
|
||||
rewrite_query --> analyzer_one;
|
||||
rewrite_query -.-> retriever_two;
|
||||
|
||||
'''
|
||||
# ---
|
||||
# name: test_in_one_fan_out_state_graph_waiting_edge_custom_state_class_pydantic1.1
|
||||
dict({
|
||||
'definitions': dict({
|
||||
'InnerObject': dict({
|
||||
'properties': dict({
|
||||
'yo': dict({
|
||||
'title': 'Yo',
|
||||
'type': 'integer',
|
||||
}),
|
||||
}),
|
||||
'required': list([
|
||||
'yo',
|
||||
]),
|
||||
'title': 'InnerObject',
|
||||
'type': 'object',
|
||||
}),
|
||||
}),
|
||||
'properties': dict({
|
||||
'answer': dict({
|
||||
'title': 'Answer',
|
||||
'type': 'string',
|
||||
}),
|
||||
'docs': dict({
|
||||
'items': dict({
|
||||
'type': 'string',
|
||||
}),
|
||||
'title': 'Docs',
|
||||
'type': 'array',
|
||||
}),
|
||||
'inner': dict({
|
||||
'$ref': '#/definitions/InnerObject',
|
||||
}),
|
||||
'query': dict({
|
||||
'title': 'Query',
|
||||
'type': 'string',
|
||||
}),
|
||||
}),
|
||||
'required': list([
|
||||
'query',
|
||||
'inner',
|
||||
'docs',
|
||||
]),
|
||||
'title': 'State',
|
||||
'type': 'object',
|
||||
})
|
||||
# ---
|
||||
# name: test_in_one_fan_out_state_graph_waiting_edge_custom_state_class_pydantic1.2
|
||||
dict({
|
||||
'definitions': dict({
|
||||
'InnerObject': dict({
|
||||
'properties': dict({
|
||||
'yo': dict({
|
||||
'title': 'Yo',
|
||||
'type': 'integer',
|
||||
}),
|
||||
}),
|
||||
'required': list([
|
||||
'yo',
|
||||
]),
|
||||
'title': 'InnerObject',
|
||||
'type': 'object',
|
||||
}),
|
||||
}),
|
||||
'properties': dict({
|
||||
'answer': dict({
|
||||
'title': 'Answer',
|
||||
'type': 'string',
|
||||
}),
|
||||
'docs': dict({
|
||||
'items': dict({
|
||||
'type': 'string',
|
||||
}),
|
||||
'title': 'Docs',
|
||||
'type': 'array',
|
||||
}),
|
||||
'inner': dict({
|
||||
'$ref': '#/definitions/InnerObject',
|
||||
}),
|
||||
'query': dict({
|
||||
'title': 'Query',
|
||||
'type': 'string',
|
||||
}),
|
||||
}),
|
||||
'required': list([
|
||||
'query',
|
||||
'inner',
|
||||
'docs',
|
||||
]),
|
||||
'title': 'State',
|
||||
'type': 'object',
|
||||
})
|
||||
# ---
|
||||
# name: test_in_one_fan_out_state_graph_waiting_edge_custom_state_class_pydantic2
|
||||
'''
|
||||
graph TD;
|
||||
__start__ --> rewrite_query;
|
||||
analyzer_one --> retriever_one;
|
||||
qa --> __end__;
|
||||
retriever_one --> qa;
|
||||
retriever_two --> qa;
|
||||
rewrite_query --> analyzer_one;
|
||||
rewrite_query -.-> retriever_two;
|
||||
|
||||
'''
|
||||
# ---
|
||||
# name: test_in_one_fan_out_state_graph_waiting_edge_custom_state_class_pydantic2.1
|
||||
dict({
|
||||
'$defs': dict({
|
||||
'InnerObject': dict({
|
||||
'properties': dict({
|
||||
'yo': dict({
|
||||
'title': 'Yo',
|
||||
'type': 'integer',
|
||||
}),
|
||||
}),
|
||||
'required': list([
|
||||
'yo',
|
||||
]),
|
||||
'title': 'InnerObject',
|
||||
'type': 'object',
|
||||
}),
|
||||
}),
|
||||
'properties': dict({
|
||||
'answer': dict({
|
||||
'anyOf': list([
|
||||
dict({
|
||||
'type': 'string',
|
||||
}),
|
||||
dict({
|
||||
'type': 'null',
|
||||
}),
|
||||
]),
|
||||
'default': None,
|
||||
'title': 'Answer',
|
||||
}),
|
||||
'docs': dict({
|
||||
'items': dict({
|
||||
'type': 'string',
|
||||
}),
|
||||
'title': 'Docs',
|
||||
'type': 'array',
|
||||
}),
|
||||
'inner': dict({
|
||||
'$ref': '#/$defs/InnerObject',
|
||||
}),
|
||||
'query': dict({
|
||||
'title': 'Query',
|
||||
'type': 'string',
|
||||
}),
|
||||
}),
|
||||
'required': list([
|
||||
'query',
|
||||
'inner',
|
||||
'docs',
|
||||
]),
|
||||
'title': 'State',
|
||||
'type': 'object',
|
||||
})
|
||||
# ---
|
||||
# name: test_in_one_fan_out_state_graph_waiting_edge_custom_state_class_pydantic2.2
|
||||
dict({
|
||||
'$defs': dict({
|
||||
'InnerObject': dict({
|
||||
'properties': dict({
|
||||
'yo': dict({
|
||||
'title': 'Yo',
|
||||
'type': 'integer',
|
||||
}),
|
||||
}),
|
||||
'required': list([
|
||||
'yo',
|
||||
]),
|
||||
'title': 'InnerObject',
|
||||
'type': 'object',
|
||||
}),
|
||||
}),
|
||||
'properties': dict({
|
||||
'answer': dict({
|
||||
'anyOf': list([
|
||||
dict({
|
||||
'type': 'string',
|
||||
}),
|
||||
dict({
|
||||
'type': 'null',
|
||||
}),
|
||||
]),
|
||||
'default': None,
|
||||
'title': 'Answer',
|
||||
}),
|
||||
'docs': dict({
|
||||
'items': dict({
|
||||
'type': 'string',
|
||||
}),
|
||||
'title': 'Docs',
|
||||
'type': 'array',
|
||||
}),
|
||||
'inner': dict({
|
||||
'$ref': '#/$defs/InnerObject',
|
||||
}),
|
||||
'query': dict({
|
||||
'title': 'Query',
|
||||
'type': 'string',
|
||||
}),
|
||||
}),
|
||||
'required': list([
|
||||
'query',
|
||||
'inner',
|
||||
'docs',
|
||||
]),
|
||||
'title': 'State',
|
||||
'type': 'object',
|
||||
})
|
||||
# ---
|
||||
# name: test_in_one_fan_out_state_graph_waiting_edge_via_branch
|
||||
'''
|
||||
graph TD;
|
||||
|
||||
@@ -7048,7 +7048,7 @@ def test_in_one_fan_out_state_graph_waiting_edge_via_branch(
|
||||
]
|
||||
|
||||
|
||||
def test_in_one_fan_out_state_graph_waiting_edge_custom_state_class(
|
||||
def test_in_one_fan_out_state_graph_waiting_edge_custom_state_class_pydantic1(
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
from langchain_core.pydantic_v1 import BaseModel, ValidationError
|
||||
@@ -7062,8 +7062,12 @@ def test_in_one_fan_out_state_graph_waiting_edge_custom_state_class(
|
||||
y = [t[1] for t in y]
|
||||
return sorted(operator.add(x, y))
|
||||
|
||||
class InnerObject(BaseModel):
|
||||
yo: int
|
||||
|
||||
class State(BaseModel):
|
||||
query: str
|
||||
inner: InnerObject
|
||||
answer: Optional[str] = None
|
||||
docs: Annotated[list[str], sorted_add]
|
||||
|
||||
@@ -7112,17 +7116,20 @@ def test_in_one_fan_out_state_graph_waiting_edge_custom_state_class(
|
||||
app = workflow.compile()
|
||||
|
||||
assert app.get_graph().draw_mermaid(with_styles=False) == snapshot
|
||||
assert app.get_input_schema().schema() == snapshot
|
||||
assert app.get_output_schema().schema() == snapshot
|
||||
|
||||
with pytest.raises(ValidationError):
|
||||
app.invoke({"query": {}})
|
||||
|
||||
assert app.invoke({"query": "what is weather in sf"}) == {
|
||||
assert app.invoke({"query": "what is weather in sf", "inner": {"yo": 1}}) == {
|
||||
"query": "analyzed: query: what is weather in sf",
|
||||
"docs": ["doc1", "doc2", "doc3", "doc4"],
|
||||
"answer": "doc1,doc2,doc3,doc4",
|
||||
"inner": {"yo": 1},
|
||||
}
|
||||
|
||||
assert [*app.stream({"query": "what is weather in sf"})] == [
|
||||
assert [*app.stream({"query": "what is weather in sf", "inner": {"yo": 1}})] == [
|
||||
{"rewrite_query": {"query": "query: what is weather in sf"}},
|
||||
{"analyzer_one": {"query": "analyzed: query: what is weather in sf"}},
|
||||
{"retriever_two": {"docs": ["doc3", "doc4"]}},
|
||||
@@ -7137,7 +7144,122 @@ def test_in_one_fan_out_state_graph_waiting_edge_custom_state_class(
|
||||
config = {"configurable": {"thread_id": "1"}}
|
||||
|
||||
assert [
|
||||
c for c in app_w_interrupt.stream({"query": "what is weather in sf"}, config)
|
||||
c
|
||||
for c in app_w_interrupt.stream(
|
||||
{"query": "what is weather in sf", "inner": {"yo": 1}}, config
|
||||
)
|
||||
] == [
|
||||
{"rewrite_query": {"query": "query: what is weather in sf"}},
|
||||
{"analyzer_one": {"query": "analyzed: query: what is weather in sf"}},
|
||||
{"retriever_two": {"docs": ["doc3", "doc4"]}},
|
||||
{"retriever_one": {"docs": ["doc1", "doc2"]}},
|
||||
]
|
||||
|
||||
assert [c for c in app_w_interrupt.stream(None, config)] == [
|
||||
{"qa": {"answer": "doc1,doc2,doc3,doc4"}},
|
||||
]
|
||||
|
||||
|
||||
def test_in_one_fan_out_state_graph_waiting_edge_custom_state_class_pydantic2(
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
from pydantic import BaseModel, ValidationError
|
||||
|
||||
def sorted_add(
|
||||
x: list[str], y: Union[list[str], list[tuple[str, str]]]
|
||||
) -> list[str]:
|
||||
if isinstance(y[0], tuple):
|
||||
for rem, _ in y:
|
||||
x.remove(rem)
|
||||
y = [t[1] for t in y]
|
||||
return sorted(operator.add(x, y))
|
||||
|
||||
class InnerObject(BaseModel):
|
||||
yo: int
|
||||
|
||||
class State(BaseModel):
|
||||
query: str
|
||||
inner: InnerObject
|
||||
answer: Optional[str] = None
|
||||
docs: Annotated[list[str], sorted_add]
|
||||
|
||||
class StateUpdate(BaseModel):
|
||||
query: Optional[str] = None
|
||||
answer: Optional[str] = None
|
||||
docs: Optional[list[str]] = None
|
||||
|
||||
def rewrite_query(data: State) -> State:
|
||||
return {"query": f"query: {data.query}"}
|
||||
|
||||
def analyzer_one(data: State) -> State:
|
||||
return StateUpdate(query=f"analyzed: {data.query}")
|
||||
|
||||
def retriever_one(data: State) -> State:
|
||||
return {"docs": ["doc1", "doc2"]}
|
||||
|
||||
def retriever_two(data: State) -> State:
|
||||
time.sleep(0.1)
|
||||
return {"docs": ["doc3", "doc4"]}
|
||||
|
||||
def qa(data: State) -> State:
|
||||
return {"answer": ",".join(data.docs)}
|
||||
|
||||
def decider(data: State) -> str:
|
||||
assert isinstance(data, State)
|
||||
return "retriever_two"
|
||||
|
||||
workflow = StateGraph(State)
|
||||
|
||||
workflow.add_node("rewrite_query", rewrite_query)
|
||||
workflow.add_node("analyzer_one", analyzer_one)
|
||||
workflow.add_node("retriever_one", retriever_one)
|
||||
workflow.add_node("retriever_two", retriever_two)
|
||||
workflow.add_node("qa", qa)
|
||||
|
||||
workflow.set_entry_point("rewrite_query")
|
||||
workflow.add_edge("rewrite_query", "analyzer_one")
|
||||
workflow.add_edge("analyzer_one", "retriever_one")
|
||||
workflow.add_conditional_edges(
|
||||
"rewrite_query", decider, {"retriever_two": "retriever_two"}
|
||||
)
|
||||
workflow.add_edge(["retriever_one", "retriever_two"], "qa")
|
||||
workflow.set_finish_point("qa")
|
||||
|
||||
app = workflow.compile()
|
||||
|
||||
assert app.get_graph().draw_mermaid(with_styles=False) == snapshot
|
||||
assert app.get_input_schema().schema() == snapshot
|
||||
assert app.get_output_schema().schema() == snapshot
|
||||
|
||||
with pytest.raises(ValidationError):
|
||||
app.invoke({"query": {}})
|
||||
|
||||
assert app.invoke({"query": "what is weather in sf", "inner": {"yo": 1}}) == {
|
||||
"query": "analyzed: query: what is weather in sf",
|
||||
"docs": ["doc1", "doc2", "doc3", "doc4"],
|
||||
"answer": "doc1,doc2,doc3,doc4",
|
||||
"inner": {"yo": 1},
|
||||
}
|
||||
|
||||
assert [*app.stream({"query": "what is weather in sf", "inner": {"yo": 1}})] == [
|
||||
{"rewrite_query": {"query": "query: what is weather in sf"}},
|
||||
{"analyzer_one": {"query": "analyzed: query: what is weather in sf"}},
|
||||
{"retriever_two": {"docs": ["doc3", "doc4"]}},
|
||||
{"retriever_one": {"docs": ["doc1", "doc2"]}},
|
||||
{"qa": {"answer": "doc1,doc2,doc3,doc4"}},
|
||||
]
|
||||
|
||||
app_w_interrupt = workflow.compile(
|
||||
checkpointer=MemorySaverAssertImmutable(),
|
||||
interrupt_after=["retriever_one"],
|
||||
)
|
||||
config = {"configurable": {"thread_id": "1"}}
|
||||
|
||||
assert [
|
||||
c
|
||||
for c in app_w_interrupt.stream(
|
||||
{"query": "what is weather in sf", "inner": {"yo": 1}}, config
|
||||
)
|
||||
] == [
|
||||
{"rewrite_query": {"query": "query: what is weather in sf"}},
|
||||
{"analyzer_one": {"query": "analyzed: query: what is weather in sf"}},
|
||||
|
||||